Merge "Migrate DataSaverTile" into main
diff --git a/AconfigFlags.bp b/AconfigFlags.bp
index 904109b..22f736e 100644
--- a/AconfigFlags.bp
+++ b/AconfigFlags.bp
@@ -27,6 +27,7 @@
     ":android.os.vibrator.flags-aconfig-java{.generated_srcjars}",
     ":android.security.flags-aconfig-java{.generated_srcjars}",
     ":android.service.chooser.flags-aconfig-java{.generated_srcjars}",
+    ":android.service.dreams.flags-aconfig-java{.generated_srcjars}",
     ":android.service.notification.flags-aconfig-java{.generated_srcjars}",
     ":android.view.flags-aconfig-java{.generated_srcjars}",
     ":android.view.accessibility.flags-aconfig-java{.generated_srcjars}",
@@ -231,7 +232,6 @@
     name: "android.security.flags-aconfig-java-host",
     aconfig_declarations: "android.security.flags-aconfig",
     host_supported: true,
-    mode: "test",
     defaults: ["framework-minus-apex-aconfig-java-defaults"],
 }
 
@@ -742,6 +742,19 @@
     defaults: ["framework-minus-apex-aconfig-java-defaults"],
 }
 
+// Dreams
+aconfig_declarations {
+    name: "android.service.dreams.flags-aconfig",
+    package: "android.service.dreams",
+    srcs: ["core/java/android/service/dreams/flags.aconfig"],
+}
+
+java_aconfig_library {
+    name: "android.service.dreams.flags-aconfig-java",
+    aconfig_declarations: "android.service.dreams.flags-aconfig",
+    defaults: ["framework-minus-apex-aconfig-java-defaults"],
+}
+
 // Notifications
 aconfig_declarations {
     name: "android.service.notification.flags-aconfig",
diff --git a/Android.bp b/Android.bp
index fa7c97d..676a0f5 100644
--- a/Android.bp
+++ b/Android.bp
@@ -106,7 +106,7 @@
         ":android.hardware.radio.voice-V3-java-source",
         ":android.hardware.security.keymint-V3-java-source",
         ":android.hardware.security.secureclock-V1-java-source",
-        ":android.hardware.thermal-V1-java-source",
+        ":android.hardware.thermal-V2-java-source",
         ":android.hardware.tv.tuner-V2-java-source",
         ":android.security.apc-java-source",
         ":android.security.authorization-java-source",
diff --git a/OWNERS b/OWNERS
index 023bdef..733157f 100644
--- a/OWNERS
+++ b/OWNERS
@@ -37,3 +37,5 @@
 
 per-file *ravenwood* = file:ravenwood/OWNERS
 per-file *Ravenwood* = file:ravenwood/OWNERS
+
+per-file PERFORMANCE_OWNERS = file:/PERFORMANCE_OWNERS
diff --git a/PERFORMANCE_OWNERS b/PERFORMANCE_OWNERS
index 9452ea3..48a0201 100644
--- a/PERFORMANCE_OWNERS
+++ b/PERFORMANCE_OWNERS
@@ -3,3 +3,6 @@
 dualli@google.com
 carmenjackson@google.com
 philipcuadra@google.com
+shayba@google.com
+jdduke@google.com
+shombert@google.com
diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/BackgroundJobsController.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/BackgroundJobsController.java
index 73a80b1..03891bb 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/controllers/BackgroundJobsController.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/BackgroundJobsController.java
@@ -98,7 +98,10 @@
             switch (action) {
                 case Intent.ACTION_PACKAGE_RESTARTED: {
                     synchronized (mLock) {
-                        mPackageStoppedState.add(pkgUid, pkgName, Boolean.TRUE);
+                        // ACTION_PACKAGE_RESTARTED doesn't always mean the app is placed and kept
+                        // in the stopped state, so don't put TRUE in the cache. Remove any existing
+                        // entry and rely on an explicit call to PackageManager's isStopped() API.
+                        mPackageStoppedState.delete(pkgUid, pkgName);
                         updateJobRestrictionsForUidLocked(pkgUid, false);
                     }
                 }
@@ -311,9 +314,15 @@
         if (mPackageStoppedState.contains(uid, packageName)) {
             return mPackageStoppedState.get(uid, packageName);
         }
-        final boolean isStopped = mPackageManagerInternal.isPackageStopped(packageName, uid);
-        mPackageStoppedState.add(uid, packageName, isStopped);
-        return isStopped;
+
+        try {
+            final boolean isStopped = mPackageManagerInternal.isPackageStopped(packageName, uid);
+            mPackageStoppedState.add(uid, packageName, isStopped);
+            return isStopped;
+        } catch (IllegalArgumentException e) {
+            Slog.d(TAG, "Couldn't determine stopped state for unknown package: " + packageName);
+            return false;
+        }
     }
 
     @GuardedBy("mLock")
diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/ConnectivityController.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/ConnectivityController.java
index e06006f..f405083 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/controllers/ConnectivityController.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/ConnectivityController.java
@@ -1769,7 +1769,8 @@
 
     @VisibleForTesting
     class CcConfig {
-        private boolean mFlexIsEnabled = FlexibilityController.FcConfig.DEFAULT_FLEXIBILITY_ENABLED;
+        private boolean mFlexIsEnabled =
+                FlexibilityController.FcConfig.DEFAULT_APPLIED_CONSTRAINTS != 0;
         private boolean mShouldReprocessNetworkCapabilities = false;
 
         /**
diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/FlexibilityController.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/FlexibilityController.java
index 13a474cc..0e67b9a 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/controllers/FlexibilityController.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/FlexibilityController.java
@@ -24,7 +24,6 @@
 import static com.android.server.job.controllers.JobStatus.CONSTRAINT_BATTERY_NOT_LOW;
 import static com.android.server.job.controllers.JobStatus.CONSTRAINT_CHARGING;
 import static com.android.server.job.controllers.JobStatus.CONSTRAINT_CONNECTIVITY;
-import static com.android.server.job.controllers.JobStatus.CONSTRAINT_FLEXIBLE;
 import static com.android.server.job.controllers.JobStatus.CONSTRAINT_IDLE;
 
 import android.annotation.ElapsedRealtimeLong;
@@ -74,17 +73,10 @@
     private static final int JOB_SPECIFIC_FLEXIBLE_CONSTRAINTS = CONSTRAINT_CONNECTIVITY;
 
     /** List of all flexible constraints. */
-    private static final int FLEXIBLE_CONSTRAINTS =
+    @VisibleForTesting
+    static final int FLEXIBLE_CONSTRAINTS =
             JOB_SPECIFIC_FLEXIBLE_CONSTRAINTS | SYSTEM_WIDE_FLEXIBLE_CONSTRAINTS;
 
-    private static final int NUM_JOB_SPECIFIC_FLEXIBLE_CONSTRAINTS =
-            Integer.bitCount(JOB_SPECIFIC_FLEXIBLE_CONSTRAINTS);
-
-    static final int NUM_SYSTEM_WIDE_FLEXIBLE_CONSTRAINTS =
-            Integer.bitCount(SYSTEM_WIDE_FLEXIBLE_CONSTRAINTS);
-
-    static final int NUM_FLEXIBLE_CONSTRAINTS = Integer.bitCount(FLEXIBLE_CONSTRAINTS);
-
     private static final long NO_LIFECYCLE_END = Long.MAX_VALUE;
 
     /**
@@ -100,9 +92,15 @@
     private long mUnseenConstraintGracePeriodMs =
             FcConfig.DEFAULT_UNSEEN_CONSTRAINT_GRACE_PERIOD_MS;
 
-    @VisibleForTesting
+    /** Set of constraints supported on this device for flex scheduling. */
+    private final int mSupportedFlexConstraints;
+
     @GuardedBy("mLock")
-    boolean mFlexibilityEnabled = FcConfig.DEFAULT_FLEXIBILITY_ENABLED;
+    private boolean mFlexibilityEnabled;
+
+    /** Set of constraints that will be used in the flex policy. */
+    @GuardedBy("mLock")
+    private int mAppliedConstraints = FcConfig.DEFAULT_APPLIED_CONSTRAINTS;
 
     private long mMinTimeBetweenFlexibilityAlarmsMs =
             FcConfig.DEFAULT_MIN_TIME_BETWEEN_FLEXIBILITY_ALARMS_MS;
@@ -118,9 +116,6 @@
      */
     private int[] mPercentToDropConstraints;
 
-    @VisibleForTesting
-    boolean mDeviceSupportsFlexConstraints;
-
     /**
      * Keeps track of what flexible constraints are satisfied at the moment.
      * Is updated by the other controllers.
@@ -178,7 +173,7 @@
                             if (!js.hasFlexibilityConstraint()) {
                                 continue;
                             }
-                            mFlexibilityTracker.resetJobNumDroppedConstraints(js, nowElapsed);
+                            mFlexibilityTracker.calculateNumDroppedConstraints(js, nowElapsed);
                             mFlexibilityAlarmQueue.scheduleDropNumConstraintsAlarm(js, nowElapsed);
                         }
                     }
@@ -186,15 +181,23 @@
             };
 
     private static final int MSG_UPDATE_JOBS = 0;
+    private static final int MSG_UPDATE_JOB = 1;
 
     public FlexibilityController(
             JobSchedulerService service, PrefetchController prefetchController) {
         super(service);
         mHandler = new FcHandler(AppSchedulingModuleThread.get().getLooper());
-        mDeviceSupportsFlexConstraints = !mContext.getPackageManager().hasSystemFeature(
-                PackageManager.FEATURE_AUTOMOTIVE);
-        mFlexibilityEnabled &= mDeviceSupportsFlexConstraints;
-        mFlexibilityTracker = new FlexibilityTracker(NUM_FLEXIBLE_CONSTRAINTS);
+        if (mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE)
+                || mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_EMBEDDED)) {
+            // Embedded devices have no user-installable apps. Assume all jobs are critical
+            // and can't be flexed.
+            mSupportedFlexConstraints = 0;
+        } else {
+            // TODO(236261941): handle devices without a battery
+            mSupportedFlexConstraints = FLEXIBLE_CONSTRAINTS;
+        }
+        mFlexibilityEnabled = (mAppliedConstraints & mSupportedFlexConstraints) != 0;
+        mFlexibilityTracker = new FlexibilityTracker(Integer.bitCount(mSupportedFlexConstraints));
         mFcConfig = new FcConfig();
         mFlexibilityAlarmQueue = new FlexibilityAlarmQueue(
                 mContext, AppSchedulingModuleThread.get().getLooper());
@@ -218,10 +221,12 @@
     public void maybeStartTrackingJobLocked(JobStatus js, JobStatus lastJob) {
         if (js.hasFlexibilityConstraint()) {
             final long nowElapsed = sElapsedRealtimeClock.millis();
-            if (!mDeviceSupportsFlexConstraints) {
+            if (mSupportedFlexConstraints == 0) {
                 js.setFlexibilityConstraintSatisfied(nowElapsed, true);
                 return;
             }
+            js.setNumAppliedFlexibleConstraints(
+                    Integer.bitCount(getRelevantAppliedConstraintsLocked(js)));
             js.setFlexibilityConstraintSatisfied(nowElapsed, isFlexibilitySatisfiedLocked(js));
             mFlexibilityTracker.add(js);
             js.setTrackingController(JobStatus.TRACKING_FLEXIBILITY);
@@ -266,6 +271,14 @@
                 || mService.isCurrentlyRunningLocked(js);
     }
 
+    @VisibleForTesting
+    @GuardedBy("mLock")
+    int getRelevantAppliedConstraintsLocked(@NonNull JobStatus js) {
+        final int relevantConstraints = SYSTEM_WIDE_FLEXIBLE_CONSTRAINTS
+                | (js.canApplyTransportAffinities() ? CONSTRAINT_CONNECTIVITY : 0);
+        return mAppliedConstraints & relevantConstraints;
+    }
+
     /**
      * Returns whether there are enough constraints satisfied to allow running the job from flex's
      * perspective. This takes into account unseen constraint combinations and expectations around
@@ -274,7 +287,7 @@
     @VisibleForTesting
     @GuardedBy("mLock")
     boolean hasEnoughSatisfiedConstraintsLocked(@NonNull JobStatus js) {
-        final int satisfiedConstraints = mSatisfiedFlexibleConstraints
+        final int satisfiedConstraints = mSatisfiedFlexibleConstraints & mAppliedConstraints
                 & (SYSTEM_WIDE_FLEXIBLE_CONSTRAINTS
                         | (js.areTransportAffinitiesSatisfied() ? CONSTRAINT_CONNECTIVITY : 0));
         final int numSatisfied = Integer.bitCount(satisfiedConstraints);
@@ -296,8 +309,7 @@
         // count have not been seen recently enough, then assume they won't be seen anytime soon,
         // so don't force the job to wait longer. If any combinations with a higher count have been
         // seen recently, then the job can potentially wait for those combinations.
-        final int irrelevantConstraints = ~(SYSTEM_WIDE_FLEXIBLE_CONSTRAINTS
-                | (js.canApplyTransportAffinities() ? CONSTRAINT_CONNECTIVITY : 0));
+        final int irrelevantConstraints = ~getRelevantAppliedConstraintsLocked(js);
         for (int i = mLastSeenConstraintTimesElapsed.size() - 1; i >= 0; --i) {
             final int constraints = mLastSeenConstraintTimesElapsed.keyAt(i);
             if ((constraints & irrelevantConstraints) != 0) {
@@ -515,9 +527,9 @@
                     for (int j = 0; j < mFlexibilityTracker.size(); j++) {
                         final ArraySet<JobStatus> jobs = mFlexibilityTracker
                                 .getJobsByNumRequiredConstraints(j);
-                        for (int i = 0; i < jobs.size(); i++) {
+                        for (int i = jobs.size() - 1; i >= 0; --i) {
                             JobStatus js = jobs.valueAt(i);
-                            mFlexibilityTracker.resetJobNumDroppedConstraints(js, nowElapsed);
+                            mFlexibilityTracker.updateFlexibleConstraints(js, nowElapsed);
                             mFlexibilityAlarmQueue.scheduleDropNumConstraintsAlarm(js, nowElapsed);
                             if (js.setFlexibilityConstraintSatisfied(
                                     nowElapsed, isFlexibilitySatisfiedLocked(js))) {
@@ -579,18 +591,46 @@
             mTrackedJobs.get(js.getNumRequiredFlexibleConstraints()).remove(js);
         }
 
-        public void resetJobNumDroppedConstraints(JobStatus js, long nowElapsed) {
+        /**
+         * Updates applied and dropped constraints for the job.
+         */
+        public void updateFlexibleConstraints(JobStatus js, long nowElapsed) {
+            final int prevNumRequired = js.getNumRequiredFlexibleConstraints();
+
+            final int numAppliedConstraints =
+                    Integer.bitCount(getRelevantAppliedConstraintsLocked(js));
+            js.setNumAppliedFlexibleConstraints(numAppliedConstraints);
+
             final int curPercent = getCurPercentOfLifecycleLocked(js, nowElapsed);
             int toDrop = 0;
-            final int jsMaxFlexibleConstraints = NUM_SYSTEM_WIDE_FLEXIBLE_CONSTRAINTS
-                    + (js.canApplyTransportAffinities() ? 1 : 0);
+            for (int i = 0; i < numAppliedConstraints; i++) {
+                if (curPercent >= mPercentToDropConstraints[i]) {
+                    toDrop++;
+                }
+            }
+            js.setNumDroppedFlexibleConstraints(toDrop);
+
+            if (prevNumRequired == js.getNumRequiredFlexibleConstraints()) {
+                return;
+            }
+            mTrackedJobs.get(prevNumRequired).remove(js);
+            add(js);
+        }
+
+        /**
+         * Calculates the number of constraints that should be dropped for the job, based on how
+         * far along the job is into its lifecycle.
+         */
+        public void calculateNumDroppedConstraints(JobStatus js, long nowElapsed) {
+            final int curPercent = getCurPercentOfLifecycleLocked(js, nowElapsed);
+            int toDrop = 0;
+            final int jsMaxFlexibleConstraints = js.getNumAppliedFlexibleConstraints();
             for (int i = 0; i < jsMaxFlexibleConstraints; i++) {
                 if (curPercent >= mPercentToDropConstraints[i]) {
                     toDrop++;
                 }
             }
-            adjustJobsRequiredConstraints(
-                    js, js.getNumDroppedFlexibleConstraints() - toDrop, nowElapsed);
+            setNumDroppedFlexibleConstraints(js, toDrop);
         }
 
         /** Returns all tracked jobs. */
@@ -599,17 +639,14 @@
         }
 
         /**
-         * Adjusts number of required flexible constraints and sorts it into the tracker.
-         * Returns false if the job status's number of flexible constraints is now 0.
+         * Updates the number of dropped flexible constraints and sorts it into the tracker.
          */
-        public boolean adjustJobsRequiredConstraints(JobStatus js, int adjustBy, long nowElapsed) {
-            if (adjustBy != 0) {
+        public void setNumDroppedFlexibleConstraints(JobStatus js, int numDropped) {
+            if (numDropped != js.getNumDroppedFlexibleConstraints()) {
                 remove(js);
-                js.adjustNumRequiredFlexibleConstraints(adjustBy);
-                js.setFlexibilityConstraintSatisfied(nowElapsed, isFlexibilitySatisfiedLocked(js));
+                js.setNumDroppedFlexibleConstraints(numDropped);
                 add(js);
             }
-            return js.getNumRequiredFlexibleConstraints() > 0;
         }
 
         public int size() {
@@ -658,8 +695,10 @@
                 if (DEBUG) {
                     Slog.d(TAG, "scheduleDropNumConstraintsAlarm: "
                             + js.getSourcePackageName() + " " + js.getSourceUserId()
+                            + " numApplied: " + js.getNumAppliedFlexibleConstraints()
                             + " numRequired: " + js.getNumRequiredFlexibleConstraints()
-                            + " numSatisfied: " + Integer.bitCount(mSatisfiedFlexibleConstraints)
+                            + " numSatisfied: " + Integer.bitCount(
+                            mSatisfiedFlexibleConstraints & getRelevantAppliedConstraintsLocked(js))
                             + " curTime: " + nowElapsed
                             + " earliest: " + earliest
                             + " latest: " + latest
@@ -669,8 +708,9 @@
                     if (DEBUG) {
                         Slog.d(TAG, "deadline proximity met: " + js);
                     }
-                    mFlexibilityTracker.adjustJobsRequiredConstraints(js,
-                            -js.getNumRequiredFlexibleConstraints(), nowElapsed);
+                    mFlexibilityTracker.setNumDroppedFlexibleConstraints(js,
+                            js.getNumAppliedFlexibleConstraints());
+                    mHandler.obtainMessage(MSG_UPDATE_JOB, js).sendToTarget();
                     return;
                 }
                 if (nextTimeElapsed == NO_LIFECYCLE_END) {
@@ -696,12 +736,15 @@
                 final long nowElapsed = sElapsedRealtimeClock.millis();
                 for (int i = 0; i < expired.size(); i++) {
                     JobStatus js = expired.valueAt(i);
-                    boolean wasFlexibilitySatisfied = js.isConstraintSatisfied(CONSTRAINT_FLEXIBLE);
-
-                    if (mFlexibilityTracker.adjustJobsRequiredConstraints(js, -1, nowElapsed)) {
+                    if (DEBUG) {
+                        Slog.d(TAG, "Alarm fired for " + js.toShortString());
+                    }
+                    mFlexibilityTracker.calculateNumDroppedConstraints(js, nowElapsed);
+                    if (js.getNumRequiredFlexibleConstraints() > 0) {
                         scheduleDropNumConstraintsAlarm(js, nowElapsed);
                     }
-                    if (wasFlexibilitySatisfied != js.isConstraintSatisfied(CONSTRAINT_FLEXIBLE)) {
+                    if (js.setFlexibilityConstraintSatisfied(nowElapsed,
+                            isFlexibilitySatisfiedLocked(js))) {
                         changedJobs.add(js);
                     }
                 }
@@ -725,7 +768,9 @@
                         final long nowElapsed = sElapsedRealtimeClock.millis();
                         final ArraySet<JobStatus> changedJobs = new ArraySet<>();
 
-                        for (int o = 0; o <= NUM_SYSTEM_WIDE_FLEXIBLE_CONSTRAINTS; ++o) {
+                        final int numAppliedSystemWideConstraints = Integer.bitCount(
+                                mAppliedConstraints & SYSTEM_WIDE_FLEXIBLE_CONSTRAINTS);
+                        for (int o = 0; o <= numAppliedSystemWideConstraints; ++o) {
                             final ArraySet<JobStatus> jobsByNumConstraints = mFlexibilityTracker
                                     .getJobsByNumRequiredConstraints(o);
 
@@ -744,6 +789,23 @@
                         }
                     }
                     break;
+
+                case MSG_UPDATE_JOB:
+                    synchronized (mLock) {
+                        final JobStatus js = (JobStatus) msg.obj;
+                        if (DEBUG) {
+                            Slog.d("blah", "Checking on " + js.toShortString());
+                        }
+                        final long nowElapsed = sElapsedRealtimeClock.millis();
+                        if (js.setFlexibilityConstraintSatisfied(
+                                nowElapsed, isFlexibilitySatisfiedLocked(js))) {
+                            // TODO(141645789): add method that will take a single job
+                            ArraySet<JobStatus> changedJob = new ArraySet<>();
+                            changedJob.add(js);
+                            mStateChangedListener.onControllerStateChanged(changedJob);
+                        }
+                    }
+                    break;
             }
         }
     }
@@ -754,7 +816,8 @@
         /** Prefix to use with all constant keys in order to "sub-namespace" the keys. */
         private static final String FC_CONFIG_PREFIX = "fc_";
 
-        static final String KEY_FLEXIBILITY_ENABLED = FC_CONFIG_PREFIX + "enable_flexibility";
+        @VisibleForTesting
+        static final String KEY_APPLIED_CONSTRAINTS = FC_CONFIG_PREFIX + "applied_constraints";
         static final String KEY_DEADLINE_PROXIMITY_LIMIT =
                 FC_CONFIG_PREFIX + "flexibility_deadline_proximity_limit_ms";
         static final String KEY_FALLBACK_FLEXIBILITY_DEADLINE =
@@ -770,7 +833,7 @@
         static final String KEY_UNSEEN_CONSTRAINT_GRACE_PERIOD_MS =
                 FC_CONFIG_PREFIX + "unseen_constraint_grace_period_ms";
 
-        static final boolean DEFAULT_FLEXIBILITY_ENABLED = false;
+        static final int DEFAULT_APPLIED_CONSTRAINTS = 0;
         @VisibleForTesting
         static final long DEFAULT_DEADLINE_PROXIMITY_LIMIT_MS = 15 * MINUTE_IN_MILLIS;
         @VisibleForTesting
@@ -783,11 +846,8 @@
         @VisibleForTesting
         static final long DEFAULT_UNSEEN_CONSTRAINT_GRACE_PERIOD_MS = 3 * DAY_IN_MILLIS;
 
-        /**
-         * If false the controller will not track new jobs
-         * and the flexibility constraint will always be satisfied.
-         */
-        public boolean FLEXIBILITY_ENABLED = DEFAULT_FLEXIBILITY_ENABLED;
+        /** Which constraints to apply/consider in flex policy. */
+        public int APPLIED_CONSTRAINTS = DEFAULT_APPLIED_CONSTRAINTS;
         /** How close to a jobs' deadline all flexible constraints will be dropped. */
         public long DEADLINE_PROXIMITY_LIMIT_MS = DEFAULT_DEADLINE_PROXIMITY_LIMIT_MS;
         /** For jobs that lack a deadline, the time that will be used to drop all constraints by. */
@@ -811,16 +871,19 @@
         public void processConstantLocked(@NonNull DeviceConfig.Properties properties,
                 @NonNull String key) {
             switch (key) {
-                case KEY_FLEXIBILITY_ENABLED:
-                    FLEXIBILITY_ENABLED = properties.getBoolean(key, DEFAULT_FLEXIBILITY_ENABLED)
-                            && mDeviceSupportsFlexConstraints;
-                    if (mFlexibilityEnabled != FLEXIBILITY_ENABLED) {
-                        mFlexibilityEnabled = FLEXIBILITY_ENABLED;
+                case KEY_APPLIED_CONSTRAINTS:
+                    APPLIED_CONSTRAINTS =
+                            properties.getInt(key, DEFAULT_APPLIED_CONSTRAINTS)
+                                    & mSupportedFlexConstraints;
+                    if (mAppliedConstraints != APPLIED_CONSTRAINTS) {
+                        mAppliedConstraints = APPLIED_CONSTRAINTS;
                         mShouldReevaluateConstraints = true;
-                        if (mFlexibilityEnabled) {
+                        if (mAppliedConstraints != 0) {
+                            mFlexibilityEnabled = true;
                             mPrefetchController
                                     .registerPrefetchChangedListener(mPrefetchChangedListener);
                         } else {
+                            mFlexibilityEnabled = false;
                             mPrefetchController
                                     .unRegisterPrefetchChangedListener(mPrefetchChangedListener);
                         }
@@ -893,7 +956,7 @@
 
         private int[] parsePercentToDropString(String s) {
             String[] dropPercentString = s.split(",");
-            int[] dropPercentInt = new int[NUM_FLEXIBLE_CONSTRAINTS];
+            int[] dropPercentInt = new int[Integer.bitCount(FLEXIBLE_CONSTRAINTS)];
             if (dropPercentInt.length != dropPercentString.length) {
                 return DEFAULT_PERCENT_TO_DROP_FLEXIBLE_CONSTRAINTS;
             }
@@ -922,7 +985,7 @@
             pw.println(":");
             pw.increaseIndent();
 
-            pw.print(KEY_FLEXIBILITY_ENABLED, FLEXIBILITY_ENABLED).println();
+            pw.print(KEY_APPLIED_CONSTRAINTS, APPLIED_CONSTRAINTS).println();
             pw.print(KEY_DEADLINE_PROXIMITY_LIMIT, DEADLINE_PROXIMITY_LIMIT_MS).println();
             pw.print(KEY_FALLBACK_FLEXIBILITY_DEADLINE, FALLBACK_FLEXIBILITY_DEADLINE_MS).println();
             pw.print(KEY_MIN_TIME_BETWEEN_FLEXIBILITY_ALARMS_MS,
diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java
index d1f575e..0d5d11e 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java
@@ -24,7 +24,6 @@
 import static com.android.server.job.JobSchedulerService.RESTRICTED_INDEX;
 import static com.android.server.job.JobSchedulerService.WORKING_INDEX;
 import static com.android.server.job.JobSchedulerService.sElapsedRealtimeClock;
-import static com.android.server.job.controllers.FlexibilityController.NUM_SYSTEM_WIDE_FLEXIBLE_CONSTRAINTS;
 import static com.android.server.job.controllers.FlexibilityController.SYSTEM_WIDE_FLEXIBLE_CONSTRAINTS;
 
 import android.annotation.ElapsedRealtimeLong;
@@ -155,7 +154,7 @@
     /**
      * Keeps track of how many flexible constraints must be satisfied for the job to execute.
      */
-    private final int mNumRequiredFlexibleConstraints;
+    private int mNumAppliedFlexibleConstraints;
 
     /**
      * Number of required flexible constraints that have been dropped.
@@ -697,11 +696,7 @@
                 && satisfiesMinWindowException
                 && (numFailures + numSystemStops) != 1
                 && lacksSomeFlexibleConstraints) {
-            mNumRequiredFlexibleConstraints =
-                    NUM_SYSTEM_WIDE_FLEXIBLE_CONSTRAINTS + (mCanApplyTransportAffinities ? 1 : 0);
             requiredConstraints |= CONSTRAINT_FLEXIBLE;
-        } else {
-            mNumRequiredFlexibleConstraints = 0;
         }
 
         this.requiredConstraints = requiredConstraints;
@@ -1527,9 +1522,14 @@
         return (requiredConstraints & CONSTRAINT_FLEXIBLE) != 0;
     }
 
+    /** Returns the number of flexible job constraints being applied to the job. */
+    public int getNumAppliedFlexibleConstraints() {
+        return mNumAppliedFlexibleConstraints;
+    }
+
     /** Returns the number of flexible job constraints required to be satisfied to execute */
     public int getNumRequiredFlexibleConstraints() {
-        return mNumRequiredFlexibleConstraints - mNumDroppedFlexibleConstraints;
+        return mNumAppliedFlexibleConstraints - mNumDroppedFlexibleConstraints;
     }
 
     /**
@@ -2112,9 +2112,14 @@
     }
 
     /** Adjusts the number of required flexible constraints by the given number */
-    public void adjustNumRequiredFlexibleConstraints(int adjustment) {
-        mNumDroppedFlexibleConstraints = Math.max(0, Math.min(mNumRequiredFlexibleConstraints,
-                mNumDroppedFlexibleConstraints - adjustment));
+    public void setNumAppliedFlexibleConstraints(int count) {
+        mNumAppliedFlexibleConstraints = count;
+    }
+
+    /** Sets the number of dropped flexible constraints to the given number */
+    public void setNumDroppedFlexibleConstraints(int count) {
+        mNumDroppedFlexibleConstraints = Math.max(0,
+                Math.min(mNumAppliedFlexibleConstraints, count));
     }
 
     /**
diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/QuotaController.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/QuotaController.java
index 1c29982..8ddbf69 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/controllers/QuotaController.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/QuotaController.java
@@ -36,6 +36,7 @@
 import android.app.ActivityManager;
 import android.app.AlarmManager;
 import android.app.UidObserver;
+import android.app.job.JobInfo;
 import android.app.usage.UsageEvents;
 import android.app.usage.UsageStatsManagerInternal;
 import android.app.usage.UsageStatsManagerInternal.UsageEventListener;
@@ -772,18 +773,23 @@
         if (!jobStatus.shouldTreatAsExpeditedJob()) {
             // If quota is currently "free", then the job can run for the full amount of time,
             // regardless of bucket (hence using charging instead of isQuotaFreeLocked()).
-            if (mService.isBatteryCharging()
-                    // The top and foreground cases here were added because apps in those states
-                    // aren't really restricted and the work could be something the user is
-                    // waiting for. Now that user-initiated jobs are a defined concept, we may
-                    // not need these exemptions as much. However, UIJs are currently limited
-                    // (as of UDC) to data transfer work. There may be other work that could
-                    // rely on this exception. Once we add more UIJ types, we can re-evaluate
-                    // the need for these exceptions.
-                    // TODO: re-evaluate the need for these exceptions
-                    || mTopAppCache.get(jobStatus.getSourceUid())
+            if (mService.isBatteryCharging()) {
+                return mConstants.RUNTIME_FREE_QUOTA_MAX_LIMIT_MS;
+            }
+            // The top and foreground cases here were added because apps in those states
+            // aren't really restricted and the work could be something the user is
+            // waiting for. Now that user-initiated jobs are a defined concept, we may
+            // not need these exemptions as much. However, UIJs are currently limited
+            // (as of UDC) to data transfer work. There may be other work that could
+            // rely on this exception. Once we add more UIJ types, we can re-evaluate
+            // the need for these exceptions.
+            // TODO: re-evaluate the need for these exceptions
+            final boolean isInPrivilegedState = mTopAppCache.get(jobStatus.getSourceUid())
                     || isTopStartedJobLocked(jobStatus)
-                    || isUidInForeground(jobStatus.getSourceUid())) {
+                    || isUidInForeground(jobStatus.getSourceUid());
+            final boolean isJobImportant = jobStatus.getEffectivePriority() >= JobInfo.PRIORITY_HIGH
+                    || (jobStatus.getFlags() & JobInfo.FLAG_IMPORTANT_WHILE_FOREGROUND) != 0;
+            if (isInPrivilegedState && isJobImportant) {
                 return mConstants.RUNTIME_FREE_QUOTA_MAX_LIMIT_MS;
             }
             return getTimeUntilQuotaConsumedLocked(
@@ -2549,7 +2555,25 @@
          */
         @Override
         public void onUsageEvent(int userId, @NonNull UsageEvents.Event event) {
-            mHandler.obtainMessage(MSG_PROCESS_USAGE_EVENT, userId, 0, event).sendToTarget();
+            // Skip posting a message to the handler for events we don't care about.
+            switch (event.getEventType()) {
+                case UsageEvents.Event.ACTIVITY_RESUMED:
+                case UsageEvents.Event.ACTIVITY_PAUSED:
+                case UsageEvents.Event.ACTIVITY_STOPPED:
+                case UsageEvents.Event.ACTIVITY_DESTROYED:
+                case UsageEvents.Event.USER_INTERACTION:
+                case UsageEvents.Event.CHOOSER_ACTION:
+                case UsageEvents.Event.NOTIFICATION_INTERRUPTION:
+                case UsageEvents.Event.NOTIFICATION_SEEN:
+                    mHandler.obtainMessage(MSG_PROCESS_USAGE_EVENT, userId, 0, event)
+                            .sendToTarget();
+                    break;
+                default:
+                    if (DEBUG) {
+                        Slog.d(TAG, "Dropping event " + event.getEventType());
+                    }
+                    break;
+            }
         }
     }
 
diff --git a/apex/jobscheduler/service/java/com/android/server/tare/InternalResourceService.java b/apex/jobscheduler/service/java/com/android/server/tare/InternalResourceService.java
index b8397d2..357e1396 100644
--- a/apex/jobscheduler/service/java/com/android/server/tare/InternalResourceService.java
+++ b/apex/jobscheduler/service/java/com/android/server/tare/InternalResourceService.java
@@ -316,8 +316,25 @@
                  */
                 @Override
                 public void onUsageEvent(int userId, @NonNull UsageEvents.Event event) {
-                    mHandler.obtainMessage(MSG_PROCESS_USAGE_EVENT, userId, 0, event)
-                            .sendToTarget();
+                    // Skip posting a message to the handler for events we don't care about.
+                    switch (event.getEventType()) {
+                        case UsageEvents.Event.ACTIVITY_RESUMED:
+                        case UsageEvents.Event.ACTIVITY_PAUSED:
+                        case UsageEvents.Event.ACTIVITY_STOPPED:
+                        case UsageEvents.Event.ACTIVITY_DESTROYED:
+                        case UsageEvents.Event.USER_INTERACTION:
+                        case UsageEvents.Event.CHOOSER_ACTION:
+                        case UsageEvents.Event.NOTIFICATION_INTERRUPTION:
+                        case UsageEvents.Event.NOTIFICATION_SEEN:
+                            mHandler.obtainMessage(MSG_PROCESS_USAGE_EVENT, userId, 0, event)
+                                    .sendToTarget();
+                            break;
+                        default:
+                            if (DEBUG) {
+                                Slog.d(TAG, "Dropping event " + event.getEventType());
+                            }
+                            break;
+                    }
                 }
             };
 
diff --git a/api/coverage/tools/ExtractFlaggedApis.kt b/api/coverage/tools/ExtractFlaggedApis.kt
index 9ffb704..43caaec 100644
--- a/api/coverage/tools/ExtractFlaggedApis.kt
+++ b/api/coverage/tools/ExtractFlaggedApis.kt
@@ -23,35 +23,43 @@
 /** Usage: extract-flagged-apis <api text file> <output .pb file> */
 fun main(args: Array<String>) {
     var cb = ApiFile.parseApi(listOf(File(args[0])))
-    val flagToApi = mutableMapOf<String, MutableList<String>>()
-    cb.getPackages()
-        .allClasses()
-        .filter { it.methods().size > 0 }
-        .forEach {
-            for (method in it.methods()) {
-                val flagValue =
-                    method.modifiers
-                        .findAnnotation("android.annotation.FlaggedApi")
-                        ?.findAttribute("value")
-                        ?.value
-                        ?.value()
-                if (flagValue != null && flagValue is String) {
-                    val methodQualifiedName = "${it.qualifiedName()}.${method.name()}"
-                    if (flagToApi.containsKey(flagValue)) {
-                        flagToApi.get(flagValue)?.add(methodQualifiedName)
-                    } else {
-                        flagToApi.put(flagValue, mutableListOf(methodQualifiedName))
+    var builder = FlagApiMap.newBuilder()
+    for (pkg in cb.getPackages().packages) {
+        var packageName = pkg.qualifiedName()
+        pkg.allClasses()
+            .filter { it.methods().size > 0 }
+            .forEach {
+                for (method in it.methods()) {
+                    val flagValue =
+                        method.modifiers
+                            .findAnnotation("android.annotation.FlaggedApi")
+                            ?.findAttribute("value")
+                            ?.value
+                            ?.value()
+                    if (flagValue != null && flagValue is String) {
+                        var api =
+                            JavaMethod.newBuilder()
+                                .setPackageName(packageName)
+                                .setClassName(it.fullName())
+                                .setMethodName(method.name())
+                        for (param in method.parameters()) {
+                            api.addParameterTypes(param.type().toTypeString())
+                        }
+                        if (builder.containsFlagToApi(flagValue)) {
+                            var updatedApis =
+                                builder
+                                    .getFlagToApiOrThrow(flagValue)
+                                    .toBuilder()
+                                    .addJavaMethods(api)
+                                    .build()
+                            builder.putFlagToApi(flagValue, updatedApis)
+                        } else {
+                            var apis = FlaggedApis.newBuilder().addJavaMethods(api).build()
+                            builder.putFlagToApi(flagValue, apis)
+                        }
                     }
                 }
             }
-        }
-    var builder = FlagApiMap.newBuilder()
-    for (flag in flagToApi.keys) {
-        var flaggedApis = FlaggedApis.newBuilder()
-        for (method in flagToApi.get(flag).orEmpty()) {
-            flaggedApis.addFlaggedApi(FlaggedApi.newBuilder().setQualifiedName(method))
-        }
-        builder.putFlagToApi(flag, flaggedApis.build())
     }
     val flagApiMap = builder.build()
     FileWriter(args[1]).use { it.write(flagApiMap.toString()) }
diff --git a/api/coverage/tools/extract_flagged_apis.proto b/api/coverage/tools/extract_flagged_apis.proto
index a858108..031d621 100644
--- a/api/coverage/tools/extract_flagged_apis.proto
+++ b/api/coverage/tools/extract_flagged_apis.proto
@@ -25,10 +25,13 @@
 }
 
 message FlaggedApis {
-  repeated FlaggedApi flagged_api = 1;
+  repeated JavaMethod java_methods = 1;
 }
 
-message FlaggedApi {
-  string qualified_name = 1;
+message JavaMethod {
+  string package_name = 1;
+  string class_name = 2;
+  string method_name = 3;
+  repeated string parameter_types = 4;
 }
 
diff --git a/core/api/current.txt b/core/api/current.txt
index d46d07e..d490c3f 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -1216,6 +1216,7 @@
     field public static final int opticalInsetLeft = 16844168; // 0x1010588
     field public static final int opticalInsetRight = 16844170; // 0x101058a
     field public static final int opticalInsetTop = 16844169; // 0x1010589
+    field @FlaggedApi("android.content.pm.sdk_lib_independence") public static final int optional;
     field public static final int order = 16843242; // 0x10101ea
     field public static final int orderInCategory = 16843231; // 0x10101df
     field public static final int ordering = 16843490; // 0x10102e2
@@ -4467,7 +4468,7 @@
     method public void onProvideKeyboardShortcuts(java.util.List<android.view.KeyboardShortcutGroup>, android.view.Menu, int);
     method public android.net.Uri onProvideReferrer();
     method public void onRequestPermissionsResult(int, @NonNull String[], @NonNull int[]);
-    method @FlaggedApi("android.permission.flags.device_aware_permission_apis") public void onRequestPermissionsResult(int, @NonNull String[], @NonNull int[], int);
+    method @FlaggedApi("android.permission.flags.device_aware_permission_apis_enabled") public void onRequestPermissionsResult(int, @NonNull String[], @NonNull int[], int);
     method @CallSuper protected void onRestart();
     method protected void onRestoreInstanceState(@NonNull android.os.Bundle);
     method public void onRestoreInstanceState(@Nullable android.os.Bundle, @Nullable android.os.PersistableBundle);
@@ -4509,14 +4510,14 @@
     method public android.view.DragAndDropPermissions requestDragAndDropPermissions(android.view.DragEvent);
     method public void requestFullscreenMode(int, @Nullable android.os.OutcomeReceiver<java.lang.Void,java.lang.Throwable>);
     method public final void requestPermissions(@NonNull String[], int);
-    method @FlaggedApi("android.permission.flags.device_aware_permission_apis") public final void requestPermissions(@NonNull String[], int, int);
+    method @FlaggedApi("android.permission.flags.device_aware_permission_apis_enabled") public final void requestPermissions(@NonNull String[], int, int);
     method public final void requestShowKeyboardShortcuts();
     method @Deprecated public boolean requestVisibleBehind(boolean);
     method public final boolean requestWindowFeature(int);
     method @NonNull public final <T extends android.view.View> T requireViewById(@IdRes int);
     method public final void runOnUiThread(Runnable);
     method public void setActionBar(@Nullable android.widget.Toolbar);
-    method public void setAllowCrossUidActivitySwitchFromBelow(boolean);
+    method @FlaggedApi("android.security.asm_restrictions_enabled") public void setAllowCrossUidActivitySwitchFromBelow(boolean);
     method public void setContentTransitionManager(android.transition.TransitionManager);
     method public void setContentView(@LayoutRes int);
     method public void setContentView(android.view.View);
@@ -4557,7 +4558,7 @@
     method public void setVrModeEnabled(boolean, @NonNull android.content.ComponentName) throws android.content.pm.PackageManager.NameNotFoundException;
     method public boolean shouldDockBigOverlays();
     method public boolean shouldShowRequestPermissionRationale(@NonNull String);
-    method @FlaggedApi("android.permission.flags.device_aware_permission_apis") public boolean shouldShowRequestPermissionRationale(@NonNull String, int);
+    method @FlaggedApi("android.permission.flags.device_aware_permission_apis_enabled") public boolean shouldShowRequestPermissionRationale(@NonNull String, int);
     method public boolean shouldUpRecreateTask(android.content.Intent);
     method public boolean showAssist(android.os.Bundle);
     method @Deprecated public final void showDialog(int);
@@ -9706,9 +9707,9 @@
     method @RequiresPermission(android.Manifest.permission.DELIVER_COMPANION_MESSAGES) public final void detachSystemDataTransport(int) throws android.companion.DeviceNotAssociatedException;
     method @Nullable public final android.os.IBinder onBind(@NonNull android.content.Intent);
     method @Deprecated @MainThread public void onDeviceAppeared(@NonNull String);
-    method @Deprecated @MainThread public void onDeviceAppeared(@NonNull android.companion.AssociationInfo);
+    method @MainThread public void onDeviceAppeared(@NonNull android.companion.AssociationInfo);
     method @Deprecated @MainThread public void onDeviceDisappeared(@NonNull String);
-    method @Deprecated @MainThread public void onDeviceDisappeared(@NonNull android.companion.AssociationInfo);
+    method @MainThread public void onDeviceDisappeared(@NonNull android.companion.AssociationInfo);
     method @FlaggedApi("android.companion.device_presence") @MainThread public void onDeviceEvent(@NonNull android.companion.AssociationInfo, int);
     field @FlaggedApi("android.companion.device_presence") public static final int DEVICE_EVENT_BLE_APPEARED = 0; // 0x0
     field @FlaggedApi("android.companion.device_presence") public static final int DEVICE_EVENT_BLE_DISAPPEARED = 1; // 0x1
@@ -9838,7 +9839,7 @@
     method public int describeContents();
     method public void enforceCallingUid();
     method @Nullable public String getAttributionTag();
-    method @FlaggedApi("android.permission.flags.device_aware_permission_apis") public int getDeviceId();
+    method @FlaggedApi("android.permission.flags.device_aware_permission_apis_enabled") public int getDeviceId();
     method @Nullable public android.content.AttributionSource getNext();
     method @Nullable public String getPackageName();
     method public int getPid();
@@ -9854,7 +9855,7 @@
     ctor public AttributionSource.Builder(@NonNull android.content.AttributionSource);
     method @NonNull public android.content.AttributionSource build();
     method @NonNull public android.content.AttributionSource.Builder setAttributionTag(@Nullable String);
-    method @FlaggedApi("android.permission.flags.device_aware_permission_apis") @NonNull public android.content.AttributionSource.Builder setDeviceId(int);
+    method @FlaggedApi("android.permission.flags.device_aware_permission_apis_enabled") @NonNull public android.content.AttributionSource.Builder setDeviceId(int);
     method @NonNull public android.content.AttributionSource.Builder setNext(@Nullable android.content.AttributionSource);
     method @FlaggedApi("android.permission.flags.set_next_attribution_source") @NonNull public android.content.AttributionSource.Builder setNextAttributionSource(@NonNull android.content.AttributionSource);
     method @NonNull public android.content.AttributionSource.Builder setPackageName(@Nullable String);
@@ -18787,6 +18788,7 @@
     method @NonNull public java.util.List<android.hardware.camera2.CaptureRequest.Key<?>> getAvailableCaptureRequestKeys();
     method @NonNull public java.util.List<android.hardware.camera2.CaptureResult.Key<?>> getAvailableCaptureResultKeys();
     method public java.util.List<android.hardware.camera2.CaptureRequest.Key<?>> getAvailablePhysicalCameraRequestKeys();
+    method @FlaggedApi("com.android.internal.camera.flags.feature_combination_query") @NonNull public java.util.List<android.hardware.camera2.CameraCharacteristics.Key<?>> getAvailableSessionCharacteristicsKeys();
     method public java.util.List<android.hardware.camera2.CaptureRequest.Key<?>> getAvailableSessionKeys();
     method @NonNull public java.util.List<android.hardware.camera2.CameraCharacteristics.Key<?>> getKeys();
     method @NonNull public java.util.List<android.hardware.camera2.CameraCharacteristics.Key<?>> getKeysNeedingPermission();
@@ -18811,6 +18813,7 @@
     field @NonNull public static final android.hardware.camera2.CameraCharacteristics.Key<int[]> CONTROL_AVAILABLE_VIDEO_STABILIZATION_MODES;
     field @NonNull public static final android.hardware.camera2.CameraCharacteristics.Key<int[]> CONTROL_AWB_AVAILABLE_MODES;
     field @NonNull public static final android.hardware.camera2.CameraCharacteristics.Key<java.lang.Boolean> CONTROL_AWB_LOCK_AVAILABLE;
+    field @FlaggedApi("com.android.internal.camera.flags.camera_ae_mode_low_light_boost") @NonNull public static final android.hardware.camera2.CameraCharacteristics.Key<android.util.Range<java.lang.Float>> CONTROL_LOW_LIGHT_BOOST_INFO_LUMINANCE_RANGE;
     field @NonNull public static final android.hardware.camera2.CameraCharacteristics.Key<java.lang.Integer> CONTROL_MAX_REGIONS_AE;
     field @NonNull public static final android.hardware.camera2.CameraCharacteristics.Key<java.lang.Integer> CONTROL_MAX_REGIONS_AF;
     field @NonNull public static final android.hardware.camera2.CameraCharacteristics.Key<java.lang.Integer> CONTROL_MAX_REGIONS_AWB;
@@ -18939,6 +18942,7 @@
     method @Deprecated public abstract void createReprocessableCaptureSessionByConfigurations(@NonNull android.hardware.camera2.params.InputConfiguration, @NonNull java.util.List<android.hardware.camera2.params.OutputConfiguration>, @NonNull android.hardware.camera2.CameraCaptureSession.StateCallback, @Nullable android.os.Handler) throws android.hardware.camera2.CameraAccessException;
     method public int getCameraAudioRestriction() throws android.hardware.camera2.CameraAccessException;
     method @NonNull public abstract String getId();
+    method @FlaggedApi("com.android.internal.camera.flags.feature_combination_query") @NonNull public android.hardware.camera2.CameraCharacteristics getSessionCharacteristics(@NonNull android.hardware.camera2.params.SessionConfiguration) throws android.hardware.camera2.CameraAccessException;
     method @Deprecated public boolean isSessionConfigurationSupported(@NonNull android.hardware.camera2.params.SessionConfiguration) throws android.hardware.camera2.CameraAccessException;
     method public void setCameraAudioRestriction(int) throws android.hardware.camera2.CameraAccessException;
     field public static final int AUDIO_RESTRICTION_NONE = 0; // 0x0
@@ -19098,6 +19102,7 @@
     field public static final int CONTROL_AE_MODE_ON_AUTO_FLASH = 2; // 0x2
     field public static final int CONTROL_AE_MODE_ON_AUTO_FLASH_REDEYE = 4; // 0x4
     field public static final int CONTROL_AE_MODE_ON_EXTERNAL_FLASH = 5; // 0x5
+    field @FlaggedApi("com.android.internal.camera.flags.camera_ae_mode_low_light_boost") public static final int CONTROL_AE_MODE_ON_LOW_LIGHT_BOOST_BRIGHTNESS_PRIORITY = 6; // 0x6
     field public static final int CONTROL_AE_PRECAPTURE_TRIGGER_CANCEL = 2; // 0x2
     field public static final int CONTROL_AE_PRECAPTURE_TRIGGER_IDLE = 0; // 0x0
     field public static final int CONTROL_AE_PRECAPTURE_TRIGGER_START = 1; // 0x1
@@ -19163,6 +19168,8 @@
     field public static final int CONTROL_EXTENDED_SCENE_MODE_BOKEH_CONTINUOUS = 2; // 0x2
     field public static final int CONTROL_EXTENDED_SCENE_MODE_BOKEH_STILL_CAPTURE = 1; // 0x1
     field public static final int CONTROL_EXTENDED_SCENE_MODE_DISABLED = 0; // 0x0
+    field @FlaggedApi("com.android.internal.camera.flags.camera_ae_mode_low_light_boost") public static final int CONTROL_LOW_LIGHT_BOOST_STATE_ACTIVE = 1; // 0x1
+    field @FlaggedApi("com.android.internal.camera.flags.camera_ae_mode_low_light_boost") public static final int CONTROL_LOW_LIGHT_BOOST_STATE_INACTIVE = 0; // 0x0
     field public static final int CONTROL_MODE_AUTO = 1; // 0x1
     field public static final int CONTROL_MODE_OFF = 0; // 0x0
     field public static final int CONTROL_MODE_OFF_KEEP_STATE = 3; // 0x3
@@ -19483,6 +19490,7 @@
     field @NonNull public static final android.hardware.camera2.CaptureResult.Key<java.lang.Integer> CONTROL_EFFECT_MODE;
     field @NonNull public static final android.hardware.camera2.CaptureResult.Key<java.lang.Boolean> CONTROL_ENABLE_ZSL;
     field @NonNull public static final android.hardware.camera2.CaptureResult.Key<java.lang.Integer> CONTROL_EXTENDED_SCENE_MODE;
+    field @FlaggedApi("com.android.internal.camera.flags.camera_ae_mode_low_light_boost") @NonNull public static final android.hardware.camera2.CaptureResult.Key<java.lang.Integer> CONTROL_LOW_LIGHT_BOOST_STATE;
     field @NonNull public static final android.hardware.camera2.CaptureResult.Key<java.lang.Integer> CONTROL_MODE;
     field @NonNull public static final android.hardware.camera2.CaptureResult.Key<java.lang.Integer> CONTROL_POST_RAW_SENSITIVITY_BOOST;
     field @NonNull public static final android.hardware.camera2.CaptureResult.Key<java.lang.Integer> CONTROL_SCENE_MODE;
@@ -36903,6 +36911,7 @@
     field public static final String ACTION_REGIONAL_PREFERENCES_SETTINGS = "android.settings.REGIONAL_PREFERENCES_SETTINGS";
     field public static final String ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS = "android.settings.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS";
     field public static final String ACTION_REQUEST_MANAGE_MEDIA = "android.settings.REQUEST_MANAGE_MEDIA";
+    field @FlaggedApi("com.android.media.flags.enable_privileged_routing_for_media_routing_control") public static final String ACTION_REQUEST_MEDIA_ROUTING_CONTROL = "android.settings.REQUEST_MEDIA_ROUTING_CONTROL";
     field public static final String ACTION_REQUEST_SCHEDULE_EXACT_ALARM = "android.settings.REQUEST_SCHEDULE_EXACT_ALARM";
     field public static final String ACTION_REQUEST_SET_AUTOFILL_SERVICE = "android.settings.REQUEST_SET_AUTOFILL_SERVICE";
     field public static final String ACTION_SEARCH_SETTINGS = "android.search.action.SEARCH_SETTINGS";
@@ -39062,7 +39071,6 @@
 
   public class NetworkSecurityPolicy {
     method public static android.security.NetworkSecurityPolicy getInstance();
-    method @FlaggedApi("android.security.certificate_transparency_configuration") public boolean isCertificateTransparencyVerificationRequired(@NonNull String);
     method public boolean isCleartextTrafficPermitted();
     method public boolean isCleartextTrafficPermitted(String);
   }
@@ -39299,7 +39307,7 @@
     method @Nullable public java.util.Date getKeyValidityStart();
     method @NonNull public String getKeystoreAlias();
     method public int getMaxUsageCount();
-    method @FlaggedApi("MGF1_DIGEST_SETTER") @NonNull public java.util.Set<java.lang.String> getMgf1Digests();
+    method @FlaggedApi("android.security.mgf1_digest_setter") @NonNull public java.util.Set<java.lang.String> getMgf1Digests();
     method public int getPurposes();
     method @NonNull public String[] getSignaturePaddings();
     method public int getUserAuthenticationType();
@@ -39307,7 +39315,7 @@
     method public boolean isDevicePropertiesAttestationIncluded();
     method @NonNull public boolean isDigestsSpecified();
     method public boolean isInvalidatedByBiometricEnrollment();
-    method @FlaggedApi("MGF1_DIGEST_SETTER") @NonNull public boolean isMgf1DigestsSpecified();
+    method @FlaggedApi("android.security.mgf1_digest_setter") @NonNull public boolean isMgf1DigestsSpecified();
     method public boolean isRandomizedEncryptionRequired();
     method public boolean isStrongBoxBacked();
     method public boolean isUnlockedDeviceRequired();
@@ -39339,7 +39347,7 @@
     method @NonNull public android.security.keystore.KeyGenParameterSpec.Builder setKeyValidityForOriginationEnd(java.util.Date);
     method @NonNull public android.security.keystore.KeyGenParameterSpec.Builder setKeyValidityStart(java.util.Date);
     method @NonNull public android.security.keystore.KeyGenParameterSpec.Builder setMaxUsageCount(int);
-    method @FlaggedApi("MGF1_DIGEST_SETTER") @NonNull public android.security.keystore.KeyGenParameterSpec.Builder setMgf1Digests(@NonNull java.lang.String...);
+    method @FlaggedApi("android.security.mgf1_digest_setter") @NonNull public android.security.keystore.KeyGenParameterSpec.Builder setMgf1Digests(@NonNull java.lang.String...);
     method @NonNull public android.security.keystore.KeyGenParameterSpec.Builder setRandomizedEncryptionRequired(boolean);
     method @NonNull public android.security.keystore.KeyGenParameterSpec.Builder setSignaturePaddings(java.lang.String...);
     method @NonNull public android.security.keystore.KeyGenParameterSpec.Builder setUnlockedDeviceRequired(boolean);
@@ -39444,14 +39452,14 @@
     method @Nullable public java.util.Date getKeyValidityForOriginationEnd();
     method @Nullable public java.util.Date getKeyValidityStart();
     method public int getMaxUsageCount();
-    method @FlaggedApi("MGF1_DIGEST_SETTER") @NonNull public java.util.Set<java.lang.String> getMgf1Digests();
+    method @FlaggedApi("android.security.mgf1_digest_setter") @NonNull public java.util.Set<java.lang.String> getMgf1Digests();
     method public int getPurposes();
     method @NonNull public String[] getSignaturePaddings();
     method public int getUserAuthenticationType();
     method public int getUserAuthenticationValidityDurationSeconds();
     method public boolean isDigestsSpecified();
     method public boolean isInvalidatedByBiometricEnrollment();
-    method @FlaggedApi("MGF1_DIGEST_SETTER") @NonNull public boolean isMgf1DigestsSpecified();
+    method @FlaggedApi("android.security.mgf1_digest_setter") @NonNull public boolean isMgf1DigestsSpecified();
     method public boolean isRandomizedEncryptionRequired();
     method public boolean isUnlockedDeviceRequired();
     method public boolean isUserAuthenticationRequired();
@@ -39473,7 +39481,7 @@
     method @NonNull public android.security.keystore.KeyProtection.Builder setKeyValidityForOriginationEnd(java.util.Date);
     method @NonNull public android.security.keystore.KeyProtection.Builder setKeyValidityStart(java.util.Date);
     method @NonNull public android.security.keystore.KeyProtection.Builder setMaxUsageCount(int);
-    method @FlaggedApi("MGF1_DIGEST_SETTER") @NonNull public android.security.keystore.KeyProtection.Builder setMgf1Digests(@Nullable java.lang.String...);
+    method @FlaggedApi("android.security.mgf1_digest_setter") @NonNull public android.security.keystore.KeyProtection.Builder setMgf1Digests(@Nullable java.lang.String...);
     method @NonNull public android.security.keystore.KeyProtection.Builder setRandomizedEncryptionRequired(boolean);
     method @NonNull public android.security.keystore.KeyProtection.Builder setSignaturePaddings(java.lang.String...);
     method @NonNull public android.security.keystore.KeyProtection.Builder setUnlockedDeviceRequired(boolean);
@@ -43359,6 +43367,9 @@
     field public static final String KEY_MMS_UA_PROF_URL_STRING = "uaProfUrl";
     field public static final String KEY_MMS_USER_AGENT_STRING = "userAgent";
     field public static final String KEY_MONTHLY_DATA_CYCLE_DAY_INT = "monthly_data_cycle_day_int";
+    field @FlaggedApi("com.android.internal.telephony.flags.carrier_enabled_satellite_flag") public static final String KEY_NTN_LTE_RSRP_THRESHOLDS_INT_ARRAY = "ntn_lte_rsrp_thresholds_int_array";
+    field @FlaggedApi("com.android.internal.telephony.flags.carrier_enabled_satellite_flag") public static final String KEY_NTN_LTE_RSRQ_THRESHOLDS_INT_ARRAY = "ntn_lte_rsrq_thresholds_int_array";
+    field @FlaggedApi("com.android.internal.telephony.flags.carrier_enabled_satellite_flag") public static final String KEY_NTN_LTE_RSSNR_THRESHOLDS_INT_ARRAY = "ntn_lte_rssnr_thresholds_int_array";
     field public static final String KEY_ONLY_AUTO_SELECT_IN_HOME_NETWORK_BOOL = "only_auto_select_in_home_network";
     field public static final String KEY_ONLY_SINGLE_DC_ALLOWED_INT_ARRAY = "only_single_dc_allowed_int_array";
     field public static final String KEY_OPERATOR_SELECTION_EXPAND_BOOL = "operator_selection_expand_bool";
@@ -43373,6 +43384,7 @@
     field public static final String KEY_OPPORTUNISTIC_NETWORK_EXIT_THRESHOLD_RSSNR_INT = "opportunistic_network_exit_threshold_rssnr_int";
     field public static final String KEY_OPPORTUNISTIC_NETWORK_MAX_BACKOFF_TIME_LONG = "opportunistic_network_max_backoff_time_long";
     field public static final String KEY_OPPORTUNISTIC_NETWORK_PING_PONG_TIME_LONG = "opportunistic_network_ping_pong_time_long";
+    field @FlaggedApi("com.android.internal.telephony.flags.carrier_enabled_satellite_flag") public static final String KEY_PARAMETERS_USED_FOR_NTN_LTE_SIGNAL_BAR_INT = "parameters_used_for_ntn_lte_signal_bar_int";
     field public static final String KEY_PING_TEST_BEFORE_DATA_SWITCH_BOOL = "ping_test_before_data_switch_bool";
     field public static final String KEY_PREFER_2G_BOOL = "prefer_2g_bool";
     field public static final String KEY_PREMIUM_CAPABILITY_MAXIMUM_DAILY_NOTIFICATION_COUNT_INT = "premium_capability_maximum_daily_notification_count_int";
@@ -53869,9 +53881,11 @@
     method @Deprecated public android.view.Display getDefaultDisplay();
     method @NonNull public default android.view.WindowMetrics getMaximumWindowMetrics();
     method public default boolean isCrossWindowBlurEnabled();
+    method @FlaggedApi("com.android.window.flags.trusted_presentation_listener_for_window") public default void registerTrustedPresentationListener(@NonNull android.os.IBinder, @NonNull android.window.TrustedPresentationThresholds, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Boolean>);
     method public default void removeCrossWindowBlurEnabledListener(@NonNull java.util.function.Consumer<java.lang.Boolean>);
     method public default void removeProposedRotationListener(@NonNull java.util.function.IntConsumer);
     method public void removeViewImmediate(android.view.View);
+    method @FlaggedApi("com.android.window.flags.trusted_presentation_listener_for_window") public default void unregisterTrustedPresentationListener(@NonNull java.util.function.Consumer<java.lang.Boolean>);
     field public static final String PROPERTY_ACTIVITY_EMBEDDING_ALLOW_SYSTEM_OVERRIDE = "android.window.PROPERTY_ACTIVITY_EMBEDDING_ALLOW_SYSTEM_OVERRIDE";
     field public static final String PROPERTY_ACTIVITY_EMBEDDING_SPLITS_ENABLED = "android.window.PROPERTY_ACTIVITY_EMBEDDING_SPLITS_ENABLED";
     field public static final String PROPERTY_CAMERA_COMPAT_ALLOW_FORCE_ROTATION = "android.window.PROPERTY_CAMERA_COMPAT_ALLOW_FORCE_ROTATION";
@@ -60852,6 +60866,16 @@
     method public void markSyncReady();
   }
 
+  @FlaggedApi("com.android.window.flags.trusted_presentation_listener_for_window") public final class TrustedPresentationThresholds implements android.os.Parcelable {
+    ctor @FlaggedApi("com.android.window.flags.trusted_presentation_listener_for_window") public TrustedPresentationThresholds(@FloatRange(from=0.0f, fromInclusive=false, to=1.0f) float, @FloatRange(from=0.0f, fromInclusive=false, to=1.0f) float, @IntRange(from=1) int);
+    method @FlaggedApi("com.android.window.flags.trusted_presentation_listener_for_window") public int describeContents();
+    method @FlaggedApi("com.android.window.flags.trusted_presentation_listener_for_window") public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @FlaggedApi("com.android.window.flags.trusted_presentation_listener_for_window") @NonNull public static final android.os.Parcelable.Creator<android.window.TrustedPresentationThresholds> CREATOR;
+    field @FlaggedApi("com.android.window.flags.trusted_presentation_listener_for_window") @FloatRange(from=0.0f, fromInclusive=false, to=1.0f) public final float minAlpha;
+    field @FlaggedApi("com.android.window.flags.trusted_presentation_listener_for_window") @FloatRange(from=0.0f, fromInclusive=false, to=1.0f) public final float minFractionRendered;
+    field @FlaggedApi("com.android.window.flags.trusted_presentation_listener_for_window") @IntRange(from=1) public final int stabilityRequirementMs;
+  }
+
 }
 
 package javax.microedition.khronos.egl {
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index a1465df..9a65388 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -3232,6 +3232,7 @@
     method @FlaggedApi("android.companion.virtual.flags.dynamic_policy") @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public void removeActivityPolicyExemption(@NonNull android.content.ComponentName);
     method public void removeSoundEffectListener(@NonNull android.companion.virtual.VirtualDeviceManager.SoundEffectListener);
     method @FlaggedApi("android.companion.virtual.flags.dynamic_policy") @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public void setDevicePolicy(int, int);
+    method @FlaggedApi("android.companion.virtual.flags.vdm_custom_ime") @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public void setDisplayImePolicy(int, int);
     method @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public void setShowPointerIcon(boolean);
     method @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public void unregisterIntentInterceptor(@NonNull android.companion.virtual.VirtualDeviceManager.IntentInterceptorCallback);
   }
@@ -4008,7 +4009,7 @@
     field public static final int DELETE_FAILED_OWNER_BLOCKED = -4; // 0xfffffffc
     field public static final int DELETE_KEEP_DATA = 1; // 0x1
     field public static final int DELETE_SUCCEEDED = 1; // 0x1
-    field @FlaggedApi("android.permission.flags.device_aware_permission_apis") public static final String EXTRA_REQUEST_PERMISSIONS_DEVICE_ID = "android.content.pm.extra.REQUEST_PERMISSIONS_DEVICE_ID";
+    field @FlaggedApi("android.permission.flags.device_aware_permission_apis_enabled") public static final String EXTRA_REQUEST_PERMISSIONS_DEVICE_ID = "android.content.pm.extra.REQUEST_PERMISSIONS_DEVICE_ID";
     field public static final String EXTRA_REQUEST_PERMISSIONS_LEGACY_ACCESS_PERMISSION_NAMES = "android.content.pm.extra.REQUEST_PERMISSIONS_LEGACY_ACCESS_PERMISSION_NAMES";
     field public static final String EXTRA_REQUEST_PERMISSIONS_NAMES = "android.content.pm.extra.REQUEST_PERMISSIONS_NAMES";
     field public static final String EXTRA_REQUEST_PERMISSIONS_RESULTS = "android.content.pm.extra.REQUEST_PERMISSIONS_RESULTS";
@@ -4089,7 +4090,8 @@
     field @Deprecated public static final int INTENT_FILTER_VERIFICATION_SUCCESS = 1; // 0x1
     field @Deprecated public static final int MASK_PERMISSION_FLAGS = 255; // 0xff
     field public static final int MATCH_ANY_USER = 4194304; // 0x400000
-    field public static final int MATCH_CLONE_PROFILE = 536870912; // 0x20000000
+    field @Deprecated public static final int MATCH_CLONE_PROFILE = 536870912; // 0x20000000
+    field @FlaggedApi("android.content.pm.fix_duplicated_flags") public static final long MATCH_CLONE_PROFILE_LONG = 17179869184L; // 0x400000000L
     field public static final int MATCH_FACTORY_ONLY = 2097152; // 0x200000
     field public static final int MATCH_HIDDEN_UNTIL_INSTALLED_COMPONENTS = 536870912; // 0x20000000
     field public static final int MATCH_INSTANT = 8388608; // 0x800000
@@ -4113,7 +4115,7 @@
 
   public static interface PackageManager.OnPermissionsChangedListener {
     method public void onPermissionsChanged(int);
-    method @FlaggedApi("android.permission.flags.device_aware_permission_apis") public default void onPermissionsChanged(int, @NonNull String);
+    method @FlaggedApi("android.permission.flags.device_aware_permission_apis_enabled") public default void onPermissionsChanged(int, @NonNull String);
   }
 
   public static final class PackageManager.UninstallCompleteCallback implements android.os.Parcelable {
@@ -4195,12 +4197,15 @@
 
   public final class UserProperties implements android.os.Parcelable {
     method public int describeContents();
+    method public int getCrossProfileContentSharingStrategy();
     method public int getShowInQuietMode();
     method public int getShowInSharingSurfaces();
     method public boolean isCredentialShareableWithParent();
     method public boolean isMediaSharedWithParent();
     method public void writeToParcel(@NonNull android.os.Parcel, int);
     field @NonNull public static final android.os.Parcelable.Creator<android.content.pm.UserProperties> CREATOR;
+    field public static final int CROSS_PROFILE_CONTENT_SHARING_DELEGATE_FROM_PARENT = 1; // 0x1
+    field public static final int CROSS_PROFILE_CONTENT_SHARING_NO_DELEGATION = 0; // 0x0
     field public static final int SHOW_IN_QUIET_MODE_DEFAULT = 2; // 0x2
     field public static final int SHOW_IN_QUIET_MODE_HIDDEN = 1; // 0x1
     field public static final int SHOW_IN_QUIET_MODE_PAUSED = 0; // 0x0
@@ -4536,6 +4541,7 @@
 
   @FlaggedApi("com.android.internal.camera.flags.concert_mode") public static final class RequestProcessor.Request {
     ctor @FlaggedApi("com.android.internal.camera.flags.concert_mode") public RequestProcessor.Request(@NonNull java.util.List<java.lang.Integer>, @NonNull java.util.List<android.util.Pair<android.hardware.camera2.CaptureRequest.Key,java.lang.Object>>, int);
+    method @FlaggedApi("com.android.internal.camera.flags.concert_mode") @NonNull public java.util.List<android.util.Pair<android.hardware.camera2.CaptureRequest.Key,java.lang.Object>> getParameters();
   }
 
   @FlaggedApi("com.android.internal.camera.flags.concert_mode") public static interface RequestProcessor.RequestCallback {
@@ -10921,13 +10927,13 @@
     method @NonNull @RequiresPermission(android.Manifest.permission.MANAGE_APP_HIBERNATION) public void onGetUnusedAppCount(@NonNull java.util.function.IntConsumer);
     method @BinderThread public abstract void onGrantOrUpgradeDefaultRuntimePermissions(@NonNull Runnable);
     method @Deprecated @BinderThread public void onOneTimePermissionSessionTimeout(@NonNull String);
-    method @FlaggedApi("android.permission.flags.device_aware_permission_apis") @BinderThread public void onOneTimePermissionSessionTimeout(@NonNull String, int);
+    method @FlaggedApi("android.permission.flags.device_aware_permission_apis_enabled") @BinderThread public void onOneTimePermissionSessionTimeout(@NonNull String, int);
     method @Deprecated @BinderThread public void onRestoreDelayedRuntimePermissionsBackup(@NonNull String, @NonNull android.os.UserHandle, @NonNull java.util.function.Consumer<java.lang.Boolean>);
     method @Deprecated @BinderThread public void onRestoreRuntimePermissionsBackup(@NonNull android.os.UserHandle, @NonNull java.io.InputStream, @NonNull Runnable);
     method @BinderThread public abstract void onRevokeRuntimePermission(@NonNull String, @NonNull String, @NonNull Runnable);
     method @BinderThread public abstract void onRevokeRuntimePermissions(@NonNull java.util.Map<java.lang.String,java.util.List<java.lang.String>>, boolean, int, @NonNull String, @NonNull java.util.function.Consumer<java.util.Map<java.lang.String,java.util.List<java.lang.String>>>);
     method @Deprecated @BinderThread public void onRevokeSelfPermissionsOnKill(@NonNull String, @NonNull java.util.List<java.lang.String>, @NonNull Runnable);
-    method @FlaggedApi("android.permission.flags.device_aware_permission_apis") @BinderThread public void onRevokeSelfPermissionsOnKill(@NonNull String, @NonNull java.util.List<java.lang.String>, int, @NonNull Runnable);
+    method @FlaggedApi("android.permission.flags.device_aware_permission_apis_enabled") @BinderThread public void onRevokeSelfPermissionsOnKill(@NonNull String, @NonNull java.util.List<java.lang.String>, int, @NonNull Runnable);
     method @Deprecated @BinderThread public abstract void onSetRuntimePermissionGrantStateByDeviceAdmin(@NonNull String, @NonNull String, @NonNull String, int, @NonNull java.util.function.Consumer<java.lang.Boolean>);
     method @BinderThread public void onSetRuntimePermissionGrantStateByDeviceAdmin(@NonNull String, @NonNull android.permission.AdminPermissionControlParams, @NonNull java.util.function.Consumer<java.lang.Boolean>);
     method @BinderThread public void onStageAndApplyRuntimePermissionsBackup(@NonNull android.os.UserHandle, @NonNull java.io.InputStream, @NonNull Runnable);
@@ -17173,6 +17179,9 @@
     method @NonNull public default java.util.List<android.content.ComponentName> notifyScreenshotListeners(int);
     method public default void registerTaskFpsCallback(@IntRange(from=0) int, @NonNull java.util.concurrent.Executor, @NonNull android.window.TaskFpsCallback);
     method public default void unregisterTaskFpsCallback(@NonNull android.window.TaskFpsCallback);
+    field public static final int DISPLAY_IME_POLICY_FALLBACK_DISPLAY = 1; // 0x1
+    field public static final int DISPLAY_IME_POLICY_HIDE = 2; // 0x2
+    field public static final int DISPLAY_IME_POLICY_LOCAL = 0; // 0x0
   }
 
   public static class WindowManager.LayoutParams extends android.view.ViewGroup.LayoutParams implements android.os.Parcelable {
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index 98a78cf..39f2737 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -259,6 +259,7 @@
     field public static final String OPSTR_ACTIVITY_RECOGNITION_SOURCE = "android:activity_recognition_source";
     field public static final String OPSTR_MANAGE_ONGOING_CALLS = "android:manage_ongoing_calls";
     field public static final String OPSTR_RECORD_AUDIO_HOTWORD = "android:record_audio_hotword";
+    field public static final String OPSTR_RESERVED_FOR_TESTING = "android:reserved_for_testing";
     field public static final String OPSTR_USE_ICC_AUTH_WITH_DEVICE_IDENTIFIER = "android:use_icc_auth_with_device_identifier";
     field public static final int OP_COARSE_LOCATION = 0; // 0x0
     field public static final int OP_RECORD_AUDIO = 27; // 0x1b
@@ -902,7 +903,7 @@
     ctor public AttributionSource(int, @Nullable String, @Nullable String, @NonNull android.os.IBinder);
     ctor public AttributionSource(int, @Nullable String, @Nullable String, @Nullable java.util.Set<java.lang.String>, @Nullable android.content.AttributionSource);
     ctor @FlaggedApi("android.permission.flags.attribution_source_constructor") public AttributionSource(int, int, @Nullable String, @Nullable String, @NonNull android.os.IBinder, @Nullable String[], @Nullable android.content.AttributionSource);
-    ctor @FlaggedApi("android.permission.flags.device_aware_permission_apis") public AttributionSource(int, int, @Nullable String, @Nullable String, @NonNull android.os.IBinder, @Nullable String[], int, @Nullable android.content.AttributionSource);
+    ctor @FlaggedApi("android.permission.flags.device_aware_permission_apis_enabled") public AttributionSource(int, int, @Nullable String, @Nullable String, @NonNull android.os.IBinder, @Nullable String[], int, @Nullable android.content.AttributionSource);
     method public void enforceCallingPid();
   }
 
@@ -1165,6 +1166,7 @@
   public static final class UserProperties.Builder {
     ctor public UserProperties.Builder();
     method @NonNull public android.content.pm.UserProperties build();
+    method @NonNull public android.content.pm.UserProperties.Builder setCrossProfileContentSharingStrategy(int);
     method @NonNull public android.content.pm.UserProperties.Builder setShowInQuietMode(int);
     method @NonNull public android.content.pm.UserProperties.Builder setShowInSharingSurfaces(int);
   }
@@ -3617,9 +3619,6 @@
     method public default void setShouldShowWithInsecureKeyguard(int, boolean);
     method public default boolean shouldShowSystemDecors(int);
     method @Nullable public default android.graphics.Bitmap snapshotTaskForRecents(@IntRange(from=0) int);
-    field public static final int DISPLAY_IME_POLICY_FALLBACK_DISPLAY = 1; // 0x1
-    field public static final int DISPLAY_IME_POLICY_HIDE = 2; // 0x2
-    field public static final int DISPLAY_IME_POLICY_LOCAL = 0; // 0x0
     field public static final int LARGE_SCREEN_SMALLEST_SCREEN_WIDTH_DP = 600; // 0x258
   }
 
diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java
index c52d27ea..ffed405 100644
--- a/core/java/android/app/Activity.java
+++ b/core/java/android/app/Activity.java
@@ -5572,7 +5572,7 @@
      * @see #shouldShowRequestPermissionRationale
      * @see Context#DEVICE_ID_DEFAULT
      */
-    @FlaggedApi(Flags.FLAG_DEVICE_AWARE_PERMISSION_APIS)
+    @FlaggedApi(Flags.FLAG_DEVICE_AWARE_PERMISSION_APIS_ENABLED)
     public final void requestPermissions(@NonNull String[] permissions, int requestCode,
             int deviceId) {
         if (requestCode < 0) {
@@ -5645,7 +5645,7 @@
      *
      * @see #requestPermissions
      */
-    @FlaggedApi(Flags.FLAG_DEVICE_AWARE_PERMISSION_APIS)
+    @FlaggedApi(Flags.FLAG_DEVICE_AWARE_PERMISSION_APIS_ENABLED)
     public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions,
             @NonNull int[] grantResults, int deviceId) {
         onRequestPermissionsResult(requestCode, permissions, grantResults);
@@ -5678,7 +5678,7 @@
      * @see #requestPermissions
      * @see #onRequestPermissionsResult
      */
-    @FlaggedApi(Flags.FLAG_DEVICE_AWARE_PERMISSION_APIS)
+    @FlaggedApi(Flags.FLAG_DEVICE_AWARE_PERMISSION_APIS_ENABLED)
     public boolean shouldShowRequestPermissionRationale(@NonNull String permission, int deviceId) {
         final PackageManager packageManager = getDeviceId() == deviceId ? getPackageManager()
                 : createDeviceContext(deviceId).getPackageManager();
@@ -9387,6 +9387,7 @@
      * @param allowed {@code true} to disable the UID restrictions; {@code false} to revert back to
      *                            the default behaviour
      */
+    @FlaggedApi(android.security.Flags.FLAG_ASM_RESTRICTIONS_ENABLED)
     public void setAllowCrossUidActivitySwitchFromBelow(boolean allowed) {
         ActivityClient.getInstance().setAllowCrossUidActivitySwitchFromBelow(mToken, allowed);
     }
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index adaaee2..8b39ed6 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -1544,11 +1544,12 @@
         @Override
         public void dumpMemInfo(ParcelFileDescriptor pfd, Debug.MemoryInfo mem, boolean checkin,
                 boolean dumpFullInfo, boolean dumpDalvik, boolean dumpSummaryOnly,
-                boolean dumpUnreachable, String[] args) {
+                boolean dumpUnreachable, boolean dumpAllocatorStats, String[] args) {
             FileOutputStream fout = new FileOutputStream(pfd.getFileDescriptor());
             PrintWriter pw = new FastPrintWriter(fout);
             try {
-                dumpMemInfo(pw, mem, checkin, dumpFullInfo, dumpDalvik, dumpSummaryOnly, dumpUnreachable);
+                dumpMemInfo(pw, mem, checkin, dumpFullInfo, dumpDalvik, dumpSummaryOnly,
+                            dumpUnreachable, dumpAllocatorStats);
             } finally {
                 pw.flush();
                 IoUtils.closeQuietly(pfd);
@@ -1557,7 +1558,8 @@
 
         @NeverCompile
         private void dumpMemInfo(PrintWriter pw, Debug.MemoryInfo memInfo, boolean checkin,
-                boolean dumpFullInfo, boolean dumpDalvik, boolean dumpSummaryOnly, boolean dumpUnreachable) {
+                boolean dumpFullInfo, boolean dumpDalvik, boolean dumpSummaryOnly,
+                boolean dumpUnreachable, boolean dumpAllocatorStats) {
             long nativeMax = Debug.getNativeHeapSize() / 1024;
             long nativeAllocated = Debug.getNativeHeapAllocatedSize() / 1024;
             long nativeFree = Debug.getNativeHeapFreeSize() / 1024;
@@ -1710,6 +1712,9 @@
                 pw.println(" Unreachable memory");
                 pw.print(Debug.getUnreachableMemory(100, showContents));
             }
+            if (dumpAllocatorStats) {
+                Debug.logAllocatorStats();
+            }
         }
 
         @NeverCompile
diff --git a/core/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java
index 71fe47e..ec43184 100644
--- a/core/java/android/app/AppOpsManager.java
+++ b/core/java/android/app/AppOpsManager.java
@@ -16,8 +16,8 @@
 
 package android.app;
 
-import static android.view.contentprotection.flags.Flags.FLAG_CREATE_ACCESSIBILITY_OVERLAY_APP_OP_ENABLED;
 import static android.permission.flags.Flags.FLAG_OP_ENABLE_MOBILE_DATA_BY_USER;
+import static android.view.contentprotection.flags.Flags.FLAG_CREATE_ACCESSIBILITY_OVERLAY_APP_OP_ENABLED;
 
 import static java.lang.Long.max;
 
@@ -1521,9 +1521,17 @@
      */
     public static final int OP_MEDIA_ROUTING_CONTROL = AppProtoEnums.APP_OP_MEDIA_ROUTING_CONTROL;
 
+    /**
+     * Op code for use by tests to avoid interfering history logs that the wider system might
+     * trigger.
+     *
+     * @hide
+     */
+    public static final int OP_RESERVED_FOR_TESTING = AppProtoEnums.APP_OP_RESERVED_FOR_TESTING;
+
     /** @hide */
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
-    public static final int _NUM_OP = 141;
+    public static final int _NUM_OP = 142;
 
     /**
      * All app ops represented as strings.
@@ -1671,6 +1679,7 @@
             OPSTR_CREATE_ACCESSIBILITY_OVERLAY,
             OPSTR_MEDIA_ROUTING_CONTROL,
             OPSTR_ENABLE_MOBILE_DATA_BY_USER,
+            OPSTR_RESERVED_FOR_TESTING,
     })
     public @interface AppOpString {}
 
@@ -2330,6 +2339,17 @@
     public static final String OPSTR_ENABLE_MOBILE_DATA_BY_USER =
             "android:enable_mobile_data_by_user";
 
+    /**
+     * Reserved for use by appop tests so that operations done legitimately by the platform don't
+     * interfere with expected results. Platform code should never use this.
+     *
+     * @hide
+     */
+    @TestApi
+    @SuppressLint("UnflaggedApi")
+    public static final String OPSTR_RESERVED_FOR_TESTING =
+            "android:reserved_for_testing";
+
     /** {@link #sAppOpsToNote} not initialized yet for this op */
     private static final byte SHOULD_COLLECT_NOTE_OP_NOT_INITIALIZED = 0;
     /** Should not collect noting of this app-op in {@link #sAppOpsToNote} */
@@ -2887,6 +2907,8 @@
                 .setPermission(Manifest.permission.MEDIA_ROUTING_CONTROL).build(),
         new AppOpInfo.Builder(OP_ENABLE_MOBILE_DATA_BY_USER, OPSTR_ENABLE_MOBILE_DATA_BY_USER,
                 "ENABLE_MOBILE_DATA_BY_USER").setDefaultMode(AppOpsManager.MODE_ALLOWED).build(),
+        new AppOpInfo.Builder(OP_RESERVED_FOR_TESTING, OPSTR_RESERVED_FOR_TESTING,
+                "OP_RESERVED_FOR_TESTING").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/IApplicationThread.aidl b/core/java/android/app/IApplicationThread.aidl
index 75d8c10..5541e7a 100644
--- a/core/java/android/app/IApplicationThread.aidl
+++ b/core/java/android/app/IApplicationThread.aidl
@@ -129,7 +129,7 @@
     void scheduleTrimMemory(int level);
     void dumpMemInfo(in ParcelFileDescriptor fd, in Debug.MemoryInfo mem, boolean checkin,
             boolean dumpInfo, boolean dumpDalvik, boolean dumpSummaryOnly, boolean dumpUnreachable,
-            in String[] args);
+            boolean dumpAllocatorLogs, in String[] args);
     void dumpMemInfoProto(in ParcelFileDescriptor fd, in Debug.MemoryInfo mem,
             boolean dumpInfo, boolean dumpDalvik, boolean dumpSummaryOnly, boolean dumpUnreachable,
             in String[] args);
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index 1e538c5..90a2659 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -16640,6 +16640,7 @@
                 == DEVICE_OWNER_TYPE_FINANCED;
     }
 
+    // TODO(b/315298076): revert ag/25574027 and update the doc
     /**
      * Called by a device owner or profile owner of an organization-owned managed profile to enable
      * or disable USB data signaling for the device. When disabled, USB data connections
@@ -16649,12 +16650,11 @@
      * {@link #canUsbDataSignalingBeDisabled()} to check whether enabling or disabling USB data
      * signaling is supported on the device.
      *
-     * Starting from {@link Build.VERSION_CODES#VANILLA_ICE_CREAM}, after the USB data signaling
+     * Starting from Android 15, after the USB data signaling
      * policy has been set, {@link PolicyUpdateReceiver#onPolicySetResult(Context, String,
      * Bundle, TargetUser, PolicyUpdateResult)} will notify the admin on whether the policy was
      * successfully set or not. This callback will contain:
      * <ul>
-     * li> The policy identifier {@link DevicePolicyIdentifiers#USB_DATA_SIGNALING_POLICY}
      * <li> The {@link TargetUser} that this policy relates to
      * <li> The {@link PolicyUpdateResult}, which will be
      * {@link PolicyUpdateResult#RESULT_POLICY_SET} if the policy was successfully set or the
diff --git a/core/java/android/companion/CompanionDeviceService.java b/core/java/android/companion/CompanionDeviceService.java
index c99a457..4d0267c 100644
--- a/core/java/android/companion/CompanionDeviceService.java
+++ b/core/java/android/companion/CompanionDeviceService.java
@@ -40,7 +40,6 @@
 
 /**
  * A service that receives calls from the system with device events.
- * See {@link #onDeviceEvent(AssociationInfo, int)}.
  *
  * <p>
  * Companion applications must create a service that {@code extends}
@@ -311,10 +310,7 @@
      * Called by system whenever a device associated with this app is connected.
      *
      * @param associationInfo A record for the companion device.
-     *
-     * @deprecated please override {@link #onDeviceEvent(AssociationInfo, int)} instead.
      */
-    @Deprecated
     @MainThread
     public void onDeviceAppeared(@NonNull AssociationInfo associationInfo) {
         if (!associationInfo.isSelfManaged()) {
@@ -326,10 +322,7 @@
      * Called by system whenever a device associated with this app is disconnected.
      *
      * @param associationInfo A record for the companion device.
-     *
-     * @deprecated please override {@link #onDeviceEvent(AssociationInfo, int)} instead.
      */
-    @Deprecated
     @MainThread
     public void onDeviceDisappeared(@NonNull AssociationInfo associationInfo) {
         if (!associationInfo.isSelfManaged()) {
diff --git a/core/java/android/companion/virtual/IVirtualDevice.aidl b/core/java/android/companion/virtual/IVirtualDevice.aidl
index 3520c0b..12229b1 100644
--- a/core/java/android/companion/virtual/IVirtualDevice.aidl
+++ b/core/java/android/companion/virtual/IVirtualDevice.aidl
@@ -219,6 +219,10 @@
     @EnforcePermission("CREATE_VIRTUAL_DEVICE")
     void setShowPointerIcon(boolean showPointerIcon);
 
+    /** Sets an IME policy for the given display. */
+    @EnforcePermission("CREATE_VIRTUAL_DEVICE")
+    void setDisplayImePolicy(int displayId, int policy);
+
     /**
      * Registers an intent interceptor that will intercept an intent attempting to launch
      * when matching the provided IntentFilter and calls the callback with the intercepted
diff --git a/core/java/android/companion/virtual/VirtualDeviceInternal.java b/core/java/android/companion/virtual/VirtualDeviceInternal.java
index 003dffb..2abeeee 100644
--- a/core/java/android/companion/virtual/VirtualDeviceInternal.java
+++ b/core/java/android/companion/virtual/VirtualDeviceInternal.java
@@ -53,6 +53,7 @@
 import android.os.RemoteException;
 import android.os.ResultReceiver;
 import android.util.ArrayMap;
+import android.view.WindowManager;
 
 import com.android.internal.annotations.GuardedBy;
 
@@ -361,6 +362,14 @@
         }
     }
 
+    void setDisplayImePolicy(int displayId, @WindowManager.DisplayImePolicy int policy) {
+        try {
+            mVirtualDevice.setDisplayImePolicy(displayId, policy);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
     void addActivityListener(
             @CallbackExecutor @NonNull Executor executor,
             @NonNull VirtualDeviceManager.ActivityListener listener) {
diff --git a/core/java/android/companion/virtual/VirtualDeviceManager.java b/core/java/android/companion/virtual/VirtualDeviceManager.java
index 41c90b9..eef60f1 100644
--- a/core/java/android/companion/virtual/VirtualDeviceManager.java
+++ b/core/java/android/companion/virtual/VirtualDeviceManager.java
@@ -63,6 +63,7 @@
 import android.os.RemoteException;
 import android.util.Log;
 import android.view.Surface;
+import android.view.WindowManager;
 
 import com.android.internal.annotations.GuardedBy;
 
@@ -914,6 +915,24 @@
         }
 
         /**
+         * Specifies the IME behavior on the given display. By default, all displays created by
+         * virtual devices have {@link WindowManager#DISPLAY_IME_POLICY_LOCAL}.
+         *
+         * @param displayId the ID of the display to change the IME policy for. It must be owned by
+         *                  this virtual device.
+         * @param policy the IME policy to use on that display
+         * @throws SecurityException if the display is not owned by this device or is not
+         *                           {@link DisplayManager#VIRTUAL_DISPLAY_FLAG_TRUSTED trusted}
+         */
+        @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
+        @FlaggedApi(Flags.FLAG_VDM_CUSTOM_IME)
+        public void setDisplayImePolicy(int displayId, @WindowManager.DisplayImePolicy int policy) {
+            if (Flags.vdmCustomIme()) {
+                mVirtualDeviceInternal.setDisplayImePolicy(displayId, policy);
+            }
+        }
+
+        /**
          * Adds an activity listener to listen for events such as top activity change or virtual
          * display task stack became empty.
          *
diff --git a/core/java/android/content/AttributionSource.java b/core/java/android/content/AttributionSource.java
index b2074a6..a1357c9 100644
--- a/core/java/android/content/AttributionSource.java
+++ b/core/java/android/content/AttributionSource.java
@@ -173,7 +173,7 @@
 
     /** @hide */
     @TestApi
-    @FlaggedApi(Flags.FLAG_DEVICE_AWARE_PERMISSION_APIS)
+    @FlaggedApi(Flags.FLAG_DEVICE_AWARE_PERMISSION_APIS_ENABLED)
     public AttributionSource(int uid, int pid, @Nullable String packageName,
             @Nullable String attributionTag, @NonNull IBinder token,
             @Nullable String[] renouncedPermissions,
@@ -539,7 +539,7 @@
      * <p>
      * This device ID is used for permissions checking during attribution source validation.
      */
-    @FlaggedApi(Flags.FLAG_DEVICE_AWARE_PERMISSION_APIS)
+    @FlaggedApi(Flags.FLAG_DEVICE_AWARE_PERMISSION_APIS_ENABLED)
     public int getDeviceId() {
         return mAttributionSourceState.deviceId;
     }
@@ -727,7 +727,7 @@
          *
          * @return the builder
          */
-        @FlaggedApi(Flags.FLAG_DEVICE_AWARE_PERMISSION_APIS)
+        @FlaggedApi(Flags.FLAG_DEVICE_AWARE_PERMISSION_APIS_ENABLED)
         public @NonNull Builder setDeviceId(int deviceId) {
             checkNotUsed();
             mBuilderFieldsSet |= 0x12;
diff --git a/core/java/android/content/ContentProvider.java b/core/java/android/content/ContentProvider.java
index c86ccfd..c7a75ed 100644
--- a/core/java/android/content/ContentProvider.java
+++ b/core/java/android/content/ContentProvider.java
@@ -117,6 +117,7 @@
  * developer guide.</p>
  * </div>
  */
+@android.ravenwood.annotation.RavenwoodKeepPartialClass
 public abstract class ContentProvider implements ContentInterface, ComponentCallbacks2 {
 
     private static final String TAG = "ContentProvider";
@@ -2781,6 +2782,7 @@
     }
 
     /** @hide */
+    @android.ravenwood.annotation.RavenwoodKeep
     private Uri maybeGetUriWithoutUserId(Uri uri) {
         if (mSingleUser) {
             return uri;
@@ -2789,6 +2791,7 @@
     }
 
     /** @hide */
+    @android.ravenwood.annotation.RavenwoodKeep
     public static int getUserIdFromAuthority(String auth, int defaultUserId) {
         if (auth == null) return defaultUserId;
         int end = auth.lastIndexOf('@');
@@ -2803,17 +2806,20 @@
     }
 
     /** @hide */
+    @android.ravenwood.annotation.RavenwoodKeep
     public static int getUserIdFromAuthority(String auth) {
         return getUserIdFromAuthority(auth, UserHandle.USER_CURRENT);
     }
 
     /** @hide */
+    @android.ravenwood.annotation.RavenwoodKeep
     public static int getUserIdFromUri(Uri uri, int defaultUserId) {
         if (uri == null) return defaultUserId;
         return getUserIdFromAuthority(uri.getAuthority(), defaultUserId);
     }
 
     /** @hide */
+    @android.ravenwood.annotation.RavenwoodKeep
     public static int getUserIdFromUri(Uri uri) {
         return getUserIdFromUri(uri, UserHandle.USER_CURRENT);
     }
@@ -2824,6 +2830,7 @@
      * @hide
      */
     @TestApi
+    @android.ravenwood.annotation.RavenwoodKeep
     public @NonNull static UserHandle getUserHandleFromUri(@NonNull Uri uri) {
         return UserHandle.of(getUserIdFromUri(uri, Process.myUserHandle().getIdentifier()));
     }
@@ -2834,6 +2841,7 @@
      * If there is no userId in the authority, it symply returns the argument
      * @hide
      */
+    @android.ravenwood.annotation.RavenwoodKeep
     public static String getAuthorityWithoutUserId(String auth) {
         if (auth == null) return null;
         int end = auth.lastIndexOf('@');
@@ -2841,6 +2849,7 @@
     }
 
     /** @hide */
+    @android.ravenwood.annotation.RavenwoodKeep
     public static Uri getUriWithoutUserId(Uri uri) {
         if (uri == null) return null;
         Uri.Builder builder = uri.buildUpon();
@@ -2849,6 +2858,7 @@
     }
 
     /** @hide */
+    @android.ravenwood.annotation.RavenwoodKeep
     public static boolean uriHasUserId(Uri uri) {
         if (uri == null) return false;
         return !TextUtils.isEmpty(uri.getUserInfo());
@@ -2872,6 +2882,7 @@
      */
     @NonNull
     @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+    @android.ravenwood.annotation.RavenwoodKeep
     public static Uri createContentUriForUser(
             @NonNull Uri contentUri, @NonNull UserHandle userHandle) {
         if (!ContentResolver.SCHEME_CONTENT.equals(contentUri.getScheme())) {
@@ -2898,6 +2909,7 @@
 
     /** @hide */
     @UnsupportedAppUsage
+    @android.ravenwood.annotation.RavenwoodKeep
     public static Uri maybeAddUserId(Uri uri, int userId) {
         if (uri == null) return null;
         if (userId != UserHandle.USER_CURRENT
diff --git a/core/java/android/content/pm/PackageInstaller.java b/core/java/android/content/pm/PackageInstaller.java
index 457fd63..e395127 100644
--- a/core/java/android/content/pm/PackageInstaller.java
+++ b/core/java/android/content/pm/PackageInstaller.java
@@ -379,7 +379,8 @@
 
     /**
      * If true, the requestor of the unarchival has specified that the app should be unarchived
-     * for all users.
+     * for all users. Sent as part of the {@link android.content.Intent#ACTION_UNARCHIVE_PACKAGE}
+     * intent.
      */
     @FlaggedApi(Flags.FLAG_ARCHIVING)
     public static final String EXTRA_UNARCHIVE_ALL_USERS =
@@ -396,6 +397,9 @@
      * with an intent for a corresponding follow-up action (e.g. storage clearing dialog) or a
      * failure dialog.
      *
+     * <p> Used as part of {@link #requestUnarchive} to return the status of the unarchival through
+     * the {@link IntentSender}.
+     *
      * @see #requestUnarchive
      */
     @FlaggedApi(Flags.FLAG_ARCHIVING)
@@ -704,7 +708,8 @@
     /**
      * The installer responsible for the unarchival is disabled.
      *
-     * <p> Should only be used by the system.
+     * <p> The system will return this status if appropriate. Installers do not need to verify for
+     * this error.
      */
     @FlaggedApi(Flags.FLAG_ARCHIVING)
     public static final int UNARCHIVAL_ERROR_INSTALLER_DISABLED = 4;
@@ -712,7 +717,8 @@
     /**
      * The installer responsible for the unarchival has been uninstalled
      *
-     * <p> Should only be used by the system.
+     * <p> The system will return this status if appropriate. Installers do not need to verify for
+     * this error.
      */
     @FlaggedApi(Flags.FLAG_ARCHIVING)
     public static final int UNARCHIVAL_ERROR_INSTALLER_UNINSTALLED = 5;
@@ -1238,7 +1244,7 @@
      *                      {@code statusReceiver} if timeout happens before commit.
      * @throws IllegalArgumentException if the {@code statusReceiver} from an immutable
      *             {@link android.app.PendingIntent} when caller has a target SDK of API
-     *             {@link android.os.Build.VERSION_CODES#VANILLA_ICE_CREAM} or above.
+     *             35 or above.
      */
     public void commitSessionAfterInstallConstraintsAreMet(int sessionId,
             @NonNull IntentSender statusReceiver, @NonNull InstallConstraints constraints,
@@ -1948,7 +1954,7 @@
          *             {@link #openWrite(String, long, long)} are still open.
          * @throws IllegalArgumentException if the {@code statusReceiver} from an immutable
          *             {@link android.app.PendingIntent} when caller has a target SDK of API
-         *             version {@link android.os.Build.VERSION_CODES#VANILLA_ICE_CREAM} or above.
+         *             version 35 or above.
          *
          * @see android.app.admin.DevicePolicyManager
          * @see #requestUserPreapproval
@@ -1979,7 +1985,7 @@
          *                       individual status codes on how to handle them.
          * @throws IllegalArgumentException if the {@code statusReceiver} from an immutable
          *             {@link android.app.PendingIntent} when caller has a target SDK of API
-         *             {@link android.os.Build.VERSION_CODES#VANILLA_ICE_CREAM} or above.
+         *             35 or above.
          *
          * @hide
          */
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index 607e904..e224329 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -731,7 +731,7 @@
          * @see VirtualDeviceManager.VirtualDevice#getPersistentDeviceId()
          * @see VirtualDeviceManager#PERSISTENT_DEVICE_ID_DEFAULT
          */
-        @FlaggedApi(android.permission.flags.Flags.FLAG_DEVICE_AWARE_PERMISSION_APIS)
+        @FlaggedApi(android.permission.flags.Flags.FLAG_DEVICE_AWARE_PERMISSION_APIS_ENABLED)
         default void onPermissionsChanged(int uid, @NonNull String persistentDeviceId) {
             Objects.requireNonNull(persistentDeviceId);
             if (Objects.equals(persistentDeviceId,
@@ -919,6 +919,10 @@
     @Retention(RetentionPolicy.SOURCE)
     public @interface InstrumentationInfoFlags {}
 
+    //-------------------------------------------------------------------------
+    // Beginning of GET_ and MATCH_ flags
+    //-------------------------------------------------------------------------
+
     /**
      * {@link PackageInfo} flag: return information about
      * activities in the package in {@link PackageInfo#activities}.
@@ -1216,30 +1220,21 @@
      */
     public static final int MATCH_DIRECT_BOOT_AUTO = 0x10000000;
 
-    /**
-     * {@link ResolveInfo} flag: allow matching components across clone profile
-     * <p>
-     * This flag is used only for query and not resolution, the default behaviour would be to
-     * restrict querying across clone profile. This flag would be honored only if caller have
-     * permission {@link Manifest.permission.QUERY_CLONED_APPS}.
-     *
-     *  @hide
-     * <p>
-     */
-    @SystemApi
-    public static final int MATCH_CLONE_PROFILE = 0x20000000;
-
-    /**
-     * @deprecated Use {@link #GET_ATTRIBUTIONS_LONG} to avoid unintended sign extension.
-     */
-    @Deprecated
-    public static final int GET_ATTRIBUTIONS = 0x80000000;
-
     /** @hide */
     @Deprecated
     public static final int MATCH_DEBUG_TRIAGED_MISSING = MATCH_DIRECT_BOOT_AUTO;
 
     /**
+     * @deprecated Use {@link #MATCH_CLONE_PROFILE_LONG} instead.
+     *
+     * @hide
+     */
+    @SuppressLint("UnflaggedApi") // Just adding the @Deprecated annotation
+    @Deprecated
+    @SystemApi
+    public static final int MATCH_CLONE_PROFILE = 0x20000000;
+
+    /**
      * {@link PackageInfo} flag: include system apps that are in the uninstalled state and have
      * been set to be hidden until installed via {@link #setSystemAppState}.
      * @hide
@@ -1257,6 +1252,12 @@
     public static final int MATCH_APEX = 0x40000000;
 
     /**
+     * @deprecated Use {@link #GET_ATTRIBUTIONS_LONG} to avoid unintended sign extension.
+     */
+    @Deprecated
+    public static final int GET_ATTRIBUTIONS = 0x80000000;
+
+    /**
      * {@link PackageInfo} flag: return all attributions declared in the package manifest
      */
     public static final long GET_ATTRIBUTIONS_LONG = 0x80000000L;
@@ -1282,6 +1283,23 @@
     public static final long MATCH_QUARANTINED_COMPONENTS = 1L << 33;
 
     /**
+     * {@link ResolveInfo} flag: allow matching components across clone profile
+     * <p>
+     * This flag is used only for query and not resolution, the default behaviour would be to
+     * restrict querying across clone profile. This flag would be honored only if caller have
+     * permission {@link Manifest.permission.QUERY_CLONED_APPS}.
+     *
+     * @hide
+     */
+    @FlaggedApi(android.content.pm.Flags.FLAG_FIX_DUPLICATED_FLAGS)
+    @SystemApi
+    public static final long MATCH_CLONE_PROFILE_LONG = 1L << 34;
+
+    //-------------------------------------------------------------------------
+    // End of GET_ and MATCH_ flags
+    //-------------------------------------------------------------------------
+
+    /**
      * Flag for {@link #addCrossProfileIntentFilter}: if this flag is set: when
      * resolving an intent that matches the {@code CrossProfileIntentFilter},
      * the current profile will be skipped. Only activities in the target user
@@ -4875,7 +4893,7 @@
      * @hide
      */
     @SystemApi
-    @FlaggedApi(android.permission.flags.Flags.FLAG_DEVICE_AWARE_PERMISSION_APIS)
+    @FlaggedApi(android.permission.flags.Flags.FLAG_DEVICE_AWARE_PERMISSION_APIS_ENABLED)
     public static final String EXTRA_REQUEST_PERMISSIONS_DEVICE_ID =
             "android.content.pm.extra.REQUEST_PERMISSIONS_DEVICE_ID";
 
diff --git a/core/java/android/content/pm/SharedLibraryInfo.java b/core/java/android/content/pm/SharedLibraryInfo.java
index fdd2aa1..25ba725 100644
--- a/core/java/android/content/pm/SharedLibraryInfo.java
+++ b/core/java/android/content/pm/SharedLibraryInfo.java
@@ -142,8 +142,10 @@
         mName = parcel.readString8();
         mVersion = parcel.readLong();
         mType = parcel.readInt();
-        mDeclaringPackage = parcel.readParcelable(null, android.content.pm.VersionedPackage.class);
-        mDependentPackages = parcel.readArrayList(null, android.content.pm.VersionedPackage.class);
+        mDeclaringPackage =
+                parcel.readParcelable(null, android.content.pm.VersionedPackage.class);
+        mDependentPackages =
+                parcel.readArrayList(null, android.content.pm.VersionedPackage.class);
         mDependencies = parcel.createTypedArrayList(SharedLibraryInfo.CREATOR);
         mIsNative = parcel.readBoolean();
     }
diff --git a/core/java/android/content/pm/UserProperties.java b/core/java/android/content/pm/UserProperties.java
index 56e8291..57749d4 100644
--- a/core/java/android/content/pm/UserProperties.java
+++ b/core/java/android/content/pm/UserProperties.java
@@ -68,6 +68,11 @@
             "authAlwaysRequiredToDisableQuietMode";
     private static final String ATTR_DELETE_APP_WITH_PARENT = "deleteAppWithParent";
     private static final String ATTR_ALWAYS_VISIBLE = "alwaysVisible";
+    private static final String ATTR_ALLOW_STOPPING_USER_WITH_DELAYED_LOCKING =
+            "allowStoppingUserWithDelayedLocking";
+
+    private static final String ATTR_CROSS_PROFILE_CONTENT_SHARING_STRATEGY =
+            "crossProfileContentSharingStrategy";
 
     /** Index values of each property (to indicate whether they are present in this object). */
     @IntDef(prefix = "INDEX_", value = {
@@ -86,6 +91,8 @@
             INDEX_SHOW_IN_QUIET_MODE,
             INDEX_SHOW_IN_SHARING_SURFACES,
             INDEX_AUTH_ALWAYS_REQUIRED_TO_DISABLE_QUIET_MODE,
+            INDEX_CROSS_PROFILE_CONTENT_SHARING_STRATEGY,
+            INDEX_ALLOW_STOPPING_USER_WITH_DELAYED_LOCKING,
     })
     @Retention(RetentionPolicy.SOURCE)
     private @interface PropertyIndex {
@@ -105,6 +112,8 @@
     private static final int INDEX_SHOW_IN_QUIET_MODE = 12;
     private static final int INDEX_AUTH_ALWAYS_REQUIRED_TO_DISABLE_QUIET_MODE = 13;
     private static final int INDEX_SHOW_IN_SHARING_SURFACES = 14;
+    private static final int INDEX_CROSS_PROFILE_CONTENT_SHARING_STRATEGY = 15;
+    private static final int INDEX_ALLOW_STOPPING_USER_WITH_DELAYED_LOCKING = 16;
     /** A bit set, mapping each PropertyIndex to whether it is present (1) or absent (0). */
     private long mPropertiesPresent = 0;
 
@@ -365,6 +374,45 @@
      */
     @SuppressLint("UnflaggedApi") // b/306636213
     public static final int SHOW_IN_SHARING_SURFACES_NO = SHOW_IN_LAUNCHER_NO;
+    /**
+     * Possible values for cross profile content sharing strategy for this profile.
+     *
+     * @hide
+     */
+    @IntDef(prefix = {"CROSS_PROFILE_CONTENT_SHARING_STRATEGY_"}, value = {
+            CROSS_PROFILE_CONTENT_SHARING_NO_DELEGATION,
+            CROSS_PROFILE_CONTENT_SHARING_DELEGATE_FROM_PARENT
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface CrossProfileContentSharingStrategy {
+    }
+
+    /**
+     * Signifies that cross-profile content sharing strategy, both to and from this profile, should
+     * not be delegated to any other user/profile.
+     * For ex:
+     * If this property is set for a profile, content sharing applications (such as Android
+     * Sharesheet), should not delegate the decision to share content between that profile and
+     * another profile to whether content sharing is allowed between any other profile/user related
+     * to those profiles. They should instead decide, based upon whether content sharing is
+     * specifically allowed between the two profiles in question.
+     */
+    @SuppressLint("UnflaggedApi") // b/306636213
+    public static final int CROSS_PROFILE_CONTENT_SHARING_NO_DELEGATION = 0;
+
+    /**
+     * Signifies that cross-profile content sharing strategy, both to and from this profile, should
+     * be based upon the strategy used by the parent user of the profile.
+     * For ex:
+     * If this property is set for a profile A, content sharing applications (such as Android
+     * Sharesheet), should share content between profile A and profile B, based upon whether content
+     * sharing is allowed between the parent of profile A and profile B.
+     * If it's also set for profile B, then decision should, in turn be made by considering content
+     * sharing strategy between the parents of both profiles.
+     */
+    @SuppressLint("UnflaggedApi") // b/306636213
+    public static final int CROSS_PROFILE_CONTENT_SHARING_DELEGATE_FROM_PARENT = 1;
+
 
     /**
      * Creates a UserProperties (intended for the SystemServer) that stores a reference to the given
@@ -406,6 +454,7 @@
             setCrossProfileIntentResolutionStrategy(orig.getCrossProfileIntentResolutionStrategy());
             setDeleteAppWithParent(orig.getDeleteAppWithParent());
             setAlwaysVisible(orig.getAlwaysVisible());
+            setAllowStoppingUserWithDelayedLocking(orig.getAllowStoppingUserWithDelayedLocking());
         }
         if (hasManagePermission) {
             // Add items that require MANAGE_USERS or stronger.
@@ -423,6 +472,7 @@
         setCredentialShareableWithParent(orig.isCredentialShareableWithParent());
         setShowInQuietMode(orig.getShowInQuietMode());
         setShowInSharingSurfaces(orig.getShowInSharingSurfaces());
+        setCrossProfileContentSharingStrategy(orig.getCrossProfileContentSharingStrategy());
     }
 
     /**
@@ -680,6 +730,11 @@
         this.mUpdateCrossProfileIntentFiltersOnOTA = val;
         setPresent(INDEX_UPDATE_CROSS_PROFILE_INTENT_FILTERS_ON_OTA);
     }
+    /**
+     Indicate if {@link com.android.server.pm.CrossProfileIntentFilter}s need to be updated during
+     OTA update between user-parent
+     */
+    private boolean mUpdateCrossProfileIntentFiltersOnOTA;
 
     /**
      * Returns whether a profile shares media with its parent user.
@@ -741,12 +796,38 @@
     }
     private boolean mAuthAlwaysRequiredToDisableQuietMode;
 
-    /*
-     Indicate if {@link com.android.server.pm.CrossProfileIntentFilter}s need to be updated during
-     OTA update between user-parent
+    /**
+     * Returns whether a user (usually a profile) is allowed to leave the CE storage unlocked when
+     * stopped.
+     *
+     * <p> Setting this property to true will enable the user's CE storage to remain unlocked when
+     * the user is stopped using
+     * {@link com.android.server.am.ActivityManagerService#stopUserWithDelayedLocking(int,
+     * boolean, IStopUserCallback)}.
+     *
+     * <p> When this property is false, delayed locking may still be applicable at a global
+     * level for all users via the {@code config_multiuserDelayUserDataLocking}. That is, delayed
+     * locking for a user can happen if either the device configuration is set or if this property
+     * is set. When both, the config and the property value is false, the user storage is always
+     * locked when the user is stopped.
+     * @hide
      */
-    private boolean mUpdateCrossProfileIntentFiltersOnOTA;
-
+    public boolean getAllowStoppingUserWithDelayedLocking() {
+        if (isPresent(INDEX_ALLOW_STOPPING_USER_WITH_DELAYED_LOCKING)) {
+            return mAllowStoppingUserWithDelayedLocking;
+        }
+        if (mDefaultProperties != null) {
+            return mDefaultProperties.mAllowStoppingUserWithDelayedLocking;
+        }
+        throw new SecurityException(
+                "You don't have permission to query allowStoppingUserWithDelayedLocking");
+    }
+    /** @hide */
+    public void setAllowStoppingUserWithDelayedLocking(boolean val) {
+        this.mAllowStoppingUserWithDelayedLocking = val;
+        setPresent(INDEX_ALLOW_STOPPING_USER_WITH_DELAYED_LOCKING);
+    }
+    private boolean mAllowStoppingUserWithDelayedLocking;
 
     /**
      * Returns the user's {@link CrossProfileIntentFilterAccessControlLevel}.
@@ -776,8 +857,7 @@
     private @CrossProfileIntentFilterAccessControlLevel int mCrossProfileIntentFilterAccessControl;
 
     /**
-     * Returns the user's {@link CrossProfileIntentResolutionStrategy}. If not explicitly
-     * configured, default value is {@link #CROSS_PROFILE_INTENT_RESOLUTION_STRATEGY_DEFAULT}.
+     * Returns the user's {@link CrossProfileIntentResolutionStrategy}.
      * @return user's {@link CrossProfileIntentResolutionStrategy}.
      *
      * @hide
@@ -792,11 +872,8 @@
         throw new SecurityException("You don't have permission to query "
                 + "crossProfileIntentResolutionStrategy");
     }
-    /**
-     * Sets {@link CrossProfileIntentResolutionStrategy} for the user.
-     * @param val resolution strategy for user
-     * @hide
-     */
+
+    /** @hide */
     public void setCrossProfileIntentResolutionStrategy(
             @CrossProfileIntentResolutionStrategy int val) {
         this.mCrossProfileIntentResolutionStrategy = val;
@@ -804,6 +881,39 @@
     }
     private @CrossProfileIntentResolutionStrategy int mCrossProfileIntentResolutionStrategy;
 
+    /**
+     * Returns the user's {@link CrossProfileContentSharingStrategy}.
+     *
+     * Content sharing applications, such as Android Sharesheet allow sharing of content
+     * (an image, for ex.) between profiles, based upon cross-profile access checks between the
+     * originating and destined profile.
+     * In some cases however, we may want another user (such as profile parent) to serve as the
+     * delegated user to be used for such checks.
+     * To effect the same, clients can fetch this property and accordingly replace the
+     * originating/destined profile by another user for cross-profile access checks.
+     *
+     * @return user's {@link CrossProfileContentSharingStrategy}.
+     */
+    @SuppressLint("UnflaggedApi")  // b/306636213
+    public @CrossProfileContentSharingStrategy int getCrossProfileContentSharingStrategy() {
+        if (isPresent(INDEX_CROSS_PROFILE_CONTENT_SHARING_STRATEGY)) {
+            return mCrossProfileContentSharingStrategy;
+        }
+        if (mDefaultProperties != null) {
+            return mDefaultProperties.mCrossProfileContentSharingStrategy;
+        }
+        throw new SecurityException("You don't have permission to query "
+                + "crossProfileContentSharingStrategy");
+    }
+
+    /** @hide */
+    public void setCrossProfileContentSharingStrategy(
+            @CrossProfileContentSharingStrategy int val) {
+        this.mCrossProfileContentSharingStrategy = val;
+        setPresent(INDEX_CROSS_PROFILE_CONTENT_SHARING_STRATEGY);
+    }
+    private @CrossProfileContentSharingStrategy int mCrossProfileContentSharingStrategy;
+
 
     @Override
     public String toString() {
@@ -825,8 +935,11 @@
                 + ", mCredentialShareableWithParent=" + isCredentialShareableWithParent()
                 + ", mAuthAlwaysRequiredToDisableQuietMode="
                 + isAuthAlwaysRequiredToDisableQuietMode()
+                + ", mAllowStoppingUserWithDelayedLocking="
+                + getAllowStoppingUserWithDelayedLocking()
                 + ", mDeleteAppWithParent=" + getDeleteAppWithParent()
                 + ", mAlwaysVisible=" + getAlwaysVisible()
+                + ", mCrossProfileContentSharingStrategy=" + getCrossProfileContentSharingStrategy()
                 + "}";
     }
 
@@ -854,8 +967,12 @@
                 + isCredentialShareableWithParent());
         pw.println(prefix + "    mAuthAlwaysRequiredToDisableQuietMode="
                 + isAuthAlwaysRequiredToDisableQuietMode());
+        pw.println(prefix + "    mAllowStoppingUserWithDelayedLocking="
+                + getAllowStoppingUserWithDelayedLocking());
         pw.println(prefix + "    mDeleteAppWithParent=" + getDeleteAppWithParent());
         pw.println(prefix + "    mAlwaysVisible=" + getAlwaysVisible());
+        pw.println(prefix + "    mCrossProfileContentSharingStrategy="
+                + getCrossProfileContentSharingStrategy());
     }
 
     /**
@@ -928,12 +1045,17 @@
                 case ATTR_AUTH_ALWAYS_REQUIRED_TO_DISABLE_QUIET_MODE:
                     setAuthAlwaysRequiredToDisableQuietMode(parser.getAttributeBoolean(i));
                     break;
+                case ATTR_ALLOW_STOPPING_USER_WITH_DELAYED_LOCKING:
+                    setAllowStoppingUserWithDelayedLocking(parser.getAttributeBoolean(i));
+                    break;
                 case ATTR_DELETE_APP_WITH_PARENT:
                     setDeleteAppWithParent(parser.getAttributeBoolean(i));
                     break;
                 case ATTR_ALWAYS_VISIBLE:
                     setAlwaysVisible(parser.getAttributeBoolean(i));
                     break;
+                case ATTR_CROSS_PROFILE_CONTENT_SHARING_STRATEGY:
+                    setCrossProfileContentSharingStrategy(parser.getAttributeInt(i));
                 default:
                     Slog.w(LOG_TAG, "Skipping unknown property " + attributeName);
             }
@@ -1000,6 +1122,10 @@
             serializer.attributeBoolean(null, ATTR_AUTH_ALWAYS_REQUIRED_TO_DISABLE_QUIET_MODE,
                     mAuthAlwaysRequiredToDisableQuietMode);
         }
+        if (isPresent(INDEX_ALLOW_STOPPING_USER_WITH_DELAYED_LOCKING)) {
+            serializer.attributeBoolean(null, ATTR_ALLOW_STOPPING_USER_WITH_DELAYED_LOCKING,
+                    mAllowStoppingUserWithDelayedLocking);
+        }
         if (isPresent(INDEX_DELETE_APP_WITH_PARENT)) {
             serializer.attributeBoolean(null, ATTR_DELETE_APP_WITH_PARENT,
                     mDeleteAppWithParent);
@@ -1008,6 +1134,10 @@
             serializer.attributeBoolean(null, ATTR_ALWAYS_VISIBLE,
                     mAlwaysVisible);
         }
+        if (isPresent(INDEX_CROSS_PROFILE_CONTENT_SHARING_STRATEGY)) {
+            serializer.attributeInt(null, ATTR_CROSS_PROFILE_CONTENT_SHARING_STRATEGY,
+                    mCrossProfileContentSharingStrategy);
+        }
     }
 
     // For use only with an object that has already had any permission-lacking fields stripped out.
@@ -1027,8 +1157,10 @@
         dest.writeBoolean(mMediaSharedWithParent);
         dest.writeBoolean(mCredentialShareableWithParent);
         dest.writeBoolean(mAuthAlwaysRequiredToDisableQuietMode);
+        dest.writeBoolean(mAllowStoppingUserWithDelayedLocking);
         dest.writeBoolean(mDeleteAppWithParent);
         dest.writeBoolean(mAlwaysVisible);
+        dest.writeInt(mCrossProfileContentSharingStrategy);
     }
 
     /**
@@ -1052,8 +1184,10 @@
         mMediaSharedWithParent = source.readBoolean();
         mCredentialShareableWithParent = source.readBoolean();
         mAuthAlwaysRequiredToDisableQuietMode = source.readBoolean();
+        mAllowStoppingUserWithDelayedLocking = source.readBoolean();
         mDeleteAppWithParent = source.readBoolean();
         mAlwaysVisible = source.readBoolean();
+        mCrossProfileContentSharingStrategy = source.readInt();
     }
 
     @Override
@@ -1098,8 +1232,11 @@
         private boolean mMediaSharedWithParent = false;
         private boolean mCredentialShareableWithParent = false;
         private boolean mAuthAlwaysRequiredToDisableQuietMode = false;
+        private boolean mAllowStoppingUserWithDelayedLocking = false;
         private boolean mDeleteAppWithParent = false;
         private boolean mAlwaysVisible = false;
+        private @CrossProfileContentSharingStrategy int mCrossProfileContentSharingStrategy =
+                CROSS_PROFILE_CONTENT_SHARING_NO_DELEGATION;
 
         /**
          * @hide
@@ -1215,6 +1352,16 @@
             return this;
         }
 
+        /** Sets the value for {@link #mAllowStoppingUserWithDelayedLocking}
+         * @hide
+         */
+        public Builder setAllowStoppingUserWithDelayedLocking(
+                boolean allowStoppingUserWithDelayedLocking) {
+            mAllowStoppingUserWithDelayedLocking =
+                    allowStoppingUserWithDelayedLocking;
+            return this;
+        }
+
         /** Sets the value for {@link #mDeleteAppWithParent}
          * @hide
          */
@@ -1231,6 +1378,19 @@
             return this;
         }
 
+        /** Sets the value for {@link #mCrossProfileContentSharingStrategy}
+         * @hide
+         */
+
+        @TestApi
+        @SuppressLint("UnflaggedApi") // b/306636213
+        @NonNull
+        public Builder setCrossProfileContentSharingStrategy(@CrossProfileContentSharingStrategy
+                int crossProfileContentSharingStrategy) {
+            mCrossProfileContentSharingStrategy = crossProfileContentSharingStrategy;
+            return this;
+        }
+
         /** Builds a UserProperties object with *all* values populated.
          * @hide
          */
@@ -1252,8 +1412,10 @@
                     mMediaSharedWithParent,
                     mCredentialShareableWithParent,
                     mAuthAlwaysRequiredToDisableQuietMode,
+                    mAllowStoppingUserWithDelayedLocking,
                     mDeleteAppWithParent,
-                    mAlwaysVisible);
+                    mAlwaysVisible,
+                    mCrossProfileContentSharingStrategy);
         }
     } // end Builder
 
@@ -1271,8 +1433,10 @@
             boolean mediaSharedWithParent,
             boolean credentialShareableWithParent,
             boolean authAlwaysRequiredToDisableQuietMode,
+            boolean allowStoppingUserWithDelayedLocking,
             boolean deleteAppWithParent,
-            boolean alwaysVisible) {
+            boolean alwaysVisible,
+            @CrossProfileContentSharingStrategy int crossProfileContentSharingStrategy) {
         mDefaultProperties = null;
         setShowInLauncher(showInLauncher);
         setStartWithParent(startWithParent);
@@ -1288,7 +1452,9 @@
         setCredentialShareableWithParent(credentialShareableWithParent);
         setAuthAlwaysRequiredToDisableQuietMode(
                 authAlwaysRequiredToDisableQuietMode);
+        setAllowStoppingUserWithDelayedLocking(allowStoppingUserWithDelayedLocking);
         setDeleteAppWithParent(deleteAppWithParent);
         setAlwaysVisible(alwaysVisible);
+        setCrossProfileContentSharingStrategy(crossProfileContentSharingStrategy);
     }
 }
diff --git a/core/java/android/content/pm/flags.aconfig b/core/java/android/content/pm/flags.aconfig
index 1b90570..b04b7ba 100644
--- a/core/java/android/content/pm/flags.aconfig
+++ b/core/java/android/content/pm/flags.aconfig
@@ -108,3 +108,10 @@
     description: "Feature flag to reduce app crashes caused by split installs with INSTALL_DONT_KILL"
     bug: "291212866"
 }
+
+flag {
+    name: "fix_duplicated_flags"
+    namespace: "package_manager_service"
+    description: "Feature flag to fix duplicated PackageManager flag values"
+    bug: "314815969"
+}
diff --git a/core/java/android/credentials/flags.aconfig b/core/java/android/credentials/flags.aconfig
index bab84aa..f876eeb 100644
--- a/core/java/android/credentials/flags.aconfig
+++ b/core/java/android/credentials/flags.aconfig
@@ -26,4 +26,11 @@
     name: "new_settings_intents"
     description: "Enables settings intents to redirect to new settings page"
     bug: "307587989"
+}
+
+flag {
+    namespace: "credential_manager"
+    name: "new_settings_ui"
+    description: "Enables new settings UI for VIC"
+    bug: "315209085"
 }
\ No newline at end of file
diff --git a/core/java/android/database/ContentObserver.java b/core/java/android/database/ContentObserver.java
index 39c9400..4322bed 100644
--- a/core/java/android/database/ContentObserver.java
+++ b/core/java/android/database/ContentObserver.java
@@ -31,6 +31,7 @@
 
 import java.util.Arrays;
 import java.util.Collection;
+import java.util.concurrent.Executor;
 
 /**
  * Receives call backs for changes to content.
@@ -54,6 +55,7 @@
     private Transport mTransport; // guarded by mLock
 
     Handler mHandler;
+    private final Executor mExecutor;
 
     /**
      * Creates a content observer.
@@ -62,6 +64,18 @@
      */
     public ContentObserver(Handler handler) {
         mHandler = handler;
+        mExecutor = null;
+    }
+
+    /**
+     * @hide
+     * Creates a content observer with an executor.
+     *
+     * @param executor The executor to run {@link #onChange} on, or null if none.
+     * @param unused a second argument to avoid source incompatibility.
+     */
+    public ContentObserver(@Nullable Executor executor, int unused) {
+        mExecutor = executor;
     }
 
     /**
@@ -306,12 +320,19 @@
     /** @hide */
     public final void dispatchChange(boolean selfChange, @NonNull Collection<Uri> uris,
             @NotifyFlags int flags, @UserIdInt int userId) {
-        if (mHandler == null) {
-            onChange(selfChange, uris, flags, userId);
-        } else {
+        if (mExecutor != null) {
+            mExecutor.execute(() -> {
+                onChange(selfChange, uris, flags, userId);
+            });
+        } else if (mHandler != null) {
+            // Supporting Handler directly rather than wrapping in a HandlerExecutor
+            //  avoids introducing a RejectedExecutionException for legacy code when
+            //  the post fails.
             mHandler.post(() -> {
                 onChange(selfChange, uris, flags, userId);
             });
+        } else {
+            onChange(selfChange, uris, flags, userId);
         }
     }
 
diff --git a/core/java/android/database/ExecutorContentObserver.java b/core/java/android/database/ExecutorContentObserver.java
new file mode 100644
index 0000000..3ea807d
--- /dev/null
+++ b/core/java/android/database/ExecutorContentObserver.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.database;
+
+import android.annotation.Nullable;
+
+import java.util.concurrent.Executor;
+
+/**
+ * @hide
+ *
+ * Receives callbacks for changes to content.
+ * Must be implemented by objects which are added to a {@link ContentObservable}.
+ */
+public abstract class ExecutorContentObserver extends ContentObserver {
+    /**
+     * Creates a content observer that uses an executor for change handling.
+     *
+     * @param executor The executor to run {@link #onChange} on, or null if none.
+     */
+    public ExecutorContentObserver(@Nullable Executor executor) {
+        super(executor, 0);
+    }
+}
diff --git a/core/java/android/hardware/camera2/CameraCharacteristics.java b/core/java/android/hardware/camera2/CameraCharacteristics.java
index bb8924c..e3a5520 100644
--- a/core/java/android/hardware/camera2/CameraCharacteristics.java
+++ b/core/java/android/hardware/camera2/CameraCharacteristics.java
@@ -35,6 +35,7 @@
 import com.android.internal.camera.flags.Flags;
 
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.Collections;
 import java.util.List;
 import java.util.Set;
@@ -206,6 +207,7 @@
     private List<CameraCharacteristics.Key<?>> mKeysNeedingPermission;
     private List<CaptureRequest.Key<?>> mAvailableRequestKeys;
     private List<CaptureRequest.Key<?>> mAvailableSessionKeys;
+    private List<CameraCharacteristics.Key<?>> mAvailableSessionCharacteristicsKeys;
     private List<CaptureRequest.Key<?>> mAvailablePhysicalRequestKeys;
     private List<CaptureResult.Key<?>> mAvailableResultKeys;
     private ArrayList<RecommendedStreamConfigurationMap> mRecommendedConfigurations;
@@ -546,6 +548,27 @@
     }
 
     /**
+     * <p>Get the keys in Camera Characteristics whose values are capture session specific.
+     * The session specific characteristics can be acquired by calling
+     * CameraDevice.getSessionCharacteristics(). </p>
+     *
+     * <p>Note that getAvailableSessionKeys returns the CaptureRequest keys that are difficult to
+     * apply per-frame, whereas this function returns CameraCharacteristics keys that are dependent
+     * on a particular SessionConfiguration.</p>
+     *
+     * @return List of CameraCharacteristic keys containing characterisitics specific to a session
+     * configuration. For Android 15, this list only contains CONTROL_ZOOM_RATIO_RANGE.
+     */
+    @NonNull
+    @FlaggedApi(Flags.FLAG_FEATURE_COMBINATION_QUERY)
+    public List<CameraCharacteristics.Key<?>> getAvailableSessionCharacteristicsKeys() {
+        if (mAvailableSessionCharacteristicsKeys == null) {
+            mAvailableSessionCharacteristicsKeys = Arrays.asList(CONTROL_ZOOM_RATIO_RANGE);
+        }
+        return mAvailableSessionCharacteristicsKeys;
+    }
+
+    /**
      * <p>Returns a subset of {@link #getAvailableCaptureRequestKeys} keys that can
      * be overridden for physical devices backing a logical multi-camera.</p>
      *
@@ -1332,6 +1355,27 @@
             new Key<Boolean>("android.control.autoframingAvailable", boolean.class);
 
     /**
+     * <p>The operating luminance range of low light boost measured in lux (lx).</p>
+     * <p><b>Range of valid values:</b><br></p>
+     * <p>The lower bound indicates the lowest scene luminance value the AE mode
+     * 'ON_LOW_LIGHT_BOOST_BRIGHTNESS_PRIORITY' can operate within. Scenes of lower luminance
+     * than this may receive less brightening, increased noise, or artifacts.</p>
+     * <p>The upper bound indicates the luminance threshold at the point when the mode is enabled.
+     * For example, 'Range[0.3, 30.0]' defines 0.3 lux being the lowest scene luminance the
+     * mode can reliably support. 30.0 lux represents the threshold when this mode is
+     * activated. Scenes measured at less than or equal to 30 lux will activate low light
+     * boost.</p>
+     * <p>If this key is defined, then the AE mode 'ON_LOW_LIGHT_BOOST_BRIGHTNESS_PRIORITY' will
+     * also be present.</p>
+     * <p><b>Optional</b> - The value for this key may be {@code null} on some devices.</p>
+     */
+    @PublicKey
+    @NonNull
+    @FlaggedApi(Flags.FLAG_CAMERA_AE_MODE_LOW_LIGHT_BOOST)
+    public static final Key<android.util.Range<Float>> CONTROL_LOW_LIGHT_BOOST_INFO_LUMINANCE_RANGE =
+            new Key<android.util.Range<Float>>("android.control.lowLightBoostInfoLuminanceRange", new TypeReference<android.util.Range<Float>>() {{ }});
+
+    /**
      * <p>List of edge enhancement modes for {@link CaptureRequest#EDGE_MODE android.edge.mode} that are supported by this camera
      * device.</p>
      * <p>Full-capability camera devices must always support OFF; camera devices that support
diff --git a/core/java/android/hardware/camera2/CameraDevice.java b/core/java/android/hardware/camera2/CameraDevice.java
index 58cba41..3835c52 100644
--- a/core/java/android/hardware/camera2/CameraDevice.java
+++ b/core/java/android/hardware/camera2/CameraDevice.java
@@ -16,6 +16,7 @@
 
 package android.hardware.camera2;
 
+import android.annotation.FlaggedApi;
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -28,6 +29,8 @@
 import android.os.Handler;
 import android.view.Surface;
 
+import com.android.internal.camera.flags.Flags;
+
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 import java.util.List;
@@ -1412,7 +1415,35 @@
         throw new UnsupportedOperationException("Subclasses must override this method");
     }
 
-    /**
+  /**
+   * <p>Get camera characteristics for a particular session configuration by the camera device.</p>
+   *
+   * <p>The camera characteristics returned here is typically more limited than the characteristics
+   * returned from {@link CameraManager#getCameraCharacteristics}. The keys that have more limited
+   * values are listed in
+   * {@link CameraCharacteristics#getAvailableSessionCharacteristicsKeys}. </p>
+   *
+   * <p>Other than that, the characteristics returned here can be used in the same way as those
+   * returned from {@link CameraManager#getCameraCharacteristics}.</p>
+   *
+   * @param sessionConfig : The session configuration for which characteristics are fetched.
+   * @return CameraCharacteristics specific to a given session configuration.
+   * @throws UnsupportedOperationException if the query operation is not supported by the camera
+   *                                       device
+   * @throws IllegalArgumentException if the session configuration is invalid
+   * @throws CameraAccessException if the camera device is no longer connected or has
+   *                               encountered a fatal error
+   * @throws IllegalStateException if the camera device has been closed
+   * @see android.hardware.camera2.CameraCharacteristics#getAvailableSessionCharacteristicsKeys
+   */
+    @NonNull
+    @FlaggedApi(Flags.FLAG_FEATURE_COMBINATION_QUERY)
+    public CameraCharacteristics getSessionCharacteristics(
+            @NonNull SessionConfiguration sessionConfig) throws CameraAccessException {
+        throw new UnsupportedOperationException("Subclasses must override this method");
+    }
+
+  /**
      * A callback objects for receiving updates about the state of a camera device.
      *
      * <p>A callback instance must be provided to the {@link CameraManager#openCamera} method to
diff --git a/core/java/android/hardware/camera2/CameraExtensionCharacteristics.java b/core/java/android/hardware/camera2/CameraExtensionCharacteristics.java
index d4d1ab3..8196bf5 100644
--- a/core/java/android/hardware/camera2/CameraExtensionCharacteristics.java
+++ b/core/java/android/hardware/camera2/CameraExtensionCharacteristics.java
@@ -1058,6 +1058,12 @@
      * <p>The set returned is not modifiable, so any attempts to modify it will throw
      * a {@code UnsupportedOperationException}.</p>
      *
+     * <p>Devices launching on Android {@link android.os.Build.VERSION_CODES#VANILLA_ICE_CREAM}
+     * or newer versions are required to support {@link CaptureRequest#CONTROL_AF_MODE},
+     * {@link CaptureRequest#CONTROL_AF_REGIONS}, {@link CaptureRequest#CONTROL_AF_TRIGGER},
+     * {@link CaptureRequest#CONTROL_ZOOM_RATIO} for
+     * {@link CameraExtensionCharacteristics#EXTENSION_NIGHT}.</p>
+     *
      * @param extension the extension type
      *
      * @return non-modifiable set of capture keys supported by camera extension session initialized
@@ -1139,6 +1145,12 @@
      * and the {@link CameraExtensionSession.ExtensionCaptureCallback#onCaptureResultAvailable}
      * callback will not be fired.</p>
      *
+     * <p>Devices launching on Android {@link android.os.Build.VERSION_CODES#VANILLA_ICE_CREAM}
+     * or newer versions are required to support {@link CaptureResult#CONTROL_AF_MODE},
+     * {@link CaptureResult#CONTROL_AF_REGIONS}, {@link CaptureResult#CONTROL_AF_TRIGGER},
+     * {@link CaptureResult#CONTROL_AF_STATE}, {@link CaptureResult#CONTROL_ZOOM_RATIO} for
+     * {@link CameraExtensionCharacteristics#EXTENSION_NIGHT}.</p>
+     *
      * @param extension the extension type
      *
      * @return non-modifiable set of capture result keys supported by camera extension session
diff --git a/core/java/android/hardware/camera2/CameraManager.java b/core/java/android/hardware/camera2/CameraManager.java
index 002c0b2..bcce4b6 100644
--- a/core/java/android/hardware/camera2/CameraManager.java
+++ b/core/java/android/hardware/camera2/CameraManager.java
@@ -1844,7 +1844,7 @@
      * Remaps Camera Ids in the CameraService.
      *
      * @hide
-    */
+     */
     @RequiresPermission(android.Manifest.permission.CAMERA_INJECT_EXTERNAL_CAMERA)
     public void remapCameraIds(@NonNull CameraIdRemapping cameraIdRemapping)
             throws CameraAccessException, SecurityException, IllegalArgumentException {
@@ -1852,6 +1852,29 @@
     }
 
     /**
+     * Injects session params into existing clients in the CameraService.
+     *
+     * @param cameraId       The camera id of client to inject session params into.
+     *                       If no such client exists for cameraId, no injection will
+     *                       take place.
+     * @param sessionParams  A {@link CaptureRequest} object containing the
+     *                       the sessionParams to inject into the existing client.
+     *
+     * @throws CameraAccessException    {@link CameraAccessException#CAMERA_DISCONNECTED} will be
+     *                                  thrown if camera service is not available. Further, if
+     *                                  if no such client exists for cameraId,
+     *                                  {@link CameraAccessException#CAMERA_ERROR} will be thrown.
+     * @throws SecurityException        If the caller does not have permission to inject session
+     *                                  params
+     * @hide
+     */
+    @RequiresPermission(android.Manifest.permission.CAMERA_INJECT_EXTERNAL_CAMERA)
+    public void injectSessionParams(@NonNull String cameraId, @NonNull CaptureRequest sessionParams)
+            throws CameraAccessException, SecurityException {
+        CameraManagerGlobal.get().injectSessionParams(cameraId, sessionParams);
+    }
+
+    /**
      * Reports {@link CameraExtensionSessionStats} to the {@link ICameraService} to be logged for
      * currently active session. Validation is done downstream.
      *
@@ -2110,6 +2133,30 @@
             }
         }
 
+        /** Injects session params into an existing client for cameraid. */
+        public void injectSessionParams(@NonNull String cameraId,
+                @NonNull CaptureRequest sessionParams)
+                throws CameraAccessException, SecurityException {
+            synchronized (mLock) {
+                ICameraService cameraService = getCameraService();
+                if (cameraService == null) {
+                    throw new CameraAccessException(
+                            CameraAccessException.CAMERA_DISCONNECTED,
+                            "Camera service is currently unavailable.");
+                }
+
+                try {
+                    cameraService.injectSessionParams(cameraId, sessionParams.getNativeMetadata());
+                } catch (ServiceSpecificException e) {
+                    throwAsPublicException(e);
+                } catch (RemoteException e) {
+                    throw new CameraAccessException(
+                            CameraAccessException.CAMERA_DISCONNECTED,
+                            "Camera service is currently unavailable.");
+                }
+            }
+        }
+
         private String[] extractCameraIdListLocked() {
             String[] cameraIds = null;
             int idCount = 0;
diff --git a/core/java/android/hardware/camera2/CameraMetadata.java b/core/java/android/hardware/camera2/CameraMetadata.java
index 003718e..765a8f7 100644
--- a/core/java/android/hardware/camera2/CameraMetadata.java
+++ b/core/java/android/hardware/camera2/CameraMetadata.java
@@ -2333,6 +2333,46 @@
      */
     public static final int CONTROL_AE_MODE_ON_EXTERNAL_FLASH = 5;
 
+    /**
+     * <p>Like 'ON' but applies additional brightness boost in low light scenes.</p>
+     * <p>When the scene lighting conditions are within the range defined by
+     * {@link CameraCharacteristics#CONTROL_LOW_LIGHT_BOOST_INFO_LUMINANCE_RANGE android.control.lowLightBoostInfoLuminanceRange} this mode will apply additional
+     * brightness boost.</p>
+     * <p>This mode will automatically adjust the intensity of low light boost applied
+     * according to the scene lighting conditions. A darker scene will receive more boost
+     * while a brighter scene will receive less boost.</p>
+     * <p>This mode can ignore the set target frame rate to allow more light to be captured
+     * which can result in choppier motion. The frame rate can extend to lower than the
+     * {@link CameraCharacteristics#CONTROL_AE_AVAILABLE_TARGET_FPS_RANGES android.control.aeAvailableTargetFpsRanges} but will not go below 10 FPS. This mode
+     * can also increase the sensor sensitivity gain which can result in increased luma
+     * and chroma noise. The sensor sensitivity gain can extend to higher values beyond
+     * {@link CameraCharacteristics#SENSOR_INFO_SENSITIVITY_RANGE android.sensor.info.sensitivityRange}. This mode may also apply additional
+     * processing to recover details in dark and bright areas of the image,and noise
+     * reduction at high sensitivity gain settings to manage the trade-off between light
+     * sensitivity and capture noise.</p>
+     * <p>This mode is restricted to two output surfaces. One output surface type can either
+     * be SurfaceView or TextureView. Another output surface type can either be MediaCodec
+     * or MediaRecorder. This mode cannot be used with a target FPS range higher than 30
+     * FPS.</p>
+     * <p>If the session configuration is not supported, the AE mode reported in the
+     * CaptureResult will be 'ON' instead of 'ON_LOW_LIGHT_BOOST_BRIGHTNESS_PRIORITY'.</p>
+     * <p>The application can observe the CapturerResult field
+     * {@link CaptureResult#CONTROL_LOW_LIGHT_BOOST_STATE android.control.lowLightBoostState} to determine when low light boost is 'ACTIVE' or
+     * 'INACTIVE'.</p>
+     * <p>The low light boost is 'ACTIVE' once the scene lighting condition is less than the
+     * upper bound lux value defined by {@link CameraCharacteristics#CONTROL_LOW_LIGHT_BOOST_INFO_LUMINANCE_RANGE android.control.lowLightBoostInfoLuminanceRange}.
+     * This mode will be 'INACTIVE' once the scene lighting condition is greater than the
+     * upper bound lux value defined by {@link CameraCharacteristics#CONTROL_LOW_LIGHT_BOOST_INFO_LUMINANCE_RANGE android.control.lowLightBoostInfoLuminanceRange}.</p>
+     *
+     * @see CameraCharacteristics#CONTROL_AE_AVAILABLE_TARGET_FPS_RANGES
+     * @see CameraCharacteristics#CONTROL_LOW_LIGHT_BOOST_INFO_LUMINANCE_RANGE
+     * @see CaptureResult#CONTROL_LOW_LIGHT_BOOST_STATE
+     * @see CameraCharacteristics#SENSOR_INFO_SENSITIVITY_RANGE
+     * @see CaptureRequest#CONTROL_AE_MODE
+     */
+    @FlaggedApi(Flags.FLAG_CAMERA_AE_MODE_LOW_LIGHT_BOOST)
+    public static final int CONTROL_AE_MODE_ON_LOW_LIGHT_BOOST_BRIGHTNESS_PRIORITY = 6;
+
     //
     // Enumeration values for CaptureRequest#CONTROL_AE_PRECAPTURE_TRIGGER
     //
@@ -4074,6 +4114,24 @@
     public static final int CONTROL_AUTOFRAMING_STATE_CONVERGED = 2;
 
     //
+    // Enumeration values for CaptureResult#CONTROL_LOW_LIGHT_BOOST_STATE
+    //
+
+    /**
+     * <p>The AE mode 'ON_LOW_LIGHT_BOOST_BRIGHTNESS_PRIORITY' is enabled but not applied.</p>
+     * @see CaptureResult#CONTROL_LOW_LIGHT_BOOST_STATE
+     */
+    @FlaggedApi(Flags.FLAG_CAMERA_AE_MODE_LOW_LIGHT_BOOST)
+    public static final int CONTROL_LOW_LIGHT_BOOST_STATE_INACTIVE = 0;
+
+    /**
+     * <p>The AE mode 'ON_LOW_LIGHT_BOOST_BRIGHTNESS_PRIORITY' is enabled and applied.</p>
+     * @see CaptureResult#CONTROL_LOW_LIGHT_BOOST_STATE
+     */
+    @FlaggedApi(Flags.FLAG_CAMERA_AE_MODE_LOW_LIGHT_BOOST)
+    public static final int CONTROL_LOW_LIGHT_BOOST_STATE_ACTIVE = 1;
+
+    //
     // Enumeration values for CaptureResult#FLASH_STATE
     //
 
diff --git a/core/java/android/hardware/camera2/CaptureResult.java b/core/java/android/hardware/camera2/CaptureResult.java
index 35f295a..ab4406c3 100644
--- a/core/java/android/hardware/camera2/CaptureResult.java
+++ b/core/java/android/hardware/camera2/CaptureResult.java
@@ -2814,6 +2814,31 @@
             new Key<Integer>("android.control.autoframingState", int.class);
 
     /**
+     * <p>Current state of the low light boost AE mode.</p>
+     * <p>When low light boost is enabled by setting the AE mode to
+     * 'ON_LOW_LIGHT_BOOST_BRIGHTNESS_PRIORITY', it can dynamically apply a low light
+     * boost when the light level threshold is exceeded.</p>
+     * <p>This state indicates when low light boost is 'ACTIVE' and applied. Similarly, it can
+     * indicate when it is not being applied by returning 'INACTIVE'.</p>
+     * <p>This key will be absent from the CaptureResult if AE mode is not set to
+     * 'ON_LOW_LIGHT_BOOST_BRIGHTNESS_PRIORITY.</p>
+     * <p><b>Possible values:</b></p>
+     * <ul>
+     *   <li>{@link #CONTROL_LOW_LIGHT_BOOST_STATE_INACTIVE INACTIVE}</li>
+     *   <li>{@link #CONTROL_LOW_LIGHT_BOOST_STATE_ACTIVE ACTIVE}</li>
+     * </ul>
+     *
+     * <p><b>Optional</b> - The value for this key may be {@code null} on some devices.</p>
+     * @see #CONTROL_LOW_LIGHT_BOOST_STATE_INACTIVE
+     * @see #CONTROL_LOW_LIGHT_BOOST_STATE_ACTIVE
+     */
+    @PublicKey
+    @NonNull
+    @FlaggedApi(Flags.FLAG_CAMERA_AE_MODE_LOW_LIGHT_BOOST)
+    public static final Key<Integer> CONTROL_LOW_LIGHT_BOOST_STATE =
+            new Key<Integer>("android.control.lowLightBoostState", int.class);
+
+    /**
      * <p>Operation mode for edge
      * enhancement.</p>
      * <p>Edge enhancement improves sharpness and details in the captured image. OFF means
diff --git a/core/java/android/hardware/camera2/extension/RequestProcessor.java b/core/java/android/hardware/camera2/extension/RequestProcessor.java
index 7c099d6..bf5ea12 100644
--- a/core/java/android/hardware/camera2/extension/RequestProcessor.java
+++ b/core/java/android/hardware/camera2/extension/RequestProcessor.java
@@ -250,7 +250,7 @@
          */
         @FlaggedApi(Flags.FLAG_CONCERT_MODE)
         @NonNull
-        List<Pair<CaptureRequest.Key, Object>> getParameters() {
+        public List<Pair<CaptureRequest.Key, Object>> getParameters() {
             return mParameters;
         }
 
diff --git a/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java b/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java
index 3851e36..ccb24e7 100644
--- a/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java
+++ b/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java
@@ -786,6 +786,18 @@
         }
     }
 
+    @Override
+    public CameraCharacteristics getSessionCharacteristics(
+            @NonNull SessionConfiguration sessionConfig) throws CameraAccessException,
+            UnsupportedOperationException, IllegalArgumentException {
+        synchronized (mInterfaceLock) {
+            checkIfCameraClosedOrInError();
+            CameraMetadataNative info = mRemoteDevice.getSessionCharacteristics(sessionConfig);
+
+            return new CameraCharacteristics(info);
+        }
+    }
+
     /**
      * For use by backwards-compatibility code only.
      */
diff --git a/core/java/android/hardware/camera2/impl/ICameraDeviceUserWrapper.java b/core/java/android/hardware/camera2/impl/ICameraDeviceUserWrapper.java
index b6b1968..2129260 100644
--- a/core/java/android/hardware/camera2/impl/ICameraDeviceUserWrapper.java
+++ b/core/java/android/hardware/camera2/impl/ICameraDeviceUserWrapper.java
@@ -16,19 +16,12 @@
 
 package android.hardware.camera2.impl;
 
-import static android.hardware.camera2.CameraAccessException.CAMERA_DISABLED;
-import static android.hardware.camera2.CameraAccessException.CAMERA_DISCONNECTED;
-import static android.hardware.camera2.CameraAccessException.CAMERA_IN_USE;
-import static android.hardware.camera2.CameraAccessException.CAMERA_ERROR;
-import static android.hardware.camera2.CameraAccessException.MAX_CAMERAS_IN_USE;
-import static android.hardware.camera2.CameraAccessException.CAMERA_DEPRECATED_HAL;
-
 import android.hardware.ICameraService;
-import android.hardware.camera2.CameraManager;
 import android.hardware.camera2.CameraAccessException;
+import android.hardware.camera2.CameraManager;
 import android.hardware.camera2.CaptureRequest;
-import android.hardware.camera2.ICameraDeviceUser;
 import android.hardware.camera2.ICameraDeviceCallbacks;
+import android.hardware.camera2.ICameraDeviceUser;
 import android.hardware.camera2.ICameraOfflineSession;
 import android.hardware.camera2.impl.CameraMetadataNative;
 import android.hardware.camera2.params.OutputConfiguration;
@@ -205,6 +198,28 @@
         }
     }
 
+    /**
+     * Fetches the CameraCharacteristics for a given session configuration.
+     */
+    public CameraMetadataNative getSessionCharacteristics(SessionConfiguration sessionConfig)
+            throws CameraAccessException {
+        try {
+            return mRemoteDevice.getSessionCharacteristics(sessionConfig);
+        } catch (ServiceSpecificException e) {
+            if (e.errorCode == ICameraService.ERROR_INVALID_OPERATION) {
+                throw new UnsupportedOperationException("Session characteristics query not "
+                    + "supported");
+            } else if (e.errorCode == ICameraService.ERROR_ILLEGAL_ARGUMENT) {
+                throw new IllegalArgumentException("Invalid session configuration");
+            }
+
+            throw e;
+        } catch (Throwable t) {
+            CameraManager.throwAsPublicException(t);
+            throw new UnsupportedOperationException("Unexpected exception", t);
+        }
+    }
+
     public long flush() throws CameraAccessException {
         try {
             return mRemoteDevice.flush();
diff --git a/core/java/android/os/BatteryConsumer.java b/core/java/android/os/BatteryConsumer.java
index 6a83cee..05a1abea 100644
--- a/core/java/android/os/BatteryConsumer.java
+++ b/core/java/android/os/BatteryConsumer.java
@@ -33,6 +33,7 @@
  *
  * @hide
  */
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
 public abstract class BatteryConsumer {
 
     private static final String TAG = "BatteryConsumer";
diff --git a/core/java/android/os/CoolingDevice.java b/core/java/android/os/CoolingDevice.java
index 4ddcd9d..06ec720 100644
--- a/core/java/android/os/CoolingDevice.java
+++ b/core/java/android/os/CoolingDevice.java
@@ -55,7 +55,11 @@
             TYPE_TPU,
             TYPE_POWER_AMPLIFIER,
             TYPE_DISPLAY,
-            TYPE_SPEAKER
+            TYPE_SPEAKER,
+            TYPE_WIFI,
+            TYPE_CAMERA,
+            TYPE_FLASHLIGHT,
+            TYPE_USB_PORT
     })
     @Retention(RetentionPolicy.SOURCE)
     public @interface Type {}
@@ -84,6 +88,14 @@
     public static final int TYPE_DISPLAY = CoolingType.DISPLAY;
     /** Speaker cooling device */
     public static final int TYPE_SPEAKER = CoolingType.SPEAKER;
+    /** WiFi cooling device */
+    public static final int TYPE_WIFI = CoolingType.WIFI;
+    /** Camera cooling device */
+    public static final int TYPE_CAMERA = CoolingType.CAMERA;
+    /** Flashlight cooling device */
+    public static final int TYPE_FLASHLIGHT = CoolingType.FLASHLIGHT;
+    /** USB PORT cooling device */
+    public static final int TYPE_USB_PORT = CoolingType.USB_PORT;
 
     /**
      * Verify a valid cooling device type.
@@ -91,7 +103,7 @@
      * @return true if a cooling device type is valid otherwise false.
      */
     public static boolean isValidType(@Type int type) {
-        return type >= TYPE_FAN && type <= TYPE_SPEAKER;
+        return type >= TYPE_FAN && type <= TYPE_USB_PORT;
     }
 
     public CoolingDevice(long value, @Type int type, @NonNull String name) {
diff --git a/core/java/android/os/Debug.java b/core/java/android/os/Debug.java
index c527cb5..04d6f61 100644
--- a/core/java/android/os/Debug.java
+++ b/core/java/android/os/Debug.java
@@ -2702,4 +2702,13 @@
      * @hide
      */
     public static native boolean isVmapStack();
+
+    /**
+     * Log internal statistics about the allocator.
+     * @return true if the statistics were logged properly, false if not.
+     *
+     * @hide
+     */
+    public static native boolean logAllocatorStats();
+
 }
diff --git a/core/java/android/os/IVibratorManagerService.aidl b/core/java/android/os/IVibratorManagerService.aidl
index 0f27569..65e498e 100644
--- a/core/java/android/os/IVibratorManagerService.aidl
+++ b/core/java/android/os/IVibratorManagerService.aidl
@@ -41,5 +41,5 @@
     // There is no order guarantee with respect to the two-way APIs above like
     // vibrate/isVibrating/cancel.
     oneway void performHapticFeedback(int uid, int deviceId, String opPkg, int constant,
-            boolean always, String reason, IBinder token);
+            boolean always, String reason);
 }
diff --git a/core/java/android/os/SystemVibratorManager.java b/core/java/android/os/SystemVibratorManager.java
index bc85412e..8e83923 100644
--- a/core/java/android/os/SystemVibratorManager.java
+++ b/core/java/android/os/SystemVibratorManager.java
@@ -39,6 +39,7 @@
 
     private final IVibratorManagerService mService;
     private final Context mContext;
+    private final int mUid;
     private final Binder mToken = new Binder();
     private final Object mLock = new Object();
     @GuardedBy("mLock")
@@ -56,6 +57,7 @@
     public SystemVibratorManager(Context context) {
         super(context);
         mContext = context;
+        mUid = Process.myUid();
         mService = IVibratorManagerService.Stub.asInterface(
                 ServiceManager.getService(Context.VIBRATOR_MANAGER_SERVICE));
     }
@@ -152,8 +154,7 @@
         }
         try {
             mService.performHapticFeedback(
-                    Process.myUid(), mContext.getDeviceId(), mPackageName, constant, always, reason,
-                    mToken);
+                    mUid, mContext.getDeviceId(), mPackageName, constant, always, reason);
         } catch (RemoteException e) {
             Log.w(TAG, "Failed to perform haptic feedback.", e);
         }
diff --git a/core/java/android/os/Temperature.java b/core/java/android/os/Temperature.java
index a138431..2e12278 100644
--- a/core/java/android/os/Temperature.java
+++ b/core/java/android/os/Temperature.java
@@ -79,7 +79,13 @@
             TYPE_TPU,
             TYPE_DISPLAY,
             TYPE_MODEM,
-            TYPE_SOC
+            TYPE_SOC,
+            TYPE_WIFI,
+            TYPE_CAMERA,
+            TYPE_FLASHLIGHT,
+            TYPE_SPEAKER,
+            TYPE_AMBIENT,
+            TYPE_POGO
     })
     @Retention(RetentionPolicy.SOURCE)
     public @interface Type {}
@@ -101,6 +107,12 @@
     public static final int TYPE_DISPLAY = TemperatureType.DISPLAY;
     public static final int TYPE_MODEM = TemperatureType.MODEM;
     public static final int TYPE_SOC = TemperatureType.SOC;
+    public static final int TYPE_WIFI = TemperatureType.WIFI;
+    public static final int TYPE_CAMERA = TemperatureType.CAMERA;
+    public static final int TYPE_FLASHLIGHT = TemperatureType.FLASHLIGHT;
+    public static final int TYPE_SPEAKER = TemperatureType.SPEAKER;
+    public static final int TYPE_AMBIENT = TemperatureType.AMBIENT;
+    public static final int TYPE_POGO = TemperatureType.POGO;
 
     /**
      * Verify a valid Temperature type.
@@ -108,7 +120,7 @@
      * @return true if a Temperature type is valid otherwise false.
      */
     public static boolean isValidType(@Type int type) {
-        return type >= TYPE_UNKNOWN && type <= TYPE_SOC;
+        return type >= TYPE_UNKNOWN && type <= TYPE_POGO;
     }
 
     /**
diff --git a/core/java/android/os/flags.aconfig b/core/java/android/os/flags.aconfig
index 980c13c..145981c 100644
--- a/core/java/android/os/flags.aconfig
+++ b/core/java/android/os/flags.aconfig
@@ -76,3 +76,11 @@
     description: "Guards the ADPF GPU APIs."
     bug: "284324521"
 }
+
+flag {
+    name: "battery_service_support_current_adb_command"
+    namespace: "backstage_power"
+    description: "Whether or not BatteryService supports adb commands for Current values."
+    is_fixed_read_only: true
+    bug: "315037695"
+}
diff --git a/core/java/android/os/vibrator/flags.aconfig b/core/java/android/os/vibrator/flags.aconfig
index 437668c..69d86a6 100644
--- a/core/java/android/os/vibrator/flags.aconfig
+++ b/core/java/android/os/vibrator/flags.aconfig
@@ -44,10 +44,3 @@
     description: "Enables the independent keyboard vibration settings feature"
     bug: "289107579"
 }
-
-flag {
-    namespace: "haptics"
-    name: "adaptive_haptics_enabled"
-    description: "Enables the adaptive haptics feature"
-    bug: "305961689"
-}
diff --git a/core/java/android/permission/PermissionControllerService.java b/core/java/android/permission/PermissionControllerService.java
index 9fe2599..5a12760 100644
--- a/core/java/android/permission/PermissionControllerService.java
+++ b/core/java/android/permission/PermissionControllerService.java
@@ -318,7 +318,7 @@
      *                 a virtual device. See {@link Context#DEVICE_ID_DEFAULT}
      */
     @BinderThread
-    @FlaggedApi(Flags.FLAG_DEVICE_AWARE_PERMISSION_APIS)
+    @FlaggedApi(Flags.FLAG_DEVICE_AWARE_PERMISSION_APIS_ENABLED)
     public void onOneTimePermissionSessionTimeout(@NonNull String packageName,
             int deviceId) {
         onOneTimePermissionSessionTimeout(packageName);
@@ -393,7 +393,7 @@
      * @see android.content.Context#revokeSelfPermissionsOnKill(java.util.Collection)
      */
     @BinderThread
-    @FlaggedApi(Flags.FLAG_DEVICE_AWARE_PERMISSION_APIS)
+    @FlaggedApi(Flags.FLAG_DEVICE_AWARE_PERMISSION_APIS_ENABLED)
     public void onRevokeSelfPermissionsOnKill(@NonNull String packageName,
             @NonNull List<String> permissions, int deviceId, @NonNull Runnable callback) {
         onRevokeSelfPermissionsOnKill(packageName, permissions, callback);
diff --git a/core/java/android/permission/flags.aconfig b/core/java/android/permission/flags.aconfig
index cbeb821..d09c229 100644
--- a/core/java/android/permission/flags.aconfig
+++ b/core/java/android/permission/flags.aconfig
@@ -1,7 +1,7 @@
 package: "android.permission.flags"
 
 flag {
-  name: "device_aware_permission_apis"
+  name: "device_aware_permission_apis_enabled"
   is_fixed_read_only: true
   namespace: "permissions"
   description: "enable device aware permission APIs"
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 8b5995a..9c27f19 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -608,6 +608,23 @@
 
     /**
      * Activity Action: Show settings to allow configuration of
+     * {@link Manifest.permission#MEDIA_ROUTING_CONTROL} permission.
+     *
+     * Input: Optionally, the Intent's data URI can specify the application package name to
+     * directly invoke the management GUI specific to the package name. For example
+     * "package:com.my.app". However, modifying this permission setting for any package is allowed
+     * only when that package holds an appropriate companion device profile such as
+     * {@link android.companion.AssociationRequest#DEVICE_PROFILE_WATCH}.
+     * <p>
+     * Output: Nothing.
+     */
+    @FlaggedApi("com.android.media.flags.enable_privileged_routing_for_media_routing_control")
+    @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+    public static final String ACTION_REQUEST_MEDIA_ROUTING_CONTROL =
+            "android.settings.REQUEST_MEDIA_ROUTING_CONTROL";
+
+    /**
+     * Activity Action: Show settings to allow configuration of
      * {@link Manifest.permission#RUN_USER_INITIATED_JOBS} permission
      *
      * Input: Optionally, the Intent's data URI can specify the application package name to
diff --git a/core/java/android/security/NetworkSecurityPolicy.java b/core/java/android/security/NetworkSecurityPolicy.java
index e679d20..0c4eeda 100644
--- a/core/java/android/security/NetworkSecurityPolicy.java
+++ b/core/java/android/security/NetworkSecurityPolicy.java
@@ -16,8 +16,6 @@
 
 package android.security;
 
-import android.annotation.FlaggedApi;
-import android.annotation.NonNull;
 import android.content.Context;
 import android.content.pm.PackageManager;
 import android.security.net.config.ApplicationConfig;
@@ -28,6 +26,9 @@
  *
  * <p>Network stacks/components should honor this policy to make it possible to centrally control
  * the relevant aspects of network security behavior.
+ *
+ * <p>The policy currently consists of a single flag: whether cleartext network traffic is
+ * permitted. See {@link #isCleartextTrafficPermitted()}.
  */
 public class NetworkSecurityPolicy {
 
@@ -93,22 +94,6 @@
     }
 
     /**
-     * Returns {@code true} if Certificate Transparency information is required to be verified by
-     * the client in TLS connections to {@code hostname}.
-     *
-     * <p>See RFC6962 section 3.3 for more details.
-     *
-     * @param hostname hostname to check whether certificate transparency verification is required
-     * @return {@code true} if certificate transparency verification is required and {@code false}
-     *     otherwise
-     */
-    @FlaggedApi(Flags.FLAG_CERTIFICATE_TRANSPARENCY_CONFIGURATION)
-    public boolean isCertificateTransparencyVerificationRequired(@NonNull String hostname) {
-        return libcore.net.NetworkSecurityPolicy.getInstance()
-                .isCertificateTransparencyVerificationRequired(hostname);
-    }
-
-    /**
      * Handle an update to the system or user certificate stores.
      * @hide
      */
diff --git a/core/java/android/security/flags.aconfig b/core/java/android/security/flags.aconfig
index 1a33b25..28ef70b 100644
--- a/core/java/android/security/flags.aconfig
+++ b/core/java/android/security/flags.aconfig
@@ -1,13 +1,6 @@
 package: "android.security"
 
 flag {
-    name: "certificate_transparency_configuration"
-    namespace: "network_security"
-    description: "Enable certificate transparency setting in the network security config"
-    bug: "28746284"
-}
-
-flag {
     name: "fsverity_api"
     namespace: "hardware_backed_security"
     description: "Feature flag for fs-verity API"
@@ -15,6 +8,13 @@
 }
 
 flag {
+    name: "mgf1_digest_setter"
+    namespace: "hardware_backed_security"
+    description: "Feature flag for mgf1 digest setter in key generation and import parameters."
+    bug: "308378912"
+}
+
+flag {
     name: "fix_unlocked_device_required_keys_v2"
     namespace: "hardware_backed_security"
     description: "Fix bugs in behavior of UnlockedDeviceRequired keystore keys"
diff --git a/core/java/android/security/net/config/ApplicationConfig.java b/core/java/android/security/net/config/ApplicationConfig.java
index 4cc870b..801eceb 100644
--- a/core/java/android/security/net/config/ApplicationConfig.java
+++ b/core/java/android/security/net/config/ApplicationConfig.java
@@ -16,15 +16,10 @@
 
 package android.security.net.config;
 
-import static android.security.Flags.certificateTransparencyConfiguration;
-
-import android.annotation.NonNull;
 import android.util.Pair;
-
 import java.util.HashSet;
 import java.util.Locale;
 import java.util.Set;
-
 import javax.net.ssl.X509TrustManager;
 
 /**
@@ -152,22 +147,6 @@
         return getConfigForHostname(hostname).isCleartextTrafficPermitted();
     }
 
-    /**
-     * Returns {@code true} if Certificate Transparency information is required to be verified by
-     * the client in TLS connections to {@code hostname}.
-     *
-     * <p>See RFC6962 section 3.3 for more details.
-     *
-     * @param hostname hostname to check whether certificate transparency verification is required
-     * @return {@code true} if certificate transparency verification is required and {@code false}
-     *     otherwise
-     */
-    public boolean isCertificateTransparencyVerificationRequired(@NonNull String hostname) {
-        return certificateTransparencyConfiguration()
-                ? getConfigForHostname(hostname).isCertificateTransparencyVerificationRequired()
-                : NetworkSecurityConfig.DEFAULT_CERTIFICATE_TRANSPARENCY_VERIFICATION_REQUIRED;
-    }
-
     public void handleTrustStorageUpdate() {
         synchronized(mLock) {
             // If the config is uninitialized then there is no work to be done to handle an update,
diff --git a/core/java/android/security/net/config/ConfigNetworkSecurityPolicy.java b/core/java/android/security/net/config/ConfigNetworkSecurityPolicy.java
index 801b32b..a708f5b 100644
--- a/core/java/android/security/net/config/ConfigNetworkSecurityPolicy.java
+++ b/core/java/android/security/net/config/ConfigNetworkSecurityPolicy.java
@@ -40,6 +40,6 @@
 
     @Override
     public boolean isCertificateTransparencyVerificationRequired(String hostname) {
-        return mConfig.isCertificateTransparencyVerificationRequired(hostname);
+        return false;
     }
 }
diff --git a/core/java/android/security/net/config/NetworkSecurityConfig.java b/core/java/android/security/net/config/NetworkSecurityConfig.java
index 129ae63..00872fb 100644
--- a/core/java/android/security/net/config/NetworkSecurityConfig.java
+++ b/core/java/android/security/net/config/NetworkSecurityConfig.java
@@ -38,12 +38,9 @@
     public static final boolean DEFAULT_CLEARTEXT_TRAFFIC_PERMITTED = true;
     /** @hide */
     public static final boolean DEFAULT_HSTS_ENFORCED = false;
-    /** @hide */
-    public static final boolean DEFAULT_CERTIFICATE_TRANSPARENCY_VERIFICATION_REQUIRED = false;
 
     private final boolean mCleartextTrafficPermitted;
     private final boolean mHstsEnforced;
-    private final boolean mCertificateTransparencyVerificationRequired;
     private final PinSet mPins;
     private final List<CertificatesEntryRef> mCertificatesEntryRefs;
     private Set<TrustAnchor> mAnchors;
@@ -51,15 +48,10 @@
     private NetworkSecurityTrustManager mTrustManager;
     private final Object mTrustManagerLock = new Object();
 
-    private NetworkSecurityConfig(
-            boolean cleartextTrafficPermitted,
-            boolean hstsEnforced,
-            boolean certificateTransparencyVerificationRequired,
-            PinSet pins,
-            List<CertificatesEntryRef> certificatesEntryRefs) {
+    private NetworkSecurityConfig(boolean cleartextTrafficPermitted, boolean hstsEnforced,
+            PinSet pins, List<CertificatesEntryRef> certificatesEntryRefs) {
         mCleartextTrafficPermitted = cleartextTrafficPermitted;
         mHstsEnforced = hstsEnforced;
-        mCertificateTransparencyVerificationRequired = certificateTransparencyVerificationRequired;
         mPins = pins;
         mCertificatesEntryRefs = certificatesEntryRefs;
         // Sort the certificates entry refs so that all entries that override pins come before
@@ -112,11 +104,6 @@
         return mHstsEnforced;
     }
 
-    // TODO(b/28746284): add exceptions for user-added certificates and enterprise overrides.
-    public boolean isCertificateTransparencyVerificationRequired() {
-        return mCertificateTransparencyVerificationRequired;
-    }
-
     public PinSet getPins() {
         return mPins;
     }
@@ -221,9 +208,6 @@
         private boolean mHstsEnforced = DEFAULT_HSTS_ENFORCED;
         private boolean mCleartextTrafficPermittedSet = false;
         private boolean mHstsEnforcedSet = false;
-        private boolean mCertificateTransparencyVerificationRequired =
-                DEFAULT_CERTIFICATE_TRANSPARENCY_VERIFICATION_REQUIRED;
-        private boolean mCertificateTransparencyVerificationRequiredSet = false;
         private Builder mParentBuilder;
 
         /**
@@ -329,35 +313,12 @@
             return mCertificatesEntryRefs;
         }
 
-        Builder setCertificateTransparencyVerificationRequired(boolean required) {
-            mCertificateTransparencyVerificationRequired = required;
-            mCertificateTransparencyVerificationRequiredSet = true;
-            return this;
-        }
-
-        private boolean getCertificateTransparencyVerificationRequired() {
-            if (mCertificateTransparencyVerificationRequiredSet) {
-                return mCertificateTransparencyVerificationRequired;
-            }
-            if (mParentBuilder != null) {
-                return mParentBuilder.getCertificateTransparencyVerificationRequired();
-            }
-            return DEFAULT_CERTIFICATE_TRANSPARENCY_VERIFICATION_REQUIRED;
-        }
-
         public NetworkSecurityConfig build() {
             boolean cleartextPermitted = getEffectiveCleartextTrafficPermitted();
             boolean hstsEnforced = getEffectiveHstsEnforced();
-            boolean certificateTransparencyVerificationRequired =
-                    getCertificateTransparencyVerificationRequired();
             PinSet pinSet = getEffectivePinSet();
             List<CertificatesEntryRef> entryRefs = getEffectiveCertificatesEntryRefs();
-            return new NetworkSecurityConfig(
-                    cleartextPermitted,
-                    hstsEnforced,
-                    certificateTransparencyVerificationRequired,
-                    pinSet,
-                    entryRefs);
+            return new NetworkSecurityConfig(cleartextPermitted, hstsEnforced, pinSet, entryRefs);
         }
     }
 }
diff --git a/core/java/android/security/net/config/XmlConfigSource.java b/core/java/android/security/net/config/XmlConfigSource.java
index b1c1479..311a8d2 100644
--- a/core/java/android/security/net/config/XmlConfigSource.java
+++ b/core/java/android/security/net/config/XmlConfigSource.java
@@ -171,11 +171,6 @@
         return new Domain(domain, includeSubdomains);
     }
 
-    private boolean parseCertificateTransparency(XmlResourceParser parser)
-            throws IOException, XmlPullParserException, ParserException {
-        return parser.getAttributeBooleanValue(null, "enabled", false);
-    }
-
     private CertificatesEntryRef parseCertificatesEntry(XmlResourceParser parser,
             boolean defaultOverridePins)
             throws IOException, XmlPullParserException, ParserException {
@@ -231,6 +226,7 @@
         boolean seenPinSet = false;
         boolean seenTrustAnchors = false;
         boolean defaultOverridePins = configType == CONFIG_DEBUG;
+        String configName = parser.getName();
         int outerDepth = parser.getDepth();
         // Add this builder now so that this builder occurs before any of its children. This
         // makes the final build pass easier.
@@ -283,15 +279,6 @@
                             "Nested domain-config not allowed in " + getConfigString(configType));
                 }
                 builders.addAll(parseConfigEntry(parser, seenDomains, builder, configType));
-            } else if ("certificateTransparency".equals(tagName)) {
-                if (configType != CONFIG_BASE && configType != CONFIG_DOMAIN) {
-                    throw new ParserException(
-                            parser,
-                            "certificateTransparency not allowed in "
-                                    + getConfigString(configType));
-                }
-                builder.setCertificateTransparencyVerificationRequired(
-                        parseCertificateTransparency(parser));
             } else {
                 XmlUtils.skipCurrentTag(parser);
             }
diff --git a/core/java/android/service/dreams/flags.aconfig b/core/java/android/service/dreams/flags.aconfig
new file mode 100644
index 0000000..91a713e
--- /dev/null
+++ b/core/java/android/service/dreams/flags.aconfig
@@ -0,0 +1,9 @@
+package: "android.service.dreams"
+
+flag {
+  name: "dream_overlay_host"
+  namespace: "communal"
+  description: "This flag enables using a host to handle displaying a dream's overlay rather than "
+      "relying on the dream's window"
+  bug: "291990564"
+}
diff --git a/core/java/android/service/voice/VisualQueryDetectionService.java b/core/java/android/service/voice/VisualQueryDetectionService.java
index d184b1e..76b076b 100644
--- a/core/java/android/service/voice/VisualQueryDetectionService.java
+++ b/core/java/android/service/voice/VisualQueryDetectionService.java
@@ -338,16 +338,24 @@
 
     /**
      * Overrides {@link Context#openFileInput} to read files with the given file names under the
-     * internal app storage of the {@link VoiceInteractionService}, i.e., only files stored in
-     * {@link Context#getFilesDir()} can be opened.
+     * internal app storage of the {@link VoiceInteractionService}, i.e., the input file path would
+     * be added with {@link Context#getFilesDir()} as prefix.
+     *
+     * @param filename Relative path of a file under {@link Context#getFilesDir()}.
+     * @throws FileNotFoundException if the file does not exist or cannot be open.
      */
     @Override
-    public @Nullable FileInputStream openFileInput(@NonNull String filename) throws
+    public @NonNull FileInputStream openFileInput(@NonNull String filename) throws
             FileNotFoundException {
         try {
             AndroidFuture<ParcelFileDescriptor> future = new AndroidFuture<>();
+            assert mDetectorSessionStorageService != null;
             mDetectorSessionStorageService.openFile(filename, future);
             ParcelFileDescriptor pfd = future.get();
+            if (pfd == null) {
+                throw new FileNotFoundException(
+                        "File does not exist. Unable to open " + filename + ".");
+            }
             return new FileInputStream(pfd.getFileDescriptor());
         } catch (RemoteException | ExecutionException | InterruptedException e) {
             Log.w(TAG, "Cannot open file due to remote service failure");
diff --git a/core/java/android/service/voice/VisualQueryDetector.java b/core/java/android/service/voice/VisualQueryDetector.java
index 91de894..b7d9705 100644
--- a/core/java/android/service/voice/VisualQueryDetector.java
+++ b/core/java/android/service/voice/VisualQueryDetector.java
@@ -447,12 +447,12 @@
         public void onOpenFile(String filename, AndroidFuture future) throws RemoteException {
             Slog.v(TAG, "BinderCallback#onOpenFile " + filename);
             Binder.withCleanCallingIdentity(() -> mExecutor.execute(() -> {
-                Slog.v(TAG, "onOpenFile: " + filename);
+                Slog.v(TAG, "onOpenFile: " + filename + "under internal app storage.");
                 File f = new File(mContext.getFilesDir(), filename);
                 ParcelFileDescriptor pfd = null;
                 try {
-                    Slog.d(TAG, "opened a file with ParcelFileDescriptor.");
                     pfd = ParcelFileDescriptor.open(f, ParcelFileDescriptor.MODE_READ_ONLY);
+                    Slog.d(TAG, "Successfully opened a file with ParcelFileDescriptor.");
                 } catch (FileNotFoundException e) {
                     Slog.e(TAG, "Cannot open file. No ParcelFileDescriptor returned.");
                 } finally {
diff --git a/core/java/android/view/AttachedSurfaceControl.java b/core/java/android/view/AttachedSurfaceControl.java
index fd5517d..f28574e 100644
--- a/core/java/android/view/AttachedSurfaceControl.java
+++ b/core/java/android/view/AttachedSurfaceControl.java
@@ -27,9 +27,6 @@
 
 import com.android.window.flags.Flags;
 
-import java.util.concurrent.Executor;
-import java.util.function.Consumer;
-
 /**
  * Provides an interface to the root-Surface of a View Hierarchy or Window. This
  * is used in combination with the {@link android.view.SurfaceControl} API to enable
@@ -197,42 +194,6 @@
     }
 
     /**
-     * Add a trusted presentation listener on the SurfaceControl associated with this window.
-     *
-     * @param t          Transaction that the trusted presentation listener is added on. This should
-     *                   be applied by the caller.
-     * @param thresholds The {@link SurfaceControl.TrustedPresentationThresholds} that will specify
-     *                   when the to invoke the callback.
-     * @param executor   The {@link Executor} where the callback will be invoked on.
-     * @param listener   The {@link Consumer} that will receive the callbacks when entered or
-     *                   exited the threshold.
-     *
-     * @see SurfaceControl.Transaction#setTrustedPresentationCallback(SurfaceControl,
-     * SurfaceControl.TrustedPresentationThresholds, Executor, Consumer)
-     *
-     * @hide b/287076178 un-hide with API bump
-     */
-    default void addTrustedPresentationCallback(@NonNull SurfaceControl.Transaction t,
-            @NonNull SurfaceControl.TrustedPresentationThresholds thresholds,
-            @NonNull Executor executor, @NonNull Consumer<Boolean> listener) {
-    }
-
-    /**
-     * Remove a trusted presentation listener on the SurfaceControl associated with this window.
-     *
-     * @param t          Transaction that the trusted presentation listener removed on. This should
-     *                   be applied by the caller.
-     * @param listener   The {@link Consumer} that was previously registered with
-     *                   addTrustedPresentationCallback that should be removed.
-     *
-     * @see SurfaceControl.Transaction#clearTrustedPresentationCallback(SurfaceControl)
-     * @hide b/287076178 un-hide with API bump
-     */
-    default void removeTrustedPresentationCallback(@NonNull SurfaceControl.Transaction t,
-            @NonNull Consumer<Boolean> listener) {
-    }
-
-    /**
      * Transfer the currently in progress touch gesture from the host to the requested
      * {@link SurfaceControlViewHost.SurfacePackage}. This requires that the
      * SurfaceControlViewHost was created with the current host's inputToken.
diff --git a/core/java/android/view/IWindowManager.aidl b/core/java/android/view/IWindowManager.aidl
index 17bbee6d0..36b74e3 100644
--- a/core/java/android/view/IWindowManager.aidl
+++ b/core/java/android/view/IWindowManager.aidl
@@ -73,6 +73,8 @@
 import android.window.ITaskFpsCallback;
 import android.window.ScreenCapture;
 import android.window.WindowContextInfo;
+import android.window.ITrustedPresentationListener;
+import android.window.TrustedPresentationThresholds;
 
 /**
  * System private interface to the window manager.
@@ -1075,4 +1077,10 @@
     @JavaPassthrough(annotation = "@android.annotation.RequiresPermission(android.Manifest"
             + ".permission.MONITOR_INPUT)")
     void unregisterDecorViewGestureListener(IDecorViewGestureListener listener, int displayId);
+
+    void registerTrustedPresentationListener(in IBinder window, in ITrustedPresentationListener listener,
+            in TrustedPresentationThresholds thresholds, int id);
+
+
+    void unregisterTrustedPresentationListener(in ITrustedPresentationListener listener, int id);
 }
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index 1530aa7..5cbb42e 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -826,11 +826,19 @@
      * The resolved pointer icon type requested by this window.
      * A null value indicates the resolved pointer icon has not yet been calculated.
      */
+    // TODO(b/293587049): Remove pointer icon tracking by type when refactor is complete.
     @Nullable
     private Integer mPointerIconType = null;
     private PointerIcon mCustomPointerIcon = null;
 
     /**
+     * The resolved pointer icon requested by this window.
+     * A null value indicates the resolved pointer icon has not yet been calculated.
+     */
+    @Nullable
+    private PointerIcon mResolvedPointerIcon = null;
+
+    /**
      * see {@link #playSoundEffect(int)}
      */
     AudioManager mAudioManager;
@@ -3216,6 +3224,12 @@
                 endDragResizing();
                 destroyHardwareResources();
             }
+
+            if (sToolkitSetFrameRateReadOnlyFlagValue && viewVisibility == View.VISIBLE) {
+                // Boost frame rate when the viewVisibility becomes true.
+                // This is mainly for lanuchers that lanuch new windows.
+                boostFrameRate(FRAME_RATE_TOUCH_BOOST_TIME);
+            }
         }
 
         // Non-visible windows can't hold accessibility focus.
@@ -3925,6 +3939,11 @@
                     focused.restoreDefaultFocus();
                 }
             }
+
+            if (sToolkitSetFrameRateReadOnlyFlagValue) {
+                // Boost the frame rate when the ViewRootImpl first becomes available.
+                boostFrameRate(FRAME_RATE_TOUCH_BOOST_TIME);
+            }
         }
 
         final boolean changedVisibility = (viewVisibilityChanged || mFirst) && isViewVisible;
@@ -7399,12 +7418,14 @@
                 // Other apps or the window manager may change the icon type outside of
                 // this app, therefore the icon type has to be reset on enter/exit event.
                 mPointerIconType = null;
+                mResolvedPointerIcon = null;
             }
 
             if (action != MotionEvent.ACTION_HOVER_EXIT) {
                 // Resolve the pointer icon
                 if (!updatePointerIcon(event) && action == MotionEvent.ACTION_HOVER_MOVE) {
                     mPointerIconType = null;
+                    mResolvedPointerIcon = null;
                 }
             }
 
@@ -7459,6 +7480,7 @@
 
     private void resetPointerIcon(MotionEvent event) {
         mPointerIconType = null;
+        mResolvedPointerIcon = null;
         updatePointerIcon(event);
     }
 
@@ -7496,6 +7518,21 @@
             pointerIcon = mView.onResolvePointerIcon(event, pointerIndex);
         }
 
+        if (enablePointerChoreographer()) {
+            if (pointerIcon == null) {
+                pointerIcon = PointerIcon.getSystemIcon(mContext, PointerIcon.TYPE_NOT_SPECIFIED);
+            }
+            if (Objects.equals(mResolvedPointerIcon, pointerIcon)) {
+                return true;
+            }
+            mResolvedPointerIcon = pointerIcon;
+
+            InputManagerGlobal.getInstance()
+                    .setPointerIcon(pointerIcon, event.getDisplayId(),
+                            event.getDeviceId(), event.getPointerId(0), getInputToken());
+            return true;
+        }
+
         final int pointerType = (pointerIcon != null) ?
                 pointerIcon.getType() : PointerIcon.TYPE_NOT_SPECIFIED;
 
@@ -7503,34 +7540,18 @@
             mPointerIconType = pointerType;
             mCustomPointerIcon = null;
             if (mPointerIconType != PointerIcon.TYPE_CUSTOM) {
-                if (enablePointerChoreographer()) {
-                    InputManagerGlobal
-                            .getInstance()
-                            .setPointerIcon(PointerIcon.getSystemIcon(mContext, pointerType),
-                                    event.getDisplayId(), event.getDeviceId(),
-                                    event.getPointerId(pointerIndex), getInputToken());
-                } else {
-                    InputManagerGlobal
-                            .getInstance()
-                            .setPointerIconType(pointerType);
-                }
+                InputManagerGlobal
+                        .getInstance()
+                        .setPointerIconType(pointerType);
                 return true;
             }
         }
         if (mPointerIconType == PointerIcon.TYPE_CUSTOM &&
                 !pointerIcon.equals(mCustomPointerIcon)) {
             mCustomPointerIcon = pointerIcon;
-            if (enablePointerChoreographer()) {
-                InputManagerGlobal
-                        .getInstance()
-                        .setPointerIcon(mCustomPointerIcon,
-                                event.getDisplayId(), event.getDeviceId(),
-                                event.getPointerId(pointerIndex), getInputToken());
-            } else {
-                InputManagerGlobal
-                        .getInstance()
-                        .setCustomPointerIcon(mCustomPointerIcon);
-            }
+            InputManagerGlobal
+                    .getInstance()
+                    .setCustomPointerIcon(mCustomPointerIcon);
         }
         return true;
     }
@@ -12005,18 +12026,6 @@
         scheduleTraversals();
     }
 
-    @Override
-    public void addTrustedPresentationCallback(@NonNull SurfaceControl.Transaction t,
-            @NonNull SurfaceControl.TrustedPresentationThresholds thresholds,
-            @NonNull Executor executor, @NonNull Consumer<Boolean> listener) {
-        t.setTrustedPresentationCallback(getSurfaceControl(), thresholds, executor, listener);
-    }
-
-    @Override
-    public void removeTrustedPresentationCallback(@NonNull SurfaceControl.Transaction t,
-            @NonNull Consumer<Boolean> listener) {
-        t.clearTrustedPresentationCallback(getSurfaceControl());
-    }
 
     private void logAndTrace(String msg) {
         if (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) {
@@ -12036,7 +12045,7 @@
         try {
             if (mLastPreferredFrameRateCategory != frameRateCategory) {
                 mFrameRateTransaction.setFrameRateCategory(mSurfaceControl,
-                    frameRateCategory, false).applyAsyncUnsafe();
+                        frameRateCategory, false).applyAsyncUnsafe();
                 mLastPreferredFrameRateCategory = frameRateCategory;
             }
         } catch (Exception e) {
@@ -12159,6 +12168,22 @@
         return mPreferredFrameRate;
     }
 
+    /**
+     * Get the value of mIsFrameRateBoosting
+     */
+    @VisibleForTesting
+    public boolean getIsFrameRateBoosting() {
+        return mIsFrameRateBoosting;
+    }
+
+    private void boostFrameRate(int boostTimeOut) {
+        mIsFrameRateBoosting = true;
+        setPreferredFrameRateCategory(mPreferredFrameRateCategory);
+        mHandler.removeMessages(MSG_TOUCH_BOOST_TIMEOUT);
+        mHandler.sendEmptyMessageDelayed(MSG_TOUCH_BOOST_TIMEOUT,
+                boostTimeOut);
+    }
+
     @Override
     public boolean transferHostTouchGestureToEmbedded(
             @NonNull SurfaceControlViewHost.SurfacePackage surfacePackage) {
diff --git a/core/java/android/view/WindowManager.java b/core/java/android/view/WindowManager.java
index 046ea77..c7e1807 100644
--- a/core/java/android/view/WindowManager.java
+++ b/core/java/android/view/WindowManager.java
@@ -122,7 +122,11 @@
 import android.view.WindowInsets.Type;
 import android.view.WindowInsets.Type.InsetsType;
 import android.view.accessibility.AccessibilityNodeInfo;
+import android.window.ITrustedPresentationListener;
 import android.window.TaskFpsCallback;
+import android.window.TrustedPresentationThresholds;
+
+import com.android.window.flags.Flags;
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
@@ -656,26 +660,35 @@
 
     /**
      * Display IME Policy: The IME should appear on the local display.
+     *
      * @hide
      */
-    @TestApi
+    @SuppressLint("UnflaggedApi")  // promoting from @TestApi.
+    @SystemApi
     int DISPLAY_IME_POLICY_LOCAL = 0;
 
     /**
-     * Display IME Policy: The IME should appear on the fallback display.
+     * Display IME Policy: The IME should appear on a fallback display.
+     *
+     * <p>The fallback display is always {@link Display#DEFAULT_DISPLAY}.</p>
+     *
      * @hide
      */
-    @TestApi
+    @SuppressLint("UnflaggedApi")  // promoting from @TestApi.
+    @SystemApi
     int DISPLAY_IME_POLICY_FALLBACK_DISPLAY = 1;
 
     /**
      * Display IME Policy: The IME should be hidden.
      *
-     * Setting this policy will prevent the IME from making a connection. This
-     * will prevent any IME from receiving metadata about input.
+     * <p>Setting this policy will prevent the IME from making a connection. This
+     * will prevent any IME from receiving metadata about input and this display will effectively
+     * have no IME.</p>
+     *
      * @hide
      */
-    @TestApi
+    @SuppressLint("UnflaggedApi")  // promoting from @TestApi.
+    @SystemApi
     int DISPLAY_IME_POLICY_HIDE = 2;
 
     /**
@@ -3251,6 +3264,13 @@
         public static final int PRIVATE_FLAG_COLOR_SPACE_AGNOSTIC = 1 << 24;
 
         /**
+         * Flag to indicate that the window consumes the insets of {@link Type#ime()}. This makes
+         * windows below this window unable to receive visible IME insets.
+         * @hide
+         */
+        public static final int PRIVATE_FLAG_CONSUME_IME_INSETS = 1 << 25;
+
+        /**
          * Flag to indicate that the window is controlling the appearance of system bars. So we
          * don't need to adjust it by reading its system UI flags for compatibility.
          * @hide
@@ -3334,6 +3354,7 @@
                 PRIVATE_FLAG_EXCLUDE_FROM_SCREEN_MAGNIFICATION,
                 PRIVATE_FLAG_NOT_MAGNIFIABLE,
                 PRIVATE_FLAG_COLOR_SPACE_AGNOSTIC,
+                PRIVATE_FLAG_CONSUME_IME_INSETS,
                 PRIVATE_FLAG_APPEARANCE_CONTROLLED,
                 PRIVATE_FLAG_BEHAVIOR_CONTROLLED,
                 PRIVATE_FLAG_FIT_INSETS_CONTROLLED,
@@ -3432,6 +3453,10 @@
                         equals = PRIVATE_FLAG_COLOR_SPACE_AGNOSTIC,
                         name = "COLOR_SPACE_AGNOSTIC"),
                 @ViewDebug.FlagToString(
+                        mask = PRIVATE_FLAG_CONSUME_IME_INSETS,
+                        equals = PRIVATE_FLAG_CONSUME_IME_INSETS,
+                        name = "CONSUME_IME_INSETS"),
+                @ViewDebug.FlagToString(
                         mask = PRIVATE_FLAG_APPEARANCE_CONTROLLED,
                         equals = PRIVATE_FLAG_APPEARANCE_CONTROLLED,
                         name = "APPEARANCE_CONTROLLED"),
@@ -3458,7 +3483,7 @@
                 @ViewDebug.FlagToString(
                         mask = PRIVATE_FLAG_SYSTEM_APPLICATION_OVERLAY,
                         equals = PRIVATE_FLAG_SYSTEM_APPLICATION_OVERLAY,
-                        name = "PRIVATE_FLAG_SYSTEM_APPLICATION_OVERLAY")
+                        name = "SYSTEM_APPLICATION_OVERLAY")
         })
         @PrivateFlags
         @TestApi
@@ -5884,4 +5909,34 @@
     default boolean replaceContentOnDisplayWithSc(int displayId, @NonNull SurfaceControl sc) {
         throw new UnsupportedOperationException();
     }
+
+    /**
+     * Add a trusted presentation listener associated with a window.
+     *
+     * <p> If this listener is already registered then the window and thresholds will be updated.
+     *
+     * @param window     The Window to add the trusted presentation listener for
+     * @param thresholds The {@link TrustedPresentationThresholds} that will specify
+     *                   when the to invoke the callback.
+     * @param executor   The {@link Executor} where the callback will be invoked on.
+     * @param listener   The {@link Consumer} that will receive the callbacks
+     *                  when entered or exited trusted presentation per the thresholds.
+     */
+    @FlaggedApi(Flags.FLAG_TRUSTED_PRESENTATION_LISTENER_FOR_WINDOW)
+    default void registerTrustedPresentationListener(@NonNull IBinder window,
+            @NonNull TrustedPresentationThresholds thresholds,  @NonNull Executor executor,
+            @NonNull Consumer<Boolean> listener) {
+        throw new UnsupportedOperationException();
+    }
+
+    /**
+     * Removes a presentation listener associated with a window. If the listener was not previously
+     * registered, the call will be a noop.
+     *
+     * @see WindowManager#registerTrustedPresentationListener(IBinder, TrustedPresentationThresholds, Executor, Consumer)
+     */
+    @FlaggedApi(Flags.FLAG_TRUSTED_PRESENTATION_LISTENER_FOR_WINDOW)
+    default void unregisterTrustedPresentationListener(@NonNull Consumer<Boolean> listener) {
+        throw new UnsupportedOperationException();
+    }
 }
diff --git a/core/java/android/view/WindowManagerGlobal.java b/core/java/android/view/WindowManagerGlobal.java
index 214f1ec..f1e4061 100644
--- a/core/java/android/view/WindowManagerGlobal.java
+++ b/core/java/android/view/WindowManagerGlobal.java
@@ -30,9 +30,13 @@
 import android.os.ServiceManager;
 import android.os.SystemProperties;
 import android.util.AndroidRuntimeException;
+import android.util.ArrayMap;
 import android.util.ArraySet;
 import android.util.Log;
+import android.util.Pair;
 import android.view.inputmethod.InputMethodManager;
+import android.window.ITrustedPresentationListener;
+import android.window.TrustedPresentationThresholds;
 
 import com.android.internal.util.FastPrintWriter;
 
@@ -43,6 +47,7 @@
 import java.util.ArrayList;
 import java.util.WeakHashMap;
 import java.util.concurrent.Executor;
+import java.util.function.Consumer;
 import java.util.function.IntConsumer;
 
 /**
@@ -143,6 +148,9 @@
 
     private Runnable mSystemPropertyUpdater;
 
+    private final TrustedPresentationListener mTrustedPresentationListener =
+            new TrustedPresentationListener();
+
     private WindowManagerGlobal() {
     }
 
@@ -324,7 +332,7 @@
             final Context context = view.getContext();
             if (context != null
                     && (context.getApplicationInfo().flags
-                            & ApplicationInfo.FLAG_HARDWARE_ACCELERATED) != 0) {
+                    & ApplicationInfo.FLAG_HARDWARE_ACCELERATED) != 0) {
                 wparams.flags |= WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED;
             }
         }
@@ -482,7 +490,7 @@
                     if (who != null) {
                         WindowLeaked leak = new WindowLeaked(
                                 what + " " + who + " has leaked window "
-                                + root.getView() + " that was originally added here");
+                                        + root.getView() + " that was originally added here");
                         leak.setStackTrace(root.getLocation().getStackTrace());
                         Log.e(TAG, "", leak);
                     }
@@ -790,6 +798,87 @@
         }
     }
 
+    public void registerTrustedPresentationListener(@NonNull IBinder window,
+            @NonNull TrustedPresentationThresholds thresholds, Executor executor,
+            @NonNull Consumer<Boolean> listener) {
+        mTrustedPresentationListener.addListener(window, thresholds, listener, executor);
+    }
+
+    public void unregisterTrustedPresentationListener(@NonNull Consumer<Boolean> listener) {
+        mTrustedPresentationListener.removeListener(listener);
+    }
+
+    private final class TrustedPresentationListener extends
+            ITrustedPresentationListener.Stub {
+        private static int sId = 0;
+        private final ArrayMap<Consumer<Boolean>, Pair<Integer, Executor>> mListeners =
+                new ArrayMap<>();
+
+        private final Object mTplLock = new Object();
+
+        private void addListener(IBinder window, TrustedPresentationThresholds thresholds,
+                Consumer<Boolean> listener, Executor executor) {
+            synchronized (mTplLock) {
+                if (mListeners.containsKey(listener)) {
+                    Log.i(TAG, "Updating listener " + listener + " thresholds to " + thresholds);
+                    removeListener(listener);
+                }
+                int id = sId++;
+                mListeners.put(listener, new Pair<>(id, executor));
+                try {
+                    WindowManagerGlobal.getWindowManagerService()
+                            .registerTrustedPresentationListener(window, this, thresholds, id);
+                } catch (RemoteException e) {
+                    e.rethrowAsRuntimeException();
+                }
+            }
+        }
+
+        private void removeListener(Consumer<Boolean> listener) {
+            synchronized (mTplLock) {
+                var removedListener = mListeners.remove(listener);
+                if (removedListener == null) {
+                    Log.i(TAG, "listener " + listener + " does not exist.");
+                    return;
+                }
+
+                try {
+                    WindowManagerGlobal.getWindowManagerService()
+                            .unregisterTrustedPresentationListener(this, removedListener.first);
+                } catch (RemoteException e) {
+                    e.rethrowAsRuntimeException();
+                }
+            }
+        }
+
+        @Override
+        public void onTrustedPresentationChanged(int[] inTrustedStateListenerIds,
+                int[] outOfTrustedStateListenerIds) {
+            ArrayList<Runnable> firedListeners = new ArrayList<>();
+            synchronized (mTplLock) {
+                mListeners.forEach((listener, idExecutorPair) -> {
+                    final var listenerId =  idExecutorPair.first;
+                    final var executor = idExecutorPair.second;
+                    for (int id : inTrustedStateListenerIds) {
+                        if (listenerId == id) {
+                            firedListeners.add(() -> executor.execute(
+                                    () -> listener.accept(/*presentationState*/true)));
+                        }
+                    }
+                    for (int id : outOfTrustedStateListenerIds) {
+                        if (listenerId == id) {
+                            firedListeners.add(() -> executor.execute(
+                                    () -> listener.accept(/*presentationState*/false)));
+                        }
+                    }
+                });
+            }
+            for (int i = 0; i < firedListeners.size(); i++) {
+                firedListeners.get(i).run();
+            }
+        }
+    }
+
     /** @hide */
     public void addWindowlessRoot(ViewRootImpl impl) {
         synchronized (mLock) {
@@ -801,7 +890,7 @@
     public void removeWindowlessRoot(ViewRootImpl impl) {
         synchronized (mLock) {
             mWindowlessRoots.remove(impl);
-	}
+        }
     }
 
     public void setRecentsAppBehindSystemBars(boolean behindSystemBars) {
diff --git a/core/java/android/view/WindowManagerImpl.java b/core/java/android/view/WindowManagerImpl.java
index d7b74b3..b4b1fde 100644
--- a/core/java/android/view/WindowManagerImpl.java
+++ b/core/java/android/view/WindowManagerImpl.java
@@ -37,6 +37,7 @@
 import android.util.Log;
 import android.window.ITaskFpsCallback;
 import android.window.TaskFpsCallback;
+import android.window.TrustedPresentationThresholds;
 import android.window.WindowContext;
 import android.window.WindowMetricsController;
 import android.window.WindowProvider;
@@ -508,4 +509,17 @@
         }
         return false;
     }
+
+    @Override
+    public void registerTrustedPresentationListener(@NonNull IBinder window,
+            @NonNull TrustedPresentationThresholds thresholds, @NonNull Executor executor,
+            @NonNull Consumer<Boolean> listener) {
+        mGlobal.registerTrustedPresentationListener(window, thresholds, executor, listener);
+    }
+
+    @Override
+    public void unregisterTrustedPresentationListener(@NonNull Consumer<Boolean> listener) {
+        mGlobal.unregisterTrustedPresentationListener(listener);
+
+    }
 }
diff --git a/core/java/android/view/inputmethod/ImeTracker.java b/core/java/android/view/inputmethod/ImeTracker.java
index fab8c77..1b7d57b 100644
--- a/core/java/android/view/inputmethod/ImeTracker.java
+++ b/core/java/android/view/inputmethod/ImeTracker.java
@@ -20,8 +20,8 @@
 import static android.view.InsetsController.ANIMATION_TYPE_SHOW;
 
 import static com.android.internal.inputmethod.InputMethodDebug.softInputDisplayReasonToString;
-import static com.android.internal.jank.InteractionJankMonitor.CUJ_IME_INSETS_HIDE_ANIMATION;
-import static com.android.internal.jank.InteractionJankMonitor.CUJ_IME_INSETS_SHOW_ANIMATION;
+import static com.android.internal.jank.Cuj.CUJ_IME_INSETS_HIDE_ANIMATION;
+import static com.android.internal.jank.Cuj.CUJ_IME_INSETS_SHOW_ANIMATION;
 import static com.android.internal.util.LatencyTracker.ACTION_REQUEST_IME_HIDDEN;
 import static com.android.internal.util.LatencyTracker.ACTION_REQUEST_IME_SHOWN;
 
@@ -758,7 +758,7 @@
          * A helper method to translate animation type to CUJ type for IME animations.
          *
          * @param animType the animation type.
-         * @return the integer in {@link com.android.internal.jank.InteractionJankMonitor.CujType},
+         * @return the integer in {@link com.android.internal.jank.Cuj.CujType},
          * or {@code -1} if the animation type is not supported for tracking yet.
          */
         private static int getImeInsetsCujFromAnimation(@AnimationType int animType) {
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/data/repository/DeviceEntryHapticsRepositoryKosmos.kt b/core/java/android/window/ITrustedPresentationListener.aidl
similarity index 64%
copy from packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/data/repository/DeviceEntryHapticsRepositoryKosmos.kt
copy to core/java/android/window/ITrustedPresentationListener.aidl
index 8bb07d9..b33128a 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/data/repository/DeviceEntryHapticsRepositoryKosmos.kt
+++ b/core/java/android/window/ITrustedPresentationListener.aidl
@@ -14,10 +14,11 @@
  * limitations under the License.
  */
 
-package com.android.systemui.deviceentry.data.repository
+package android.window;
 
-import com.android.systemui.kosmos.Kosmos
-
-var Kosmos.deviceEntryHapticsRepository: DeviceEntryHapticsRepository by
-    Kosmos.Fixture { fakeDeviceEntryHapticsRepository }
-val Kosmos.fakeDeviceEntryHapticsRepository by Kosmos.Fixture { FakeDeviceEntryHapticsRepository() }
+/**
+ * @hide
+ */
+oneway interface ITrustedPresentationListener {
+    void onTrustedPresentationChanged(in int[] enteredTrustedStateIds, in int[] exitedTrustedStateIds);
+}
\ No newline at end of file
diff --git a/core/java/android/window/TrustedPresentationListener.java b/core/java/android/window/TrustedPresentationListener.java
new file mode 100644
index 0000000..02fd6d9
--- /dev/null
+++ b/core/java/android/window/TrustedPresentationListener.java
@@ -0,0 +1,26 @@
+/*
+ * 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.window;
+
+/**
+ * @hide
+ */
+public interface TrustedPresentationListener {
+
+    void onTrustedPresentationChanged(boolean inTrustedPresentationState);
+
+}
diff --git a/core/java/android/window/TrustedPresentationThresholds.aidl b/core/java/android/window/TrustedPresentationThresholds.aidl
new file mode 100644
index 0000000..d7088bf
--- /dev/null
+++ b/core/java/android/window/TrustedPresentationThresholds.aidl
@@ -0,0 +1,3 @@
+package android.window;
+
+parcelable TrustedPresentationThresholds;
diff --git a/core/java/android/window/TrustedPresentationThresholds.java b/core/java/android/window/TrustedPresentationThresholds.java
new file mode 100644
index 0000000..90f8834
--- /dev/null
+++ b/core/java/android/window/TrustedPresentationThresholds.java
@@ -0,0 +1,144 @@
+/*
+ * 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.window;
+
+import android.annotation.FlaggedApi;
+import android.annotation.FloatRange;
+import android.annotation.IntRange;
+import android.annotation.SuppressLint;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.view.SurfaceControl;
+
+import androidx.annotation.NonNull;
+
+import com.android.window.flags.Flags;
+
+/**
+ * Threshold values that are sent with
+ * {@link android.view.WindowManager#registerTrustedPresentationListener(IBinder,
+ * TrustedPresentationThresholds, Executor, Consumer)}
+ */
+@FlaggedApi(Flags.FLAG_TRUSTED_PRESENTATION_LISTENER_FOR_WINDOW)
+public final class TrustedPresentationThresholds implements Parcelable {
+    /**
+     * The min alpha the {@link SurfaceControl} is required to have to be considered inside the
+     * threshold.
+     */
+    @FloatRange(from = 0f, fromInclusive = false, to = 1f)
+    @FlaggedApi(Flags.FLAG_TRUSTED_PRESENTATION_LISTENER_FOR_WINDOW)
+    @SuppressLint("InternalField") // simple data class
+    public final float minAlpha;
+
+    /**
+     * The min fraction of the SurfaceControl that was presented to the user to be considered
+     * inside the threshold.
+     */
+    @FloatRange(from = 0f, fromInclusive = false, to = 1f)
+    @FlaggedApi(Flags.FLAG_TRUSTED_PRESENTATION_LISTENER_FOR_WINDOW)
+    @SuppressLint("InternalField") // simple data class
+    public final float minFractionRendered;
+
+    /**
+     * The time in milliseconds required for the {@link SurfaceControl} to be in the threshold.
+     */
+    @IntRange(from = 1)
+    @FlaggedApi(Flags.FLAG_TRUSTED_PRESENTATION_LISTENER_FOR_WINDOW)
+    @SuppressLint("InternalField") // simple data class
+    public final int stabilityRequirementMs;
+
+    private void checkValid() {
+        if (minAlpha <= 0 || minFractionRendered <= 0 || stabilityRequirementMs < 1) {
+            throw new IllegalArgumentException(
+                    "TrustedPresentationThresholds values are invalid");
+        }
+    }
+
+    /**
+     * Creates a new TrustedPresentationThresholds.
+     *
+     * @param minAlpha               The min alpha the {@link SurfaceControl} is required to
+     *                               have to be considered inside the
+     *                               threshold.
+     * @param minFractionRendered    The min fraction of the SurfaceControl that was presented
+     *                               to the user to be considered
+     *                               inside the threshold.
+     * @param stabilityRequirementMs The time in milliseconds required for the
+     *                               {@link SurfaceControl} to be in the threshold.
+     */
+    @FlaggedApi(Flags.FLAG_TRUSTED_PRESENTATION_LISTENER_FOR_WINDOW)
+    public TrustedPresentationThresholds(
+            @FloatRange(from = 0f, fromInclusive = false, to = 1f) float minAlpha,
+            @FloatRange(from = 0f, fromInclusive = false, to = 1f) float minFractionRendered,
+            @IntRange(from = 1) int stabilityRequirementMs) {
+        this.minAlpha = minAlpha;
+        this.minFractionRendered = minFractionRendered;
+        this.stabilityRequirementMs = stabilityRequirementMs;
+        checkValid();
+    }
+
+    @Override
+    @FlaggedApi(Flags.FLAG_TRUSTED_PRESENTATION_LISTENER_FOR_WINDOW)
+    public String toString() {
+        return "TrustedPresentationThresholds { "
+                + "minAlpha = " + minAlpha + ", "
+                + "minFractionRendered = " + minFractionRendered + ", "
+                + "stabilityRequirementMs = " + stabilityRequirementMs
+                + " }";
+    }
+
+    @Override
+    @FlaggedApi(Flags.FLAG_TRUSTED_PRESENTATION_LISTENER_FOR_WINDOW)
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+        dest.writeFloat(minAlpha);
+        dest.writeFloat(minFractionRendered);
+        dest.writeInt(stabilityRequirementMs);
+    }
+
+    @Override
+    @FlaggedApi(Flags.FLAG_TRUSTED_PRESENTATION_LISTENER_FOR_WINDOW)
+    public int describeContents() {
+        return 0;
+    }
+
+    /**
+     * @hide
+     */
+    TrustedPresentationThresholds(@NonNull Parcel in) {
+        minAlpha = in.readFloat();
+        minFractionRendered = in.readFloat();
+        stabilityRequirementMs = in.readInt();
+
+        checkValid();
+    }
+
+    @FlaggedApi(Flags.FLAG_TRUSTED_PRESENTATION_LISTENER_FOR_WINDOW)
+    public static final @NonNull Creator<TrustedPresentationThresholds> CREATOR =
+            new Creator<TrustedPresentationThresholds>() {
+                @Override
+                @FlaggedApi(Flags.FLAG_TRUSTED_PRESENTATION_LISTENER_FOR_WINDOW)
+                public TrustedPresentationThresholds[] newArray(int size) {
+                    return new TrustedPresentationThresholds[size];
+                }
+
+                @Override
+                @FlaggedApi(Flags.FLAG_TRUSTED_PRESENTATION_LISTENER_FOR_WINDOW)
+                public TrustedPresentationThresholds createFromParcel(@NonNull Parcel in) {
+                    return new TrustedPresentationThresholds(in);
+                }
+            };
+}
diff --git a/core/java/android/window/flags/window_surfaces.aconfig b/core/java/android/window/flags/window_surfaces.aconfig
index 29932f3..56df493 100644
--- a/core/java/android/window/flags/window_surfaces.aconfig
+++ b/core/java/android/window/flags/window_surfaces.aconfig
@@ -56,3 +56,11 @@
     is_fixed_read_only: true
     bug: "308662081"
 }
+
+flag {
+    namespace: "window_surfaces"
+    name: "trusted_presentation_listener_for_window"
+    description: "Enable trustedPresentationListener on windows public API"
+    is_fixed_read_only: true
+    bug: "278027319"
+}
\ No newline at end of file
diff --git a/core/java/com/android/internal/app/SuspendedAppActivity.java b/core/java/com/android/internal/app/SuspendedAppActivity.java
index 1e0b2a0..efc1455 100644
--- a/core/java/com/android/internal/app/SuspendedAppActivity.java
+++ b/core/java/com/android/internal/app/SuspendedAppActivity.java
@@ -80,7 +80,8 @@
                 // Suspension conditions were modified, dismiss any related visible dialogs.
                 final String[] modified = intent.getStringArrayExtra(
                         Intent.EXTRA_CHANGED_PACKAGE_LIST);
-                if (ArrayUtils.contains(modified, mSuspendedPackage)) {
+                if (ArrayUtils.contains(modified, mSuspendedPackage)
+                        && !isPackageSuspended(mSuspendedPackage)) {
                     if (!isFinishing()) {
                         Slog.w(TAG, "Package " + mSuspendedPackage + " has modified"
                                 + " suspension conditions while dialog was visible. Finishing.");
@@ -92,6 +93,15 @@
         }
     };
 
+    private boolean isPackageSuspended(String packageName) {
+        try {
+            return mPm.isPackageSuspended(packageName);
+        } catch (PackageManager.NameNotFoundException ne) {
+            Slog.e(TAG, "Package " + packageName + " not found", ne);
+        }
+        return false;
+    }
+
     private CharSequence getAppLabel(String packageName) {
         try {
             return mPm.getApplicationInfoAsUser(packageName, 0, mUserId).loadLabel(mPm);
diff --git a/core/java/com/android/internal/jank/Cuj.java b/core/java/com/android/internal/jank/Cuj.java
new file mode 100644
index 0000000..f460233
--- /dev/null
+++ b/core/java/com/android/internal/jank/Cuj.java
@@ -0,0 +1,503 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.jank;
+
+import android.annotation.IntDef;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.FrameworkStatsLog;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.Arrays;
+
+/** @hide */
+public class Cuj {
+    @VisibleForTesting
+    public static final int MAX_LENGTH_OF_CUJ_NAME = 80;
+
+    // Every value must have a corresponding entry in CUJ_STATSD_INTERACTION_TYPE.
+    public static final int CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE = 0;
+    public static final int CUJ_NOTIFICATION_SHADE_SCROLL_FLING = 2;
+    public static final int CUJ_NOTIFICATION_SHADE_ROW_EXPAND = 3;
+    public static final int CUJ_NOTIFICATION_SHADE_ROW_SWIPE = 4;
+    public static final int CUJ_NOTIFICATION_SHADE_QS_EXPAND_COLLAPSE = 5;
+    public static final int CUJ_NOTIFICATION_SHADE_QS_SCROLL_SWIPE = 6;
+    public static final int CUJ_LAUNCHER_APP_LAUNCH_FROM_RECENTS = 7;
+    public static final int CUJ_LAUNCHER_APP_LAUNCH_FROM_ICON = 8;
+    public static final int CUJ_LAUNCHER_APP_CLOSE_TO_HOME = 9;
+    public static final int CUJ_LAUNCHER_APP_CLOSE_TO_PIP = 10;
+    public static final int CUJ_LAUNCHER_QUICK_SWITCH = 11;
+    public static final int CUJ_NOTIFICATION_HEADS_UP_APPEAR = 12;
+    public static final int CUJ_NOTIFICATION_HEADS_UP_DISAPPEAR = 13;
+    public static final int CUJ_NOTIFICATION_ADD = 14;
+    public static final int CUJ_NOTIFICATION_REMOVE = 15;
+    public static final int CUJ_NOTIFICATION_APP_START = 16;
+    public static final int CUJ_LOCKSCREEN_PASSWORD_APPEAR = 17;
+    public static final int CUJ_LOCKSCREEN_PATTERN_APPEAR = 18;
+    public static final int CUJ_LOCKSCREEN_PIN_APPEAR = 19;
+    public static final int CUJ_LOCKSCREEN_PASSWORD_DISAPPEAR = 20;
+    public static final int CUJ_LOCKSCREEN_PATTERN_DISAPPEAR = 21;
+    public static final int CUJ_LOCKSCREEN_PIN_DISAPPEAR = 22;
+    public static final int CUJ_LOCKSCREEN_TRANSITION_FROM_AOD = 23;
+    public static final int CUJ_LOCKSCREEN_TRANSITION_TO_AOD = 24;
+    public static final int CUJ_LAUNCHER_OPEN_ALL_APPS = 25;
+    public static final int CUJ_LAUNCHER_ALL_APPS_SCROLL = 26;
+    public static final int CUJ_LAUNCHER_APP_LAUNCH_FROM_WIDGET = 27;
+    public static final int CUJ_SETTINGS_PAGE_SCROLL = 28;
+    public static final int CUJ_LOCKSCREEN_UNLOCK_ANIMATION = 29;
+    public static final int CUJ_SHADE_APP_LAUNCH_FROM_HISTORY_BUTTON = 30;
+    public static final int CUJ_SHADE_APP_LAUNCH_FROM_MEDIA_PLAYER = 31;
+    public static final int CUJ_SHADE_APP_LAUNCH_FROM_QS_TILE = 32;
+    public static final int CUJ_SHADE_APP_LAUNCH_FROM_SETTINGS_BUTTON = 33;
+    public static final int CUJ_STATUS_BAR_APP_LAUNCH_FROM_CALL_CHIP = 34;
+    public static final int CUJ_PIP_TRANSITION = 35;
+    public static final int CUJ_WALLPAPER_TRANSITION = 36;
+    public static final int CUJ_USER_SWITCH = 37;
+    public static final int CUJ_SPLASHSCREEN_AVD = 38;
+    public static final int CUJ_SPLASHSCREEN_EXIT_ANIM = 39;
+    public static final int CUJ_SCREEN_OFF = 40;
+    public static final int CUJ_SCREEN_OFF_SHOW_AOD = 41;
+    public static final int CUJ_ONE_HANDED_ENTER_TRANSITION = 42;
+    public static final int CUJ_ONE_HANDED_EXIT_TRANSITION = 43;
+    public static final int CUJ_UNFOLD_ANIM = 44;
+    public static final int CUJ_SUW_LOADING_TO_SHOW_INFO_WITH_ACTIONS = 45;
+    public static final int CUJ_SUW_SHOW_FUNCTION_SCREEN_WITH_ACTIONS = 46;
+    public static final int CUJ_SUW_LOADING_TO_NEXT_FLOW = 47;
+    public static final int CUJ_SUW_LOADING_SCREEN_FOR_STATUS = 48;
+    public static final int CUJ_SPLIT_SCREEN_ENTER = 49;
+    public static final int CUJ_SPLIT_SCREEN_EXIT = 50;
+    public static final int CUJ_LOCKSCREEN_LAUNCH_CAMERA = 51; // reserved.
+    public static final int CUJ_SPLIT_SCREEN_RESIZE = 52;
+    public static final int CUJ_SETTINGS_SLIDER = 53;
+    public static final int CUJ_TAKE_SCREENSHOT = 54;
+    public static final int CUJ_VOLUME_CONTROL = 55;
+    public static final int CUJ_BIOMETRIC_PROMPT_TRANSITION = 56;
+    public static final int CUJ_SETTINGS_TOGGLE = 57;
+    public static final int CUJ_SHADE_DIALOG_OPEN = 58;
+    public static final int CUJ_USER_DIALOG_OPEN = 59;
+    public static final int CUJ_TASKBAR_EXPAND = 60;
+    public static final int CUJ_TASKBAR_COLLAPSE = 61;
+    public static final int CUJ_SHADE_CLEAR_ALL = 62;
+    public static final int CUJ_LAUNCHER_UNLOCK_ENTRANCE_ANIMATION = 63;
+    public static final int CUJ_LOCKSCREEN_OCCLUSION = 64;
+    public static final int CUJ_RECENTS_SCROLLING = 65;
+    public static final int CUJ_LAUNCHER_APP_SWIPE_TO_RECENTS = 66;
+    public static final int CUJ_LAUNCHER_CLOSE_ALL_APPS_SWIPE = 67;
+    public static final int CUJ_LAUNCHER_CLOSE_ALL_APPS_TO_HOME = 68;
+    public static final int CUJ_LOCKSCREEN_CLOCK_MOVE_ANIMATION = 70;
+    public static final int CUJ_LAUNCHER_OPEN_SEARCH_RESULT = 71;
+    // 72 - 77 are reserved for b/281564325.
+
+    /**
+     * In some cases when we do not have any end-target, we play a simple slide-down animation.
+     * eg: Open an app from Overview/Task switcher such that there is no home-screen icon.
+     * eg: Exit the app using back gesture.
+     */
+    public static final int CUJ_LAUNCHER_APP_CLOSE_TO_HOME_FALLBACK = 78;
+    public static final int CUJ_IME_INSETS_SHOW_ANIMATION = 80;
+    public static final int CUJ_IME_INSETS_HIDE_ANIMATION = 81;
+
+    public static final int CUJ_SPLIT_SCREEN_DOUBLE_TAP_DIVIDER = 82;
+
+    public static final int CUJ_LAUNCHER_UNFOLD_ANIM = 83;
+
+    public static final int CUJ_PREDICTIVE_BACK_CROSS_ACTIVITY = 84;
+    public static final int CUJ_PREDICTIVE_BACK_CROSS_TASK = 85;
+    public static final int CUJ_PREDICTIVE_BACK_HOME = 86;
+
+    // When adding a CUJ, update this and make sure to also update CUJ_TO_STATSD_INTERACTION_TYPE.
+    @VisibleForTesting
+    static final int LAST_CUJ = CUJ_PREDICTIVE_BACK_HOME;
+
+    /** @hide */
+    @IntDef({
+            CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE,
+            CUJ_NOTIFICATION_SHADE_SCROLL_FLING,
+            CUJ_NOTIFICATION_SHADE_ROW_EXPAND,
+            CUJ_NOTIFICATION_SHADE_ROW_SWIPE,
+            CUJ_NOTIFICATION_SHADE_QS_EXPAND_COLLAPSE,
+            CUJ_NOTIFICATION_SHADE_QS_SCROLL_SWIPE,
+            CUJ_LAUNCHER_APP_LAUNCH_FROM_RECENTS,
+            CUJ_LAUNCHER_APP_LAUNCH_FROM_ICON,
+            CUJ_LAUNCHER_APP_CLOSE_TO_HOME,
+            CUJ_LAUNCHER_APP_CLOSE_TO_PIP,
+            CUJ_LAUNCHER_QUICK_SWITCH,
+            CUJ_NOTIFICATION_HEADS_UP_APPEAR,
+            CUJ_NOTIFICATION_HEADS_UP_DISAPPEAR,
+            CUJ_NOTIFICATION_ADD,
+            CUJ_NOTIFICATION_REMOVE,
+            CUJ_NOTIFICATION_APP_START,
+            CUJ_LOCKSCREEN_PASSWORD_APPEAR,
+            CUJ_LOCKSCREEN_PATTERN_APPEAR,
+            CUJ_LOCKSCREEN_PIN_APPEAR,
+            CUJ_LOCKSCREEN_PASSWORD_DISAPPEAR,
+            CUJ_LOCKSCREEN_PATTERN_DISAPPEAR,
+            CUJ_LOCKSCREEN_PIN_DISAPPEAR,
+            CUJ_LOCKSCREEN_TRANSITION_FROM_AOD,
+            CUJ_LOCKSCREEN_TRANSITION_TO_AOD,
+            CUJ_LAUNCHER_OPEN_ALL_APPS,
+            CUJ_LAUNCHER_ALL_APPS_SCROLL,
+            CUJ_LAUNCHER_APP_LAUNCH_FROM_WIDGET,
+            CUJ_SETTINGS_PAGE_SCROLL,
+            CUJ_LOCKSCREEN_UNLOCK_ANIMATION,
+            CUJ_SHADE_APP_LAUNCH_FROM_HISTORY_BUTTON,
+            CUJ_SHADE_APP_LAUNCH_FROM_MEDIA_PLAYER,
+            CUJ_SHADE_APP_LAUNCH_FROM_QS_TILE,
+            CUJ_SHADE_APP_LAUNCH_FROM_SETTINGS_BUTTON,
+            CUJ_STATUS_BAR_APP_LAUNCH_FROM_CALL_CHIP,
+            CUJ_PIP_TRANSITION,
+            CUJ_WALLPAPER_TRANSITION,
+            CUJ_USER_SWITCH,
+            CUJ_SPLASHSCREEN_AVD,
+            CUJ_SPLASHSCREEN_EXIT_ANIM,
+            CUJ_SCREEN_OFF,
+            CUJ_SCREEN_OFF_SHOW_AOD,
+            CUJ_ONE_HANDED_ENTER_TRANSITION,
+            CUJ_ONE_HANDED_EXIT_TRANSITION,
+            CUJ_UNFOLD_ANIM,
+            CUJ_SUW_LOADING_TO_SHOW_INFO_WITH_ACTIONS,
+            CUJ_SUW_SHOW_FUNCTION_SCREEN_WITH_ACTIONS,
+            CUJ_SUW_LOADING_TO_NEXT_FLOW,
+            CUJ_SUW_LOADING_SCREEN_FOR_STATUS,
+            CUJ_SPLIT_SCREEN_ENTER,
+            CUJ_SPLIT_SCREEN_EXIT,
+            CUJ_LOCKSCREEN_LAUNCH_CAMERA,
+            CUJ_SPLIT_SCREEN_RESIZE,
+            CUJ_SETTINGS_SLIDER,
+            CUJ_TAKE_SCREENSHOT,
+            CUJ_VOLUME_CONTROL,
+            CUJ_BIOMETRIC_PROMPT_TRANSITION,
+            CUJ_SETTINGS_TOGGLE,
+            CUJ_SHADE_DIALOG_OPEN,
+            CUJ_USER_DIALOG_OPEN,
+            CUJ_TASKBAR_EXPAND,
+            CUJ_TASKBAR_COLLAPSE,
+            CUJ_SHADE_CLEAR_ALL,
+            CUJ_LAUNCHER_UNLOCK_ENTRANCE_ANIMATION,
+            CUJ_LOCKSCREEN_OCCLUSION,
+            CUJ_RECENTS_SCROLLING,
+            CUJ_LAUNCHER_APP_SWIPE_TO_RECENTS,
+            CUJ_LAUNCHER_CLOSE_ALL_APPS_SWIPE,
+            CUJ_LAUNCHER_CLOSE_ALL_APPS_TO_HOME,
+            CUJ_LOCKSCREEN_CLOCK_MOVE_ANIMATION,
+            CUJ_LAUNCHER_OPEN_SEARCH_RESULT,
+            CUJ_LAUNCHER_APP_CLOSE_TO_HOME_FALLBACK,
+            CUJ_IME_INSETS_SHOW_ANIMATION,
+            CUJ_IME_INSETS_HIDE_ANIMATION,
+            CUJ_SPLIT_SCREEN_DOUBLE_TAP_DIVIDER,
+            CUJ_LAUNCHER_UNFOLD_ANIM,
+            CUJ_PREDICTIVE_BACK_CROSS_ACTIVITY,
+            CUJ_PREDICTIVE_BACK_CROSS_TASK,
+            CUJ_PREDICTIVE_BACK_HOME,
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface CujType {
+    }
+
+    private static final int NO_STATSD_LOGGING = -1;
+
+    // Used to convert CujType to InteractionType enum value for statsd logging.
+    // Use NO_STATSD_LOGGING in case the measurement for a given CUJ should not be logged to statsd.
+    private static final int[] CUJ_TO_STATSD_INTERACTION_TYPE = new int[LAST_CUJ + 1];
+    static {
+        Arrays.fill(CUJ_TO_STATSD_INTERACTION_TYPE, NO_STATSD_LOGGING);
+
+        CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__NOTIFICATION_SHADE_SWIPE;
+        CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_NOTIFICATION_SHADE_SCROLL_FLING] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_SCROLL_FLING;
+        CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_NOTIFICATION_SHADE_ROW_EXPAND] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_ROW_EXPAND;
+        CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_NOTIFICATION_SHADE_ROW_SWIPE] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_ROW_SWIPE;
+        CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_NOTIFICATION_SHADE_QS_EXPAND_COLLAPSE] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_QS_EXPAND_COLLAPSE;
+        CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_NOTIFICATION_SHADE_QS_SCROLL_SWIPE] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_QS_SCROLL_SWIPE;
+        CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_LAUNCHER_APP_LAUNCH_FROM_RECENTS] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_APP_LAUNCH_FROM_RECENTS;
+        CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_LAUNCHER_APP_LAUNCH_FROM_ICON] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_APP_LAUNCH_FROM_ICON;
+        CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_LAUNCHER_APP_CLOSE_TO_HOME] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_APP_CLOSE_TO_HOME;
+        CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_LAUNCHER_APP_CLOSE_TO_PIP] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_APP_CLOSE_TO_PIP;
+        CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_LAUNCHER_QUICK_SWITCH] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_QUICK_SWITCH;
+        CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_NOTIFICATION_HEADS_UP_APPEAR] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_HEADS_UP_APPEAR;
+        CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_NOTIFICATION_HEADS_UP_DISAPPEAR] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_HEADS_UP_DISAPPEAR;
+        CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_NOTIFICATION_ADD] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_NOTIFICATION_ADD;
+        CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_NOTIFICATION_REMOVE] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_NOTIFICATION_REMOVE;
+        CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_NOTIFICATION_APP_START] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_APP_LAUNCH;
+        CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_LOCKSCREEN_PASSWORD_APPEAR] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LOCKSCREEN_PASSWORD_APPEAR;
+        CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_LOCKSCREEN_PATTERN_APPEAR] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LOCKSCREEN_PATTERN_APPEAR;
+        CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_LOCKSCREEN_PIN_APPEAR] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LOCKSCREEN_PIN_APPEAR;
+        CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_LOCKSCREEN_PASSWORD_DISAPPEAR] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LOCKSCREEN_PASSWORD_DISAPPEAR;
+        CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_LOCKSCREEN_PATTERN_DISAPPEAR] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LOCKSCREEN_PATTERN_DISAPPEAR;
+        CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_LOCKSCREEN_PIN_DISAPPEAR] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LOCKSCREEN_PIN_DISAPPEAR;
+        CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_LOCKSCREEN_TRANSITION_FROM_AOD] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LOCKSCREEN_TRANSITION_FROM_AOD;
+        CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_LOCKSCREEN_TRANSITION_TO_AOD] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LOCKSCREEN_TRANSITION_TO_AOD;
+        CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_LAUNCHER_OPEN_ALL_APPS] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_OPEN_ALL_APPS;
+        CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_LAUNCHER_ALL_APPS_SCROLL] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_ALL_APPS_SCROLL;
+        CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_LAUNCHER_APP_LAUNCH_FROM_WIDGET] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_APP_LAUNCH_FROM_WIDGET;
+        CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_SETTINGS_PAGE_SCROLL] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SETTINGS_PAGE_SCROLL;
+        CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_LOCKSCREEN_UNLOCK_ANIMATION] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LOCKSCREEN_UNLOCK_ANIMATION;
+        CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_SHADE_APP_LAUNCH_FROM_HISTORY_BUTTON] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_APP_LAUNCH_FROM_HISTORY_BUTTON;
+        CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_SHADE_APP_LAUNCH_FROM_MEDIA_PLAYER] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_APP_LAUNCH_FROM_MEDIA_PLAYER;
+        CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_SHADE_APP_LAUNCH_FROM_QS_TILE] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_APP_LAUNCH_FROM_QS_TILE;
+        CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_SHADE_APP_LAUNCH_FROM_SETTINGS_BUTTON] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_APP_LAUNCH_FROM_SETTINGS_BUTTON;
+        CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_STATUS_BAR_APP_LAUNCH_FROM_CALL_CHIP] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__STATUS_BAR_APP_LAUNCH_FROM_CALL_CHIP;
+        CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_PIP_TRANSITION] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__PIP_TRANSITION;
+        CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_WALLPAPER_TRANSITION] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__WALLPAPER_TRANSITION;
+        CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_USER_SWITCH] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__USER_SWITCH;
+        CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_SPLASHSCREEN_AVD] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SPLASHSCREEN_AVD;
+        CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_SPLASHSCREEN_EXIT_ANIM] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SPLASHSCREEN_EXIT_ANIM;
+        CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_SCREEN_OFF] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SCREEN_OFF;
+        CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_SCREEN_OFF_SHOW_AOD] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SCREEN_OFF_SHOW_AOD;
+        CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_ONE_HANDED_ENTER_TRANSITION] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__ONE_HANDED_ENTER_TRANSITION;
+        CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_ONE_HANDED_EXIT_TRANSITION] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__ONE_HANDED_EXIT_TRANSITION;
+        CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_UNFOLD_ANIM] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__UNFOLD_ANIM;
+        CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_SUW_LOADING_TO_SHOW_INFO_WITH_ACTIONS] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SUW_LOADING_TO_SHOW_INFO_WITH_ACTIONS;
+        CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_SUW_SHOW_FUNCTION_SCREEN_WITH_ACTIONS] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SUW_SHOW_FUNCTION_SCREEN_WITH_ACTIONS;
+        CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_SUW_LOADING_TO_NEXT_FLOW] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SUW_LOADING_TO_NEXT_FLOW;
+        CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_SUW_LOADING_SCREEN_FOR_STATUS] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SUW_LOADING_SCREEN_FOR_STATUS;
+        CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_SPLIT_SCREEN_ENTER] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SPLIT_SCREEN_ENTER;
+        CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_SPLIT_SCREEN_EXIT] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SPLIT_SCREEN_EXIT;
+        CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_LOCKSCREEN_LAUNCH_CAMERA] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LOCKSCREEN_LAUNCH_CAMERA;
+        CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_SPLIT_SCREEN_RESIZE] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SPLIT_SCREEN_RESIZE;
+        CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_SETTINGS_SLIDER] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SETTINGS_SLIDER;
+        CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_TAKE_SCREENSHOT] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__TAKE_SCREENSHOT;
+        CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_VOLUME_CONTROL] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__VOLUME_CONTROL;
+        CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_BIOMETRIC_PROMPT_TRANSITION] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__BIOMETRIC_PROMPT_TRANSITION;
+        CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_SETTINGS_TOGGLE] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SETTINGS_TOGGLE;
+        CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_SHADE_DIALOG_OPEN] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_DIALOG_OPEN;
+        CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_USER_DIALOG_OPEN] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__USER_DIALOG_OPEN;
+        CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_TASKBAR_EXPAND] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__TASKBAR_EXPAND;
+        CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_TASKBAR_COLLAPSE] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__TASKBAR_COLLAPSE;
+        CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_SHADE_CLEAR_ALL] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_CLEAR_ALL;
+        CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_LAUNCHER_UNLOCK_ENTRANCE_ANIMATION] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_UNLOCK_ENTRANCE_ANIMATION;
+        CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_LOCKSCREEN_OCCLUSION] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LOCKSCREEN_OCCLUSION;
+        CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_RECENTS_SCROLLING] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__RECENTS_SCROLLING;
+        CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_LAUNCHER_APP_SWIPE_TO_RECENTS] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_APP_SWIPE_TO_RECENTS;
+        CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_LAUNCHER_CLOSE_ALL_APPS_SWIPE] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_CLOSE_ALL_APPS_SWIPE;
+        CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_LAUNCHER_CLOSE_ALL_APPS_TO_HOME] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_CLOSE_ALL_APPS_TO_HOME;
+        CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_LOCKSCREEN_CLOCK_MOVE_ANIMATION] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LOCKSCREEN_CLOCK_MOVE_ANIMATION;
+        CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_LAUNCHER_OPEN_SEARCH_RESULT] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_OPEN_SEARCH_RESULT;
+        CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_LAUNCHER_APP_CLOSE_TO_HOME_FALLBACK] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_APP_CLOSE_TO_HOME_FALLBACK;
+        CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_IME_INSETS_SHOW_ANIMATION] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__IME_INSETS_SHOW_ANIMATION;
+        CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_IME_INSETS_HIDE_ANIMATION] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__IME_INSETS_HIDE_ANIMATION;
+        CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_SPLIT_SCREEN_DOUBLE_TAP_DIVIDER] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SPLIT_SCREEN_DOUBLE_TAP_DIVIDER;
+        CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_LAUNCHER_UNFOLD_ANIM] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_UNFOLD_ANIM;
+        CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_PREDICTIVE_BACK_CROSS_ACTIVITY] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__PREDICTIVE_BACK_CROSS_ACTIVITY;
+        CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_PREDICTIVE_BACK_CROSS_TASK] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__PREDICTIVE_BACK_CROSS_TASK;
+        CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_PREDICTIVE_BACK_HOME] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__PREDICTIVE_BACK_HOME;
+    }
+
+    private Cuj() {
+    }
+
+    /**
+     * A helper method to translate CUJ type to CUJ name.
+     *
+     * @param cujType the cuj type defined in this file
+     * @return the name of the cuj type
+     */
+    public static String getNameOfCuj(int cujType) {
+        // Please note:
+        // 1. The length of the returned string shouldn't exceed MAX_LENGTH_OF_CUJ_NAME.
+        // 2. The returned string should be the same with the name defined in atoms.proto.
+        switch (cujType) {
+            case CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE:
+                return "NOTIFICATION_SHADE_EXPAND_COLLAPSE";
+            case CUJ_NOTIFICATION_SHADE_SCROLL_FLING:
+                return "NOTIFICATION_SHADE_SCROLL_FLING";
+            case CUJ_NOTIFICATION_SHADE_ROW_EXPAND:
+                return "NOTIFICATION_SHADE_ROW_EXPAND";
+            case CUJ_NOTIFICATION_SHADE_ROW_SWIPE:
+                return "NOTIFICATION_SHADE_ROW_SWIPE";
+            case CUJ_NOTIFICATION_SHADE_QS_EXPAND_COLLAPSE:
+                return "NOTIFICATION_SHADE_QS_EXPAND_COLLAPSE";
+            case CUJ_NOTIFICATION_SHADE_QS_SCROLL_SWIPE:
+                return "NOTIFICATION_SHADE_QS_SCROLL_SWIPE";
+            case CUJ_LAUNCHER_APP_LAUNCH_FROM_RECENTS:
+                return "LAUNCHER_APP_LAUNCH_FROM_RECENTS";
+            case CUJ_LAUNCHER_APP_LAUNCH_FROM_ICON:
+                return "LAUNCHER_APP_LAUNCH_FROM_ICON";
+            case CUJ_LAUNCHER_APP_CLOSE_TO_HOME:
+                return "LAUNCHER_APP_CLOSE_TO_HOME";
+            case CUJ_LAUNCHER_APP_CLOSE_TO_PIP:
+                return "LAUNCHER_APP_CLOSE_TO_PIP";
+            case CUJ_LAUNCHER_QUICK_SWITCH:
+                return "LAUNCHER_QUICK_SWITCH";
+            case CUJ_NOTIFICATION_HEADS_UP_APPEAR:
+                return "NOTIFICATION_HEADS_UP_APPEAR";
+            case CUJ_NOTIFICATION_HEADS_UP_DISAPPEAR:
+                return "NOTIFICATION_HEADS_UP_DISAPPEAR";
+            case CUJ_NOTIFICATION_ADD:
+                return "NOTIFICATION_ADD";
+            case CUJ_NOTIFICATION_REMOVE:
+                return "NOTIFICATION_REMOVE";
+            case CUJ_NOTIFICATION_APP_START:
+                return "NOTIFICATION_APP_START";
+            case CUJ_LOCKSCREEN_PASSWORD_APPEAR:
+                return "LOCKSCREEN_PASSWORD_APPEAR";
+            case CUJ_LOCKSCREEN_PATTERN_APPEAR:
+                return "LOCKSCREEN_PATTERN_APPEAR";
+            case CUJ_LOCKSCREEN_PIN_APPEAR:
+                return "LOCKSCREEN_PIN_APPEAR";
+            case CUJ_LOCKSCREEN_PASSWORD_DISAPPEAR:
+                return "LOCKSCREEN_PASSWORD_DISAPPEAR";
+            case CUJ_LOCKSCREEN_PATTERN_DISAPPEAR:
+                return "LOCKSCREEN_PATTERN_DISAPPEAR";
+            case CUJ_LOCKSCREEN_PIN_DISAPPEAR:
+                return "LOCKSCREEN_PIN_DISAPPEAR";
+            case CUJ_LOCKSCREEN_TRANSITION_FROM_AOD:
+                return "LOCKSCREEN_TRANSITION_FROM_AOD";
+            case CUJ_LOCKSCREEN_TRANSITION_TO_AOD:
+                return "LOCKSCREEN_TRANSITION_TO_AOD";
+            case CUJ_LAUNCHER_OPEN_ALL_APPS :
+                return "LAUNCHER_OPEN_ALL_APPS";
+            case CUJ_LAUNCHER_ALL_APPS_SCROLL:
+                return "LAUNCHER_ALL_APPS_SCROLL";
+            case CUJ_LAUNCHER_APP_LAUNCH_FROM_WIDGET:
+                return "LAUNCHER_APP_LAUNCH_FROM_WIDGET";
+            case CUJ_SETTINGS_PAGE_SCROLL:
+                return "SETTINGS_PAGE_SCROLL";
+            case CUJ_LOCKSCREEN_UNLOCK_ANIMATION:
+                return "LOCKSCREEN_UNLOCK_ANIMATION";
+            case CUJ_SHADE_APP_LAUNCH_FROM_HISTORY_BUTTON:
+                return "SHADE_APP_LAUNCH_FROM_HISTORY_BUTTON";
+            case CUJ_SHADE_APP_LAUNCH_FROM_MEDIA_PLAYER:
+                return "SHADE_APP_LAUNCH_FROM_MEDIA_PLAYER";
+            case CUJ_SHADE_APP_LAUNCH_FROM_QS_TILE:
+                return "SHADE_APP_LAUNCH_FROM_QS_TILE";
+            case CUJ_SHADE_APP_LAUNCH_FROM_SETTINGS_BUTTON:
+                return "SHADE_APP_LAUNCH_FROM_SETTINGS_BUTTON";
+            case CUJ_STATUS_BAR_APP_LAUNCH_FROM_CALL_CHIP:
+                return "STATUS_BAR_APP_LAUNCH_FROM_CALL_CHIP";
+            case CUJ_PIP_TRANSITION:
+                return "PIP_TRANSITION";
+            case CUJ_WALLPAPER_TRANSITION:
+                return "WALLPAPER_TRANSITION";
+            case CUJ_USER_SWITCH:
+                return "USER_SWITCH";
+            case CUJ_SPLASHSCREEN_AVD:
+                return "SPLASHSCREEN_AVD";
+            case CUJ_SPLASHSCREEN_EXIT_ANIM:
+                return "SPLASHSCREEN_EXIT_ANIM";
+            case CUJ_SCREEN_OFF:
+                return "SCREEN_OFF";
+            case CUJ_SCREEN_OFF_SHOW_AOD:
+                return "SCREEN_OFF_SHOW_AOD";
+            case CUJ_ONE_HANDED_ENTER_TRANSITION:
+                return "ONE_HANDED_ENTER_TRANSITION";
+            case CUJ_ONE_HANDED_EXIT_TRANSITION:
+                return "ONE_HANDED_EXIT_TRANSITION";
+            case CUJ_UNFOLD_ANIM:
+                return "UNFOLD_ANIM";
+            case CUJ_SUW_LOADING_TO_SHOW_INFO_WITH_ACTIONS:
+                return "SUW_LOADING_TO_SHOW_INFO_WITH_ACTIONS";
+            case CUJ_SUW_SHOW_FUNCTION_SCREEN_WITH_ACTIONS:
+                return "SUW_SHOW_FUNCTION_SCREEN_WITH_ACTIONS";
+            case CUJ_SUW_LOADING_TO_NEXT_FLOW:
+                return "SUW_LOADING_TO_NEXT_FLOW";
+            case CUJ_SUW_LOADING_SCREEN_FOR_STATUS:
+                return "SUW_LOADING_SCREEN_FOR_STATUS";
+            case CUJ_SPLIT_SCREEN_ENTER:
+                return "SPLIT_SCREEN_ENTER";
+            case CUJ_SPLIT_SCREEN_EXIT:
+                return "SPLIT_SCREEN_EXIT";
+            case CUJ_LOCKSCREEN_LAUNCH_CAMERA:
+                return "LOCKSCREEN_LAUNCH_CAMERA";
+            case CUJ_SPLIT_SCREEN_RESIZE:
+                return "SPLIT_SCREEN_RESIZE";
+            case CUJ_SETTINGS_SLIDER:
+                return "SETTINGS_SLIDER";
+            case CUJ_TAKE_SCREENSHOT:
+                return "TAKE_SCREENSHOT";
+            case CUJ_VOLUME_CONTROL:
+                return "VOLUME_CONTROL";
+            case CUJ_BIOMETRIC_PROMPT_TRANSITION:
+                return "BIOMETRIC_PROMPT_TRANSITION";
+            case CUJ_SETTINGS_TOGGLE:
+                return "SETTINGS_TOGGLE";
+            case CUJ_SHADE_DIALOG_OPEN:
+                return "SHADE_DIALOG_OPEN";
+            case CUJ_USER_DIALOG_OPEN:
+                return "USER_DIALOG_OPEN";
+            case CUJ_TASKBAR_EXPAND:
+                return "TASKBAR_EXPAND";
+            case CUJ_TASKBAR_COLLAPSE:
+                return "TASKBAR_COLLAPSE";
+            case CUJ_SHADE_CLEAR_ALL:
+                return "SHADE_CLEAR_ALL";
+            case CUJ_LAUNCHER_UNLOCK_ENTRANCE_ANIMATION:
+                return "LAUNCHER_UNLOCK_ENTRANCE_ANIMATION";
+            case CUJ_LOCKSCREEN_OCCLUSION:
+                return "LOCKSCREEN_OCCLUSION";
+            case CUJ_RECENTS_SCROLLING:
+                return "RECENTS_SCROLLING";
+            case CUJ_LAUNCHER_APP_SWIPE_TO_RECENTS:
+                return "LAUNCHER_APP_SWIPE_TO_RECENTS";
+            case CUJ_LAUNCHER_CLOSE_ALL_APPS_SWIPE:
+                return "LAUNCHER_CLOSE_ALL_APPS_SWIPE";
+            case CUJ_LAUNCHER_CLOSE_ALL_APPS_TO_HOME:
+                return "LAUNCHER_CLOSE_ALL_APPS_TO_HOME";
+            case CUJ_LOCKSCREEN_CLOCK_MOVE_ANIMATION:
+                return "LOCKSCREEN_CLOCK_MOVE_ANIMATION";
+            case CUJ_LAUNCHER_OPEN_SEARCH_RESULT:
+                return "LAUNCHER_OPEN_SEARCH_RESULT";
+            case CUJ_LAUNCHER_APP_CLOSE_TO_HOME_FALLBACK:
+                return "LAUNCHER_APP_CLOSE_TO_HOME_FALLBACK";
+            case CUJ_IME_INSETS_SHOW_ANIMATION:
+                return "IME_INSETS_SHOW_ANIMATION";
+            case CUJ_IME_INSETS_HIDE_ANIMATION:
+                return "IME_INSETS_HIDE_ANIMATION";
+            case CUJ_SPLIT_SCREEN_DOUBLE_TAP_DIVIDER:
+                return "SPLIT_SCREEN_DOUBLE_TAP_DIVIDER";
+            case CUJ_LAUNCHER_UNFOLD_ANIM:
+                return "LAUNCHER_UNFOLD_ANIM";
+            case CUJ_PREDICTIVE_BACK_CROSS_ACTIVITY:
+                return "PREDICTIVE_BACK_CROSS_ACTIVITY";
+            case CUJ_PREDICTIVE_BACK_CROSS_TASK:
+                return "PREDICTIVE_BACK_CROSS_TASK";
+            case CUJ_PREDICTIVE_BACK_HOME:
+                return "PREDICTIVE_BACK_HOME";
+        }
+        return "UNKNOWN";
+    }
+
+    public static int getStatsdInteractionType(@CujType int cujType) {
+        return CUJ_TO_STATSD_INTERACTION_TYPE[cujType];
+    }
+
+    /** Returns whether the measurements for the given CUJ should be written to statsd. */
+    public static boolean logToStatsd(@CujType int cujType) {
+        return getStatsdInteractionType(cujType) != NO_STATSD_LOGGING;
+    }
+
+    /**
+     * A helper method to translate interaction type to CUJ name.
+     *
+     * @param interactionType the interaction type defined in AtomsProto.java
+     * @return the name of the interaction type
+     */
+    public static String getNameOfInteraction(int interactionType) {
+        // There is an offset amount of 1 between cujType and interactionType.
+        return Cuj.getNameOfCuj(getCujTypeFromInteraction(interactionType));
+    }
+
+    /**
+     * A helper method to translate interaction type to CUJ type.
+     *
+     * @param interactionType the interaction type defined in AtomsProto.java
+     * @return the integer in {@link Cuj.CujType}
+     */
+    private static int getCujTypeFromInteraction(int interactionType) {
+        return interactionType - 1;
+    }
+}
diff --git a/core/java/com/android/internal/jank/FrameTracker.java b/core/java/com/android/internal/jank/FrameTracker.java
index c83452d..86729f7 100644
--- a/core/java/com/android/internal/jank/FrameTracker.java
+++ b/core/java/com/android/internal/jank/FrameTracker.java
@@ -53,7 +53,6 @@
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.jank.DisplayRefreshRate.RefreshRate;
 import com.android.internal.jank.InteractionJankMonitor.Configuration;
-import com.android.internal.jank.InteractionJankMonitor.Session;
 import com.android.internal.util.FrameworkStatsLog;
 
 import java.lang.annotation.Retention;
@@ -67,7 +66,6 @@
 public class FrameTracker extends SurfaceControl.OnJankDataListener
         implements HardwareRendererObserver.OnFrameMetricsAvailableListener {
     private static final String TAG = "FrameTracker";
-    private static final boolean DEBUG = false;
 
     private static final long INVALID_ID = -1;
     public static final int NANOS_IN_MILLISECOND = 1_000_000;
@@ -93,20 +91,19 @@
             REASON_CANCEL_NORMAL,
             REASON_CANCEL_NOT_BEGUN,
             REASON_CANCEL_SAME_VSYNC,
+            REASON_CANCEL_TIMEOUT,
     })
     @Retention(RetentionPolicy.SOURCE)
     public @interface Reasons {
     }
 
-    @VisibleForTesting
-    public final InteractionJankMonitor mMonitor;
     private final HardwareRendererObserver mObserver;
     private final int mTraceThresholdMissedFrames;
     private final int mTraceThresholdFrameTimeMillis;
     private final ThreadedRendererWrapper mRendererWrapper;
     private final FrameMetricsWrapper mMetricsWrapper;
     private final SparseArray<JankInfo> mJankInfos = new SparseArray<>();
-    private final Session mSession;
+    private final Configuration mConfig;
     private final ViewRootWrapper mViewRoot;
     private final SurfaceControlWrapper mSurfaceControlWrapper;
     private final int mDisplayId;
@@ -197,19 +194,18 @@
         }
     }
 
-    public FrameTracker(@NonNull InteractionJankMonitor monitor, @NonNull Session session,
-            @NonNull Handler handler, @Nullable ThreadedRendererWrapper renderer,
+    public FrameTracker(@NonNull Configuration config,
+            @Nullable ThreadedRendererWrapper renderer,
             @Nullable ViewRootWrapper viewRootWrapper,
             @NonNull SurfaceControlWrapper surfaceControlWrapper,
             @NonNull ChoreographerWrapper choreographer,
             @Nullable FrameMetricsWrapper metrics,
             @NonNull StatsLogWrapper statsLog,
             int traceThresholdMissedFrames, int traceThresholdFrameTimeMillis,
-            @Nullable FrameTrackerListener listener, @NonNull Configuration config) {
-        mMonitor = monitor;
+            @Nullable FrameTrackerListener listener) {
         mSurfaceOnly = config.isSurfaceOnly();
-        mSession = session;
-        mHandler = handler;
+        mConfig = config;
+        mHandler = config.getHandler();
         mChoreographer = choreographer;
         mSurfaceControlWrapper = surfaceControlWrapper;
         mStatsLog = statsLog;
@@ -222,7 +218,7 @@
         mObserver = mSurfaceOnly
                 ? null
                 : new HardwareRendererObserver(this, mMetricsWrapper.getTiming(),
-                        handler, /* waitForPresentTime= */ false);
+                        mHandler, /* waitForPresentTime= */ false);
 
         mTraceThresholdMissedFrames = traceThresholdMissedFrames;
         mTraceThresholdFrameTimeMillis = traceThresholdFrameTimeMillis;
@@ -242,7 +238,7 @@
             mSurfaceChangedCallback = new ViewRootImpl.SurfaceChangedCallback() {
                 @Override
                 public void surfaceCreated(SurfaceControl.Transaction t) {
-                    getHandler().runWithScissors(() -> {
+                    mHandler.runWithScissors(() -> {
                         if (mSurfaceControl == null) {
                             mSurfaceControl = mViewRoot.getSurfaceControl();
                             if (mBeginVsyncId != INVALID_ID) {
@@ -262,13 +258,7 @@
 
                     // Wait a while to give the system a chance for the remaining
                     // frames to arrive, then force finish the session.
-                    getHandler().postDelayed(() -> {
-                        if (DEBUG) {
-                            Log.d(TAG, "surfaceDestroyed: " + mSession.getName()
-                                    + ", finalized=" + mMetricsFinalized
-                                    + ", info=" + mJankInfos.size()
-                                    + ", vsync=" + mBeginVsyncId);
-                        }
+                    mHandler.postDelayed(() -> {
                         if (!mMetricsFinalized) {
                             end(REASON_END_SURFACE_DESTROYED);
                             finish();
@@ -282,11 +272,6 @@
         }
     }
 
-    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
-    public Handler getHandler() {
-        return mHandler;
-    }
-
     /**
      * Begin a trace session of the CUJ.
      */
@@ -300,10 +285,6 @@
             mBeginVsyncId = mDeferMonitoring ? currentVsync + 1 : currentVsync;
         }
         if (mSurfaceControl != null) {
-            if (DEBUG) {
-                Log.d(TAG, "begin: " + mSession.getName() + ", begin=" + mBeginVsyncId
-                        + ", defer=" + mDeferMonitoring + ", current=" + currentVsync);
-            }
             if (mDeferMonitoring && currentVsync < mBeginVsyncId) {
                 markEvent("FT#deferMonitoring", 0);
                 // Normal case, we begin the instrument from the very beginning,
@@ -314,11 +295,6 @@
                 // there is no need to skip the frame where the begin invocation happens.
                 beginInternal();
             }
-        } else {
-            if (DEBUG) {
-                Log.d(TAG, "begin: defer beginning since the surface is not ready for CUJ="
-                        + mSession.getName());
-            }
         }
     }
 
@@ -336,8 +312,8 @@
             return;
         }
         mTracingStarted = true;
-        Trace.asyncTraceForTrackBegin(Trace.TRACE_TAG_APP, mSession.getName(), mSession.getName(),
-                (int) mBeginVsyncId);
+        String name = mConfig.getSessionName();
+        Trace.asyncTraceForTrackBegin(Trace.TRACE_TAG_APP, name, name, (int) mBeginVsyncId);
         markEvent("FT#beginVsync", mBeginVsyncId);
         markEvent("FT#layerId", mSurfaceControl.getLayerId());
         mSurfaceControlWrapper.addJankStatsListener(this, mSurfaceControl);
@@ -361,15 +337,10 @@
         } else if (mEndVsyncId <= mBeginVsyncId) {
             return cancel(REASON_CANCEL_SAME_VSYNC);
         } else {
-            if (DEBUG) {
-                Log.d(TAG, "end: " + mSession.getName()
-                        + ", end=" + mEndVsyncId + ", reason=" + reason);
-            }
+            final String name = mConfig.getSessionName();
             markEvent("FT#end", reason);
             markEvent("FT#endVsync", mEndVsyncId);
-            Trace.asyncTraceForTrackEnd(Trace.TRACE_TAG_APP, mSession.getName(),
-                    (int) mBeginVsyncId);
-            mSession.setReason(reason);
+            Trace.asyncTraceForTrackEnd(Trace.TRACE_TAG_APP, name, (int) mBeginVsyncId);
 
             // We don't remove observer here,
             // will remove it when all the frame metrics in this duration are called back.
@@ -395,16 +366,16 @@
                         mFlushAttempts++;
                     } else {
                         mWaitForFinishTimedOut = () -> {
-                            Log.e(TAG, "force finish cuj, time out: " + mSession.getName());
+                            Log.e(TAG, "force finish cuj, time out: " + name);
                             finish();
                         };
                         delay = TimeUnit.SECONDS.toMillis(10);
                     }
-                    getHandler().postDelayed(mWaitForFinishTimedOut, delay);
+                    mHandler.postDelayed(mWaitForFinishTimedOut, delay);
                 }
             };
-            getHandler().postDelayed(mWaitForFinishTimedOut, FLUSH_DELAY_MILLISECOND);
-            notifyCujEvent(ACTION_SESSION_END);
+            mHandler.postDelayed(mWaitForFinishTimedOut, FLUSH_DELAY_MILLISECOND);
+            notifyCujEvent(ACTION_SESSION_END, reason);
             return true;
         }
     }
@@ -421,22 +392,16 @@
         markEvent("FT#cancel", reason);
         // We don't need to end the trace section if it has never begun.
         if (mTracingStarted) {
-            Trace.asyncTraceForTrackEnd(Trace.TRACE_TAG_APP, mSession.getName(),
-                    (int) mBeginVsyncId);
+            Trace.asyncTraceForTrackEnd(
+                    Trace.TRACE_TAG_APP, mConfig.getSessionName(), (int) mBeginVsyncId);
         }
 
         // Always remove the observers in cancel call to avoid leakage.
         removeObservers();
 
-        if (DEBUG) {
-            Log.d(TAG, "cancel: " + mSession.getName() + ", begin=" + mBeginVsyncId
-                    + ", end=" + mEndVsyncId + ", reason=" + reason);
-        }
-
-        mSession.setReason(reason);
         // Notify the listener the session has been cancelled.
         // We don't notify the listeners if the session never begun.
-        notifyCujEvent(ACTION_SESSION_CANCEL);
+        notifyCujEvent(ACTION_SESSION_CANCEL, reason);
         return true;
     }
 
@@ -455,13 +420,13 @@
                         "The length of the trace event description <%s> exceeds %d",
                         event, MAX_LENGTH_EVENT_DESC));
             }
-            Trace.instantForTrack(Trace.TRACE_TAG_APP, mSession.getName(), event);
+            Trace.instantForTrack(Trace.TRACE_TAG_APP, mConfig.getSessionName(), event);
         }
     }
 
-    private void notifyCujEvent(String action) {
+    private void notifyCujEvent(String action, @Reasons int reason) {
         if (mListener == null) return;
-        mListener.onCujEvents(mSession, action);
+        mListener.onCujEvents(this, action, reason);
     }
 
     @Override
@@ -496,7 +461,7 @@
      */
     @VisibleForTesting
     public void postCallback(Runnable callback) {
-        getHandler().post(callback);
+        mHandler.post(callback);
     }
 
     @Nullable
@@ -587,13 +552,15 @@
         if (mMetricsFinalized || mCancelled) return;
         mMetricsFinalized = true;
 
-        getHandler().removeCallbacks(mWaitForFinishTimedOut);
+        mHandler.removeCallbacks(mWaitForFinishTimedOut);
         mWaitForFinishTimedOut = null;
         markEvent("FT#finish", mJankInfos.size());
 
         // The tracing has been ended, remove the observer, see if need to trigger perfetto.
         removeObservers();
 
+        final String name = mConfig.getSessionName();
+
         int totalFramesCount = 0;
         long maxFrameTimeNanos = 0;
         int missedFramesCount = 0;
@@ -616,7 +583,7 @@
                 totalFramesCount++;
                 boolean missedFrame = false;
                 if ((info.jankType & JANK_APP_DEADLINE_MISSED) != 0) {
-                    Log.w(TAG, "Missed App frame:" + info + ", CUJ=" + mSession.getName());
+                    Log.w(TAG, "Missed App frame:" + info + ", CUJ=" + name);
                     missedAppFramesCount++;
                     missedFrame = true;
                 }
@@ -625,7 +592,7 @@
                         || (info.jankType & JANK_SURFACEFLINGER_GPU_DEADLINE_MISSED) != 0
                         || (info.jankType & SURFACE_FLINGER_SCHEDULING) != 0
                         || (info.jankType & PREDICTION_ERROR) != 0) {
-                    Log.w(TAG, "Missed SF frame:" + info + ", CUJ=" + mSession.getName());
+                    Log.w(TAG, "Missed SF frame:" + info + ", CUJ=" + name);
                     missedSfFramesCount++;
                     missedFrame = true;
                 }
@@ -646,7 +613,7 @@
                 if (!mSurfaceOnly && !info.hwuiCallbackFired) {
                     markEvent("FT#MissedHWUICallback", info.frameVsyncId);
                     Log.w(TAG, "Missing HWUI jank callback for vsyncId: " + info.frameVsyncId
-                            + ", CUJ=" + mSession.getName());
+                            + ", CUJ=" + name);
                 }
             }
             if (!mSurfaceOnly && info.hwuiCallbackFired) {
@@ -654,7 +621,7 @@
                 if (!info.surfaceControlCallbackFired) {
                     markEvent("FT#MissedSFCallback", info.frameVsyncId);
                     Log.w(TAG, "Missing SF jank callback for vsyncId: " + info.frameVsyncId
-                            + ", CUJ=" + mSession.getName());
+                            + ", CUJ=" + name);
                 }
             }
         }
@@ -662,29 +629,26 @@
                 maxSuccessiveMissedFramesCount, successiveMissedFramesCount);
 
         // Log the frame stats as counters to make them easily accessible in traces.
-        Trace.traceCounter(Trace.TRACE_TAG_APP, mSession.getName() + "#missedFrames",
-                missedFramesCount);
-        Trace.traceCounter(Trace.TRACE_TAG_APP, mSession.getName() + "#missedAppFrames",
-                missedAppFramesCount);
-        Trace.traceCounter(Trace.TRACE_TAG_APP, mSession.getName() + "#missedSfFrames",
-                missedSfFramesCount);
-        Trace.traceCounter(Trace.TRACE_TAG_APP, mSession.getName() + "#totalFrames",
-                totalFramesCount);
-        Trace.traceCounter(Trace.TRACE_TAG_APP, mSession.getName() + "#maxFrameTimeMillis",
+        Trace.traceCounter(Trace.TRACE_TAG_APP, name + "#missedFrames", missedFramesCount);
+        Trace.traceCounter(Trace.TRACE_TAG_APP, name + "#missedAppFrames", missedAppFramesCount);
+        Trace.traceCounter(Trace.TRACE_TAG_APP, name + "#missedSfFrames", missedSfFramesCount);
+        Trace.traceCounter(Trace.TRACE_TAG_APP, name + "#totalFrames", totalFramesCount);
+        Trace.traceCounter(Trace.TRACE_TAG_APP, name + "#maxFrameTimeMillis",
                 (int) (maxFrameTimeNanos / NANOS_IN_MILLISECOND));
-        Trace.traceCounter(Trace.TRACE_TAG_APP, mSession.getName() + "#maxSuccessiveMissedFrames",
+        Trace.traceCounter(Trace.TRACE_TAG_APP, name + "#maxSuccessiveMissedFrames",
                 maxSuccessiveMissedFramesCount);
 
         // Trigger perfetto if necessary.
-        if (shouldTriggerPerfetto(missedFramesCount, (int) maxFrameTimeNanos)) {
-            triggerPerfetto();
+        if (mListener != null
+                && shouldTriggerPerfetto(missedFramesCount, (int) maxFrameTimeNanos)) {
+            mListener.triggerPerfetto(mConfig);
         }
-        if (mSession.logToStatsd()) {
+        if (mConfig.logToStatsd()) {
             mStatsLog.write(
                     FrameworkStatsLog.UI_INTERACTION_FRAME_INFO_REPORTED,
                     mDisplayId,
                     refreshRate,
-                    mSession.getStatsdInteractionType(),
+                    mConfig.getStatsdInteractionType(),
                     totalFramesCount,
                     missedFramesCount,
                     maxFrameTimeNanos, /* will be 0 if mSurfaceOnly == true */
@@ -692,16 +656,6 @@
                     missedAppFramesCount,
                     maxSuccessiveMissedFramesCount);
         }
-        if (DEBUG) {
-            Log.i(TAG, "finish: CUJ=" + mSession.getName()
-                    + " (" + mBeginVsyncId + "," + mEndVsyncId + ")"
-                    + " totalFrames=" + totalFramesCount
-                    + " missedAppFrames=" + missedAppFramesCount
-                    + " missedSfFrames=" + missedSfFramesCount
-                    + " missedFrames=" + missedFramesCount
-                    + " maxFrameTimeMillis=" + maxFrameTimeNanos / NANOS_IN_MILLISECOND
-                    + " maxSuccessiveMissedFramesCount=" + maxSuccessiveMissedFramesCount);
-        }
     }
 
     ThreadedRendererWrapper getThreadedRenderer() {
@@ -737,13 +691,6 @@
     }
 
     /**
-     * Trigger the prefetto daemon.
-     */
-    public void triggerPerfetto() {
-        mMonitor.trigger(mSession);
-    }
-
-    /**
      * A wrapper class that we can spy FrameMetrics (a final class) in unit tests.
      */
     public static class FrameMetricsWrapper {
@@ -895,9 +842,17 @@
         /**
          * Notify that the CUJ session was created.
          *
-         * @param session the CUJ session
+         * @param tracker the tracker
          * @param action the specific action
+         * @param reason the reason for the action
          */
-        void onCujEvents(Session session, String action);
+        void onCujEvents(FrameTracker tracker, String action, @Reasons int reason);
+
+        /**
+         * Notify that the Perfetto trace should be triggered.
+         *
+         * @param config the tracker configuration
+         */
+        void triggerPerfetto(Configuration config);
     }
 }
diff --git a/core/java/com/android/internal/jank/InteractionJankMonitor.java b/core/java/com/android/internal/jank/InteractionJankMonitor.java
index e6b036c..8b1879f 100644
--- a/core/java/com/android/internal/jank/InteractionJankMonitor.java
+++ b/core/java/com/android/internal/jank/InteractionJankMonitor.java
@@ -23,89 +23,9 @@
 import static com.android.internal.jank.FrameTracker.REASON_CANCEL_NORMAL;
 import static com.android.internal.jank.FrameTracker.REASON_CANCEL_TIMEOUT;
 import static com.android.internal.jank.FrameTracker.REASON_END_NORMAL;
-import static com.android.internal.jank.FrameTracker.REASON_END_UNKNOWN;
-import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__BIOMETRIC_PROMPT_TRANSITION;
-import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__IME_INSETS_HIDE_ANIMATION;
-import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__IME_INSETS_SHOW_ANIMATION;
-import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_ALL_APPS_SCROLL;
-import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_APP_CLOSE_TO_HOME;
-import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_APP_CLOSE_TO_HOME_FALLBACK;
-import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_APP_CLOSE_TO_PIP;
-import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_APP_LAUNCH_FROM_ICON;
-import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_APP_LAUNCH_FROM_RECENTS;
-import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_APP_LAUNCH_FROM_WIDGET;
-import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_APP_SWIPE_TO_RECENTS;
-import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_CLOSE_ALL_APPS_SWIPE;
-import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_CLOSE_ALL_APPS_TO_HOME;
-import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_OPEN_ALL_APPS;
-import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_OPEN_SEARCH_RESULT;
-import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_QUICK_SWITCH;
-import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_UNFOLD_ANIM;
-import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_UNLOCK_ENTRANCE_ANIMATION;
-import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LOCKSCREEN_CLOCK_MOVE_ANIMATION;
-import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LOCKSCREEN_LAUNCH_CAMERA;
-import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LOCKSCREEN_OCCLUSION;
-import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LOCKSCREEN_PASSWORD_APPEAR;
-import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LOCKSCREEN_PASSWORD_DISAPPEAR;
-import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LOCKSCREEN_PATTERN_APPEAR;
-import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LOCKSCREEN_PATTERN_DISAPPEAR;
-import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LOCKSCREEN_PIN_APPEAR;
-import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LOCKSCREEN_PIN_DISAPPEAR;
-import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LOCKSCREEN_TRANSITION_FROM_AOD;
-import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LOCKSCREEN_TRANSITION_TO_AOD;
-import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LOCKSCREEN_UNLOCK_ANIMATION;
-import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__NOTIFICATION_SHADE_SWIPE;
-import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__ONE_HANDED_ENTER_TRANSITION;
-import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__ONE_HANDED_EXIT_TRANSITION;
-import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__PIP_TRANSITION;
-import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__PREDICTIVE_BACK_CROSS_ACTIVITY;
-import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__PREDICTIVE_BACK_CROSS_TASK;
-import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__PREDICTIVE_BACK_HOME;
-import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__RECENTS_SCROLLING;
-import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SCREEN_OFF;
-import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SCREEN_OFF_SHOW_AOD;
-import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SETTINGS_PAGE_SCROLL;
-import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SETTINGS_SLIDER;
-import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SETTINGS_TOGGLE;
-import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_APP_LAUNCH;
-import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_APP_LAUNCH_FROM_HISTORY_BUTTON;
-import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_APP_LAUNCH_FROM_MEDIA_PLAYER;
-import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_APP_LAUNCH_FROM_QS_TILE;
-import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_APP_LAUNCH_FROM_SETTINGS_BUTTON;
-import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_CLEAR_ALL;
-import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_DIALOG_OPEN;
-import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_HEADS_UP_APPEAR;
-import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_HEADS_UP_DISAPPEAR;
-import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_NOTIFICATION_ADD;
-import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_NOTIFICATION_REMOVE;
-import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_QS_EXPAND_COLLAPSE;
-import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_QS_SCROLL_SWIPE;
-import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_ROW_EXPAND;
-import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_ROW_SWIPE;
-import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_SCROLL_FLING;
-import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SPLASHSCREEN_AVD;
-import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SPLASHSCREEN_EXIT_ANIM;
-import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SPLIT_SCREEN_DOUBLE_TAP_DIVIDER;
-import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SPLIT_SCREEN_ENTER;
-import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SPLIT_SCREEN_EXIT;
-import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SPLIT_SCREEN_RESIZE;
-import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__STATUS_BAR_APP_LAUNCH_FROM_CALL_CHIP;
-import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SUW_LOADING_SCREEN_FOR_STATUS;
-import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SUW_LOADING_TO_NEXT_FLOW;
-import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SUW_LOADING_TO_SHOW_INFO_WITH_ACTIONS;
-import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SUW_SHOW_FUNCTION_SCREEN_WITH_ACTIONS;
-import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__TAKE_SCREENSHOT;
-import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__TASKBAR_COLLAPSE;
-import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__TASKBAR_EXPAND;
-import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__UNFOLD_ANIM;
-import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__USER_DIALOG_OPEN;
-import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__USER_SWITCH;
-import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__VOLUME_CONTROL;
-import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__WALLPAPER_TRANSITION;
 
 import android.Manifest;
 import android.annotation.ColorInt;
-import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.RequiresPermission;
 import android.annotation.UiThread;
@@ -137,34 +57,31 @@
 import com.android.internal.jank.FrameTracker.ViewRootWrapper;
 import com.android.internal.util.PerfettoTrigger;
 
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
 import java.time.Instant;
-import java.util.Locale;
 import java.util.concurrent.ThreadLocalRandom;
 import java.util.concurrent.TimeUnit;
+import java.util.function.Supplier;
 
 /**
- * This class let users to begin and end the always on tracing mechanism.
+ * This class lets users begin and end the always on tracing mechanism.
  *
  * Enabling for local development:
- *
+ *<pre>
  * adb shell device_config put interaction_jank_monitor enabled true
  * adb shell device_config put interaction_jank_monitor sampling_interval 1
- *
+ * </pre>
  * On debuggable builds, an overlay can be used to display the name of the
  * currently running cuj using:
- *
+ * <pre>
  * adb shell device_config put interaction_jank_monitor debug_overlay_enabled true
- *
- * NOTE: The overlay will interfere with metrics, so it should only be used
- * for understanding which UI events correspeond to which CUJs.
+ * </pre>
+ * <b>NOTE</b>: The overlay will interfere with metrics, so it should only be used
+ * for understanding which UI events correspond to which CUJs.
  *
  * @hide
  */
 public class InteractionJankMonitor {
     private static final String TAG = InteractionJankMonitor.class.getSimpleName();
-    private static final boolean DEBUG = false;
     private static final String ACTION_PREFIX = InteractionJankMonitor.class.getCanonicalName();
 
     private static final String DEFAULT_WORKER_NAME = TAG + "-Worker";
@@ -186,218 +103,79 @@
     private static final int DEFAULT_TRACE_THRESHOLD_FRAME_TIME_MILLIS = 64;
     private static final boolean DEFAULT_DEBUG_OVERLAY_ENABLED = false;
 
-    @VisibleForTesting
-    public static final int MAX_LENGTH_OF_CUJ_NAME = 80;
     private static final int MAX_LENGTH_SESSION_NAME = 100;
 
     public static final String ACTION_SESSION_END = ACTION_PREFIX + ".ACTION_SESSION_END";
     public static final String ACTION_SESSION_CANCEL = ACTION_PREFIX + ".ACTION_SESSION_CANCEL";
 
-    // Every value must have a corresponding entry in CUJ_STATSD_INTERACTION_TYPE.
-    public static final int CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE = 0;
-    public static final int CUJ_NOTIFICATION_SHADE_SCROLL_FLING = 2;
-    public static final int CUJ_NOTIFICATION_SHADE_ROW_EXPAND = 3;
-    public static final int CUJ_NOTIFICATION_SHADE_ROW_SWIPE = 4;
-    public static final int CUJ_NOTIFICATION_SHADE_QS_EXPAND_COLLAPSE = 5;
-    public static final int CUJ_NOTIFICATION_SHADE_QS_SCROLL_SWIPE = 6;
-    public static final int CUJ_LAUNCHER_APP_LAUNCH_FROM_RECENTS = 7;
-    public static final int CUJ_LAUNCHER_APP_LAUNCH_FROM_ICON = 8;
-    public static final int CUJ_LAUNCHER_APP_CLOSE_TO_HOME = 9;
-    public static final int CUJ_LAUNCHER_APP_CLOSE_TO_PIP = 10;
-    public static final int CUJ_LAUNCHER_QUICK_SWITCH = 11;
-    public static final int CUJ_NOTIFICATION_HEADS_UP_APPEAR = 12;
-    public static final int CUJ_NOTIFICATION_HEADS_UP_DISAPPEAR = 13;
-    public static final int CUJ_NOTIFICATION_ADD = 14;
-    public static final int CUJ_NOTIFICATION_REMOVE = 15;
-    public static final int CUJ_NOTIFICATION_APP_START = 16;
-    public static final int CUJ_LOCKSCREEN_PASSWORD_APPEAR = 17;
-    public static final int CUJ_LOCKSCREEN_PATTERN_APPEAR = 18;
-    public static final int CUJ_LOCKSCREEN_PIN_APPEAR = 19;
-    public static final int CUJ_LOCKSCREEN_PASSWORD_DISAPPEAR = 20;
-    public static final int CUJ_LOCKSCREEN_PATTERN_DISAPPEAR = 21;
-    public static final int CUJ_LOCKSCREEN_PIN_DISAPPEAR = 22;
-    public static final int CUJ_LOCKSCREEN_TRANSITION_FROM_AOD = 23;
-    public static final int CUJ_LOCKSCREEN_TRANSITION_TO_AOD = 24;
-    public static final int CUJ_LAUNCHER_OPEN_ALL_APPS = 25;
-    public static final int CUJ_LAUNCHER_ALL_APPS_SCROLL = 26;
-    public static final int CUJ_LAUNCHER_APP_LAUNCH_FROM_WIDGET = 27;
-    public static final int CUJ_SETTINGS_PAGE_SCROLL = 28;
-    public static final int CUJ_LOCKSCREEN_UNLOCK_ANIMATION = 29;
-    public static final int CUJ_SHADE_APP_LAUNCH_FROM_HISTORY_BUTTON = 30;
-    public static final int CUJ_SHADE_APP_LAUNCH_FROM_MEDIA_PLAYER = 31;
-    public static final int CUJ_SHADE_APP_LAUNCH_FROM_QS_TILE = 32;
-    public static final int CUJ_SHADE_APP_LAUNCH_FROM_SETTINGS_BUTTON = 33;
-    public static final int CUJ_STATUS_BAR_APP_LAUNCH_FROM_CALL_CHIP = 34;
-    public static final int CUJ_PIP_TRANSITION = 35;
-    public static final int CUJ_WALLPAPER_TRANSITION = 36;
-    public static final int CUJ_USER_SWITCH = 37;
-    public static final int CUJ_SPLASHSCREEN_AVD = 38;
-    public static final int CUJ_SPLASHSCREEN_EXIT_ANIM = 39;
-    public static final int CUJ_SCREEN_OFF = 40;
-    public static final int CUJ_SCREEN_OFF_SHOW_AOD = 41;
-    public static final int CUJ_ONE_HANDED_ENTER_TRANSITION = 42;
-    public static final int CUJ_ONE_HANDED_EXIT_TRANSITION = 43;
-    public static final int CUJ_UNFOLD_ANIM = 44;
-    public static final int CUJ_SUW_LOADING_TO_SHOW_INFO_WITH_ACTIONS = 45;
-    public static final int CUJ_SUW_SHOW_FUNCTION_SCREEN_WITH_ACTIONS = 46;
-    public static final int CUJ_SUW_LOADING_TO_NEXT_FLOW = 47;
-    public static final int CUJ_SUW_LOADING_SCREEN_FOR_STATUS = 48;
-    public static final int CUJ_SPLIT_SCREEN_ENTER = 49;
-    public static final int CUJ_SPLIT_SCREEN_EXIT = 50;
-    public static final int CUJ_LOCKSCREEN_LAUNCH_CAMERA = 51; // reserved.
-    public static final int CUJ_SPLIT_SCREEN_RESIZE = 52;
-    public static final int CUJ_SETTINGS_SLIDER = 53;
-    public static final int CUJ_TAKE_SCREENSHOT = 54;
-    public static final int CUJ_VOLUME_CONTROL = 55;
-    public static final int CUJ_BIOMETRIC_PROMPT_TRANSITION = 56;
-    public static final int CUJ_SETTINGS_TOGGLE = 57;
-    public static final int CUJ_SHADE_DIALOG_OPEN = 58;
-    public static final int CUJ_USER_DIALOG_OPEN = 59;
-    public static final int CUJ_TASKBAR_EXPAND = 60;
-    public static final int CUJ_TASKBAR_COLLAPSE = 61;
-    public static final int CUJ_SHADE_CLEAR_ALL = 62;
-    public static final int CUJ_LAUNCHER_UNLOCK_ENTRANCE_ANIMATION = 63;
-    public static final int CUJ_LOCKSCREEN_OCCLUSION = 64;
-    public static final int CUJ_RECENTS_SCROLLING = 65;
-    public static final int CUJ_LAUNCHER_APP_SWIPE_TO_RECENTS = 66;
-    public static final int CUJ_LAUNCHER_CLOSE_ALL_APPS_SWIPE = 67;
-    public static final int CUJ_LAUNCHER_CLOSE_ALL_APPS_TO_HOME = 68;
-    public static final int CUJ_LOCKSCREEN_CLOCK_MOVE_ANIMATION = 70;
-    public static final int CUJ_LAUNCHER_OPEN_SEARCH_RESULT = 71;
-    // 72 - 77 are reserved for b/281564325.
-
-    /**
-     * In some cases when we do not have any end-target, we play a simple slide-down animation.
-     * eg: Open an app from Overview/Task switcher such that there is no home-screen icon.
-     * eg: Exit the app using back gesture.
-     */
-    public static final int CUJ_LAUNCHER_APP_CLOSE_TO_HOME_FALLBACK = 78;
-    public static final int CUJ_IME_INSETS_SHOW_ANIMATION = 80;
-    public static final int CUJ_IME_INSETS_HIDE_ANIMATION = 81;
-
-    public static final int CUJ_SPLIT_SCREEN_DOUBLE_TAP_DIVIDER = 82;
-
-    public static final int CUJ_LAUNCHER_UNFOLD_ANIM = 83;
-
-    public static final int CUJ_PREDICTIVE_BACK_CROSS_ACTIVITY = 84;
-    public static final int CUJ_PREDICTIVE_BACK_CROSS_TASK = 85;
-    public static final int CUJ_PREDICTIVE_BACK_HOME = 86;
-
-    private static final int LAST_CUJ = CUJ_PREDICTIVE_BACK_HOME;
-    private static final int NO_STATSD_LOGGING = -1;
-
-    // Used to convert CujType to InteractionType enum value for statsd logging.
-    // Use NO_STATSD_LOGGING in case the measurement for a given CUJ should not be logged to statsd.
-    @VisibleForTesting
-    public static final int[] CUJ_TO_STATSD_INTERACTION_TYPE = new int[LAST_CUJ + 1];
-
-    static {
-        CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__NOTIFICATION_SHADE_SWIPE;
-        CUJ_TO_STATSD_INTERACTION_TYPE[1] = NO_STATSD_LOGGING; // This is deprecated.
-        CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_NOTIFICATION_SHADE_SCROLL_FLING] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_SCROLL_FLING;
-        CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_NOTIFICATION_SHADE_ROW_EXPAND] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_ROW_EXPAND;
-        CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_NOTIFICATION_SHADE_ROW_SWIPE] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_ROW_SWIPE;
-        CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_NOTIFICATION_SHADE_QS_EXPAND_COLLAPSE] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_QS_EXPAND_COLLAPSE;
-        CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_NOTIFICATION_SHADE_QS_SCROLL_SWIPE] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_QS_SCROLL_SWIPE;
-        CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_LAUNCHER_APP_LAUNCH_FROM_RECENTS] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_APP_LAUNCH_FROM_RECENTS;
-        CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_LAUNCHER_APP_LAUNCH_FROM_ICON] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_APP_LAUNCH_FROM_ICON;
-        CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_LAUNCHER_APP_CLOSE_TO_HOME] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_APP_CLOSE_TO_HOME;
-        CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_LAUNCHER_APP_CLOSE_TO_PIP] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_APP_CLOSE_TO_PIP;
-        CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_LAUNCHER_QUICK_SWITCH] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_QUICK_SWITCH;
-        CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_NOTIFICATION_HEADS_UP_APPEAR] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_HEADS_UP_APPEAR;
-        CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_NOTIFICATION_HEADS_UP_DISAPPEAR] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_HEADS_UP_DISAPPEAR;
-        CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_NOTIFICATION_ADD] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_NOTIFICATION_ADD;
-        CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_NOTIFICATION_REMOVE] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_NOTIFICATION_REMOVE;
-        CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_NOTIFICATION_APP_START] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_APP_LAUNCH;
-        CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_LOCKSCREEN_PASSWORD_APPEAR] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LOCKSCREEN_PASSWORD_APPEAR;
-        CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_LOCKSCREEN_PATTERN_APPEAR] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LOCKSCREEN_PATTERN_APPEAR;
-        CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_LOCKSCREEN_PIN_APPEAR] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LOCKSCREEN_PIN_APPEAR;
-        CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_LOCKSCREEN_PASSWORD_DISAPPEAR] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LOCKSCREEN_PASSWORD_DISAPPEAR;
-        CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_LOCKSCREEN_PATTERN_DISAPPEAR] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LOCKSCREEN_PATTERN_DISAPPEAR;
-        CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_LOCKSCREEN_PIN_DISAPPEAR] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LOCKSCREEN_PIN_DISAPPEAR;
-        CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_LOCKSCREEN_TRANSITION_FROM_AOD] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LOCKSCREEN_TRANSITION_FROM_AOD;
-        CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_LOCKSCREEN_TRANSITION_TO_AOD] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LOCKSCREEN_TRANSITION_TO_AOD;
-        CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_LAUNCHER_OPEN_ALL_APPS] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_OPEN_ALL_APPS;
-        CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_LAUNCHER_ALL_APPS_SCROLL] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_ALL_APPS_SCROLL;
-        CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_LAUNCHER_APP_LAUNCH_FROM_WIDGET] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_APP_LAUNCH_FROM_WIDGET;
-        CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_SETTINGS_PAGE_SCROLL] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SETTINGS_PAGE_SCROLL;
-        CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_LOCKSCREEN_UNLOCK_ANIMATION] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LOCKSCREEN_UNLOCK_ANIMATION;
-        CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_SHADE_APP_LAUNCH_FROM_HISTORY_BUTTON] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_APP_LAUNCH_FROM_HISTORY_BUTTON;
-        CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_SHADE_APP_LAUNCH_FROM_MEDIA_PLAYER] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_APP_LAUNCH_FROM_MEDIA_PLAYER;
-        CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_SHADE_APP_LAUNCH_FROM_QS_TILE] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_APP_LAUNCH_FROM_QS_TILE;
-        CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_SHADE_APP_LAUNCH_FROM_SETTINGS_BUTTON] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_APP_LAUNCH_FROM_SETTINGS_BUTTON;
-        CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_STATUS_BAR_APP_LAUNCH_FROM_CALL_CHIP] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__STATUS_BAR_APP_LAUNCH_FROM_CALL_CHIP;
-        CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_PIP_TRANSITION] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__PIP_TRANSITION;
-        CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_WALLPAPER_TRANSITION] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__WALLPAPER_TRANSITION;
-        CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_USER_SWITCH] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__USER_SWITCH;
-        CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_SPLASHSCREEN_AVD] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SPLASHSCREEN_AVD;
-        CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_SPLASHSCREEN_EXIT_ANIM] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SPLASHSCREEN_EXIT_ANIM;
-        CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_SCREEN_OFF] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SCREEN_OFF;
-        CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_SCREEN_OFF_SHOW_AOD] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SCREEN_OFF_SHOW_AOD;
-        CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_ONE_HANDED_ENTER_TRANSITION] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__ONE_HANDED_ENTER_TRANSITION;
-        CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_ONE_HANDED_EXIT_TRANSITION] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__ONE_HANDED_EXIT_TRANSITION;
-        CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_UNFOLD_ANIM] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__UNFOLD_ANIM;
-        CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_SUW_LOADING_TO_SHOW_INFO_WITH_ACTIONS] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SUW_LOADING_TO_SHOW_INFO_WITH_ACTIONS;
-        CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_SUW_SHOW_FUNCTION_SCREEN_WITH_ACTIONS] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SUW_SHOW_FUNCTION_SCREEN_WITH_ACTIONS;
-        CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_SUW_LOADING_TO_NEXT_FLOW] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SUW_LOADING_TO_NEXT_FLOW;
-        CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_SUW_LOADING_SCREEN_FOR_STATUS] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SUW_LOADING_SCREEN_FOR_STATUS;
-        CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_SPLIT_SCREEN_ENTER] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SPLIT_SCREEN_ENTER;
-        CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_SPLIT_SCREEN_EXIT] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SPLIT_SCREEN_EXIT;
-        CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_LOCKSCREEN_LAUNCH_CAMERA] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LOCKSCREEN_LAUNCH_CAMERA;
-        CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_SPLIT_SCREEN_RESIZE] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SPLIT_SCREEN_RESIZE;
-        CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_SETTINGS_SLIDER] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SETTINGS_SLIDER;
-        CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_TAKE_SCREENSHOT] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__TAKE_SCREENSHOT;
-        CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_VOLUME_CONTROL] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__VOLUME_CONTROL;
-        CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_BIOMETRIC_PROMPT_TRANSITION] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__BIOMETRIC_PROMPT_TRANSITION;
-        CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_SETTINGS_TOGGLE] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SETTINGS_TOGGLE;
-        CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_SHADE_DIALOG_OPEN] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_DIALOG_OPEN;
-        CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_USER_DIALOG_OPEN] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__USER_DIALOG_OPEN;
-        CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_TASKBAR_EXPAND] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__TASKBAR_EXPAND;
-        CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_TASKBAR_COLLAPSE] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__TASKBAR_COLLAPSE;
-        CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_SHADE_CLEAR_ALL] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_CLEAR_ALL;
-        CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_LAUNCHER_UNLOCK_ENTRANCE_ANIMATION] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_UNLOCK_ENTRANCE_ANIMATION;
-        CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_LOCKSCREEN_OCCLUSION] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LOCKSCREEN_OCCLUSION;
-        CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_RECENTS_SCROLLING] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__RECENTS_SCROLLING;
-        CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_LAUNCHER_APP_SWIPE_TO_RECENTS] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_APP_SWIPE_TO_RECENTS;
-        CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_LAUNCHER_CLOSE_ALL_APPS_SWIPE] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_CLOSE_ALL_APPS_SWIPE;
-        CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_LAUNCHER_CLOSE_ALL_APPS_TO_HOME] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_CLOSE_ALL_APPS_TO_HOME;
-        CUJ_TO_STATSD_INTERACTION_TYPE[69] = NO_STATSD_LOGGING; // This is deprecated.
-        CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_LOCKSCREEN_CLOCK_MOVE_ANIMATION] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LOCKSCREEN_CLOCK_MOVE_ANIMATION;
-        CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_LAUNCHER_OPEN_SEARCH_RESULT] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_OPEN_SEARCH_RESULT;
-        // 72 - 77 are reserved for b/281564325.
-        CUJ_TO_STATSD_INTERACTION_TYPE[72] = NO_STATSD_LOGGING;
-        CUJ_TO_STATSD_INTERACTION_TYPE[73] = NO_STATSD_LOGGING;
-        CUJ_TO_STATSD_INTERACTION_TYPE[74] = NO_STATSD_LOGGING;
-        CUJ_TO_STATSD_INTERACTION_TYPE[75] = NO_STATSD_LOGGING;
-        CUJ_TO_STATSD_INTERACTION_TYPE[76] = NO_STATSD_LOGGING;
-        CUJ_TO_STATSD_INTERACTION_TYPE[77] = NO_STATSD_LOGGING;
-        CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_LAUNCHER_APP_CLOSE_TO_HOME_FALLBACK] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_APP_CLOSE_TO_HOME_FALLBACK;
-        CUJ_TO_STATSD_INTERACTION_TYPE[79] = NO_STATSD_LOGGING; // This is deprecated.
-        CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_IME_INSETS_SHOW_ANIMATION] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__IME_INSETS_SHOW_ANIMATION;
-        CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_IME_INSETS_HIDE_ANIMATION] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__IME_INSETS_HIDE_ANIMATION;
-        CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_SPLIT_SCREEN_DOUBLE_TAP_DIVIDER] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SPLIT_SCREEN_DOUBLE_TAP_DIVIDER;
-        CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_LAUNCHER_UNFOLD_ANIM] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_UNFOLD_ANIM;
-        CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_PREDICTIVE_BACK_CROSS_ACTIVITY] =
-            UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__PREDICTIVE_BACK_CROSS_ACTIVITY;
-        CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_PREDICTIVE_BACK_CROSS_TASK] =
-            UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__PREDICTIVE_BACK_CROSS_TASK;
-        CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_PREDICTIVE_BACK_HOME] =
-            UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__PREDICTIVE_BACK_HOME;
-    }
+    // These are not the CUJ constants you are looking for. These constants simply forward their
+    // definition from {@link Cuj}. They are here only as a transition measure until all references
+    // have been updated to the new location.
+    @Deprecated public static final int CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE = Cuj.CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE;
+    @Deprecated public static final int CUJ_NOTIFICATION_SHADE_SCROLL_FLING = Cuj.CUJ_NOTIFICATION_SHADE_SCROLL_FLING;
+    @Deprecated public static final int CUJ_NOTIFICATION_SHADE_ROW_EXPAND = Cuj.CUJ_NOTIFICATION_SHADE_ROW_EXPAND;
+    @Deprecated public static final int CUJ_NOTIFICATION_SHADE_ROW_SWIPE = Cuj.CUJ_NOTIFICATION_SHADE_ROW_SWIPE;
+    @Deprecated public static final int CUJ_NOTIFICATION_SHADE_QS_EXPAND_COLLAPSE = Cuj.CUJ_NOTIFICATION_SHADE_QS_EXPAND_COLLAPSE;
+    @Deprecated public static final int CUJ_NOTIFICATION_SHADE_QS_SCROLL_SWIPE = Cuj.CUJ_NOTIFICATION_SHADE_QS_SCROLL_SWIPE;
+    @Deprecated public static final int CUJ_LAUNCHER_APP_CLOSE_TO_HOME = Cuj.CUJ_LAUNCHER_APP_CLOSE_TO_HOME;
+    @Deprecated public static final int CUJ_LAUNCHER_QUICK_SWITCH = Cuj.CUJ_LAUNCHER_QUICK_SWITCH;
+    @Deprecated public static final int CUJ_NOTIFICATION_HEADS_UP_APPEAR = Cuj.CUJ_NOTIFICATION_HEADS_UP_APPEAR;
+    @Deprecated public static final int CUJ_NOTIFICATION_HEADS_UP_DISAPPEAR = Cuj.CUJ_NOTIFICATION_HEADS_UP_DISAPPEAR;
+    @Deprecated public static final int CUJ_NOTIFICATION_ADD = Cuj.CUJ_NOTIFICATION_ADD;
+    @Deprecated public static final int CUJ_NOTIFICATION_REMOVE = Cuj.CUJ_NOTIFICATION_REMOVE;
+    @Deprecated public static final int CUJ_NOTIFICATION_APP_START = Cuj.CUJ_NOTIFICATION_APP_START;
+    @Deprecated public static final int CUJ_LOCKSCREEN_PASSWORD_APPEAR = Cuj.CUJ_LOCKSCREEN_PASSWORD_APPEAR;
+    @Deprecated public static final int CUJ_LOCKSCREEN_PATTERN_APPEAR = Cuj.CUJ_LOCKSCREEN_PATTERN_APPEAR;
+    @Deprecated public static final int CUJ_LOCKSCREEN_PIN_APPEAR = Cuj.CUJ_LOCKSCREEN_PIN_APPEAR;
+    @Deprecated public static final int CUJ_LOCKSCREEN_PASSWORD_DISAPPEAR = Cuj.CUJ_LOCKSCREEN_PASSWORD_DISAPPEAR;
+    @Deprecated public static final int CUJ_LOCKSCREEN_PATTERN_DISAPPEAR = Cuj.CUJ_LOCKSCREEN_PATTERN_DISAPPEAR;
+    @Deprecated public static final int CUJ_LOCKSCREEN_PIN_DISAPPEAR = Cuj.CUJ_LOCKSCREEN_PIN_DISAPPEAR;
+    @Deprecated public static final int CUJ_LOCKSCREEN_TRANSITION_FROM_AOD = Cuj.CUJ_LOCKSCREEN_TRANSITION_FROM_AOD;
+    @Deprecated public static final int CUJ_LOCKSCREEN_TRANSITION_TO_AOD = Cuj.CUJ_LOCKSCREEN_TRANSITION_TO_AOD;
+    @Deprecated public static final int CUJ_SETTINGS_PAGE_SCROLL = Cuj.CUJ_SETTINGS_PAGE_SCROLL;
+    @Deprecated public static final int CUJ_LOCKSCREEN_UNLOCK_ANIMATION = Cuj.CUJ_LOCKSCREEN_UNLOCK_ANIMATION;
+    @Deprecated public static final int CUJ_SHADE_APP_LAUNCH_FROM_HISTORY_BUTTON = Cuj.CUJ_SHADE_APP_LAUNCH_FROM_HISTORY_BUTTON;
+    @Deprecated public static final int CUJ_SHADE_APP_LAUNCH_FROM_MEDIA_PLAYER = Cuj.CUJ_SHADE_APP_LAUNCH_FROM_MEDIA_PLAYER;
+    @Deprecated public static final int CUJ_SHADE_APP_LAUNCH_FROM_QS_TILE = Cuj.CUJ_SHADE_APP_LAUNCH_FROM_QS_TILE;
+    @Deprecated public static final int CUJ_SHADE_APP_LAUNCH_FROM_SETTINGS_BUTTON = Cuj.CUJ_SHADE_APP_LAUNCH_FROM_SETTINGS_BUTTON;
+    @Deprecated public static final int CUJ_STATUS_BAR_APP_LAUNCH_FROM_CALL_CHIP = Cuj.CUJ_STATUS_BAR_APP_LAUNCH_FROM_CALL_CHIP;
+    @Deprecated public static final int CUJ_PIP_TRANSITION = Cuj.CUJ_PIP_TRANSITION;
+    @Deprecated public static final int CUJ_USER_SWITCH = Cuj.CUJ_USER_SWITCH;
+    @Deprecated public static final int CUJ_SPLASHSCREEN_AVD = Cuj.CUJ_SPLASHSCREEN_AVD;
+    @Deprecated public static final int CUJ_SPLASHSCREEN_EXIT_ANIM = Cuj.CUJ_SPLASHSCREEN_EXIT_ANIM;
+    @Deprecated public static final int CUJ_SCREEN_OFF = Cuj.CUJ_SCREEN_OFF;
+    @Deprecated public static final int CUJ_SCREEN_OFF_SHOW_AOD = Cuj.CUJ_SCREEN_OFF_SHOW_AOD;
+    @Deprecated public static final int CUJ_UNFOLD_ANIM = Cuj.CUJ_UNFOLD_ANIM;
+    @Deprecated public static final int CUJ_SUW_LOADING_TO_SHOW_INFO_WITH_ACTIONS = Cuj.CUJ_SUW_LOADING_TO_SHOW_INFO_WITH_ACTIONS;
+    @Deprecated public static final int CUJ_SUW_SHOW_FUNCTION_SCREEN_WITH_ACTIONS = Cuj.CUJ_SUW_SHOW_FUNCTION_SCREEN_WITH_ACTIONS;
+    @Deprecated public static final int CUJ_SUW_LOADING_TO_NEXT_FLOW = Cuj.CUJ_SUW_LOADING_TO_NEXT_FLOW;
+    @Deprecated public static final int CUJ_SUW_LOADING_SCREEN_FOR_STATUS = Cuj.CUJ_SUW_LOADING_SCREEN_FOR_STATUS;
+    @Deprecated public static final int CUJ_SPLIT_SCREEN_RESIZE = Cuj.CUJ_SPLIT_SCREEN_RESIZE;
+    @Deprecated public static final int CUJ_SETTINGS_SLIDER = Cuj.CUJ_SETTINGS_SLIDER;
+    @Deprecated public static final int CUJ_TAKE_SCREENSHOT = Cuj.CUJ_TAKE_SCREENSHOT;
+    @Deprecated public static final int CUJ_VOLUME_CONTROL = Cuj.CUJ_VOLUME_CONTROL;
+    @Deprecated public static final int CUJ_BIOMETRIC_PROMPT_TRANSITION = Cuj.CUJ_BIOMETRIC_PROMPT_TRANSITION;
+    @Deprecated public static final int CUJ_SETTINGS_TOGGLE = Cuj.CUJ_SETTINGS_TOGGLE;
+    @Deprecated public static final int CUJ_SHADE_DIALOG_OPEN = Cuj.CUJ_SHADE_DIALOG_OPEN;
+    @Deprecated public static final int CUJ_USER_DIALOG_OPEN = Cuj.CUJ_USER_DIALOG_OPEN;
+    @Deprecated public static final int CUJ_TASKBAR_EXPAND = Cuj.CUJ_TASKBAR_EXPAND;
+    @Deprecated public static final int CUJ_TASKBAR_COLLAPSE = Cuj.CUJ_TASKBAR_COLLAPSE;
+    @Deprecated public static final int CUJ_SHADE_CLEAR_ALL = Cuj.CUJ_SHADE_CLEAR_ALL;
+    @Deprecated public static final int CUJ_LOCKSCREEN_OCCLUSION = Cuj.CUJ_LOCKSCREEN_OCCLUSION;
+    @Deprecated public static final int CUJ_LOCKSCREEN_CLOCK_MOVE_ANIMATION = Cuj.CUJ_LOCKSCREEN_CLOCK_MOVE_ANIMATION;
+    @Deprecated public static final int CUJ_SPLIT_SCREEN_DOUBLE_TAP_DIVIDER = Cuj.CUJ_SPLIT_SCREEN_DOUBLE_TAP_DIVIDER;
+    @Deprecated public static final int CUJ_PREDICTIVE_BACK_CROSS_ACTIVITY = Cuj.CUJ_PREDICTIVE_BACK_CROSS_ACTIVITY;
+    @Deprecated public static final int CUJ_PREDICTIVE_BACK_CROSS_TASK = Cuj.CUJ_PREDICTIVE_BACK_CROSS_TASK;
+    @Deprecated public static final int CUJ_PREDICTIVE_BACK_HOME = Cuj.CUJ_PREDICTIVE_BACK_HOME;
 
     private static class InstanceHolder {
         public static final InteractionJankMonitor INSTANCE =
             new InteractionJankMonitor(new HandlerThread(DEFAULT_WORKER_NAME));
     }
 
-    private final DeviceConfig.OnPropertiesChangedListener mPropertiesChangedListener =
-            this::updateProperties;
-
     @GuardedBy("mLock")
-    private final SparseArray<FrameTracker> mRunningTrackers;
-    @GuardedBy("mLock")
-    private final SparseArray<Runnable> mTimeoutActions;
-    private final HandlerThread mWorker;
+    private final SparseArray<RunningTracker> mRunningTrackers = new SparseArray<>();
+    private final Handler mWorker;
     private final DisplayResolutionTracker mDisplayResolutionTracker;
     private final Object mLock = new Object();
     private @ColorInt int mDebugBgColor = Color.CYAN;
@@ -409,91 +187,6 @@
     private int mTraceThresholdMissedFrames = DEFAULT_TRACE_THRESHOLD_MISSED_FRAMES;
     private int mTraceThresholdFrameTimeMillis = DEFAULT_TRACE_THRESHOLD_FRAME_TIME_MILLIS;
 
-    /** @hide */
-    @IntDef({
-            CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE,
-            CUJ_NOTIFICATION_SHADE_SCROLL_FLING,
-            CUJ_NOTIFICATION_SHADE_ROW_EXPAND,
-            CUJ_NOTIFICATION_SHADE_ROW_SWIPE,
-            CUJ_NOTIFICATION_SHADE_QS_EXPAND_COLLAPSE,
-            CUJ_NOTIFICATION_SHADE_QS_SCROLL_SWIPE,
-            CUJ_LAUNCHER_APP_LAUNCH_FROM_RECENTS,
-            CUJ_LAUNCHER_APP_LAUNCH_FROM_ICON,
-            CUJ_LAUNCHER_APP_CLOSE_TO_HOME,
-            CUJ_LAUNCHER_APP_CLOSE_TO_PIP,
-            CUJ_LAUNCHER_QUICK_SWITCH,
-            CUJ_NOTIFICATION_HEADS_UP_APPEAR,
-            CUJ_NOTIFICATION_HEADS_UP_DISAPPEAR,
-            CUJ_NOTIFICATION_ADD,
-            CUJ_NOTIFICATION_REMOVE,
-            CUJ_NOTIFICATION_APP_START,
-            CUJ_LOCKSCREEN_PASSWORD_APPEAR,
-            CUJ_LOCKSCREEN_PATTERN_APPEAR,
-            CUJ_LOCKSCREEN_PIN_APPEAR,
-            CUJ_LOCKSCREEN_PASSWORD_DISAPPEAR,
-            CUJ_LOCKSCREEN_PATTERN_DISAPPEAR,
-            CUJ_LOCKSCREEN_PIN_DISAPPEAR,
-            CUJ_LOCKSCREEN_TRANSITION_FROM_AOD,
-            CUJ_LOCKSCREEN_TRANSITION_TO_AOD,
-            CUJ_LAUNCHER_OPEN_ALL_APPS,
-            CUJ_LAUNCHER_ALL_APPS_SCROLL,
-            CUJ_LAUNCHER_APP_LAUNCH_FROM_WIDGET,
-            CUJ_SETTINGS_PAGE_SCROLL,
-            CUJ_LOCKSCREEN_UNLOCK_ANIMATION,
-            CUJ_SHADE_APP_LAUNCH_FROM_HISTORY_BUTTON,
-            CUJ_SHADE_APP_LAUNCH_FROM_MEDIA_PLAYER,
-            CUJ_SHADE_APP_LAUNCH_FROM_QS_TILE,
-            CUJ_SHADE_APP_LAUNCH_FROM_SETTINGS_BUTTON,
-            CUJ_STATUS_BAR_APP_LAUNCH_FROM_CALL_CHIP,
-            CUJ_PIP_TRANSITION,
-            CUJ_WALLPAPER_TRANSITION,
-            CUJ_USER_SWITCH,
-            CUJ_SPLASHSCREEN_AVD,
-            CUJ_SPLASHSCREEN_EXIT_ANIM,
-            CUJ_SCREEN_OFF,
-            CUJ_SCREEN_OFF_SHOW_AOD,
-            CUJ_ONE_HANDED_ENTER_TRANSITION,
-            CUJ_ONE_HANDED_EXIT_TRANSITION,
-            CUJ_UNFOLD_ANIM,
-            CUJ_SUW_LOADING_TO_SHOW_INFO_WITH_ACTIONS,
-            CUJ_SUW_SHOW_FUNCTION_SCREEN_WITH_ACTIONS,
-            CUJ_SUW_LOADING_TO_NEXT_FLOW,
-            CUJ_SUW_LOADING_SCREEN_FOR_STATUS,
-            CUJ_SPLIT_SCREEN_ENTER,
-            CUJ_SPLIT_SCREEN_EXIT,
-            CUJ_LOCKSCREEN_LAUNCH_CAMERA,
-            CUJ_SPLIT_SCREEN_RESIZE,
-            CUJ_SETTINGS_SLIDER,
-            CUJ_TAKE_SCREENSHOT,
-            CUJ_VOLUME_CONTROL,
-            CUJ_BIOMETRIC_PROMPT_TRANSITION,
-            CUJ_SETTINGS_TOGGLE,
-            CUJ_SHADE_DIALOG_OPEN,
-            CUJ_USER_DIALOG_OPEN,
-            CUJ_TASKBAR_EXPAND,
-            CUJ_TASKBAR_COLLAPSE,
-            CUJ_SHADE_CLEAR_ALL,
-            CUJ_LAUNCHER_UNLOCK_ENTRANCE_ANIMATION,
-            CUJ_LOCKSCREEN_OCCLUSION,
-            CUJ_RECENTS_SCROLLING,
-            CUJ_LAUNCHER_APP_SWIPE_TO_RECENTS,
-            CUJ_LAUNCHER_CLOSE_ALL_APPS_SWIPE,
-            CUJ_LAUNCHER_CLOSE_ALL_APPS_TO_HOME,
-            CUJ_LOCKSCREEN_CLOCK_MOVE_ANIMATION,
-            CUJ_LAUNCHER_OPEN_SEARCH_RESULT,
-            CUJ_LAUNCHER_APP_CLOSE_TO_HOME_FALLBACK,
-            CUJ_IME_INSETS_SHOW_ANIMATION,
-            CUJ_IME_INSETS_HIDE_ANIMATION,
-            CUJ_SPLIT_SCREEN_DOUBLE_TAP_DIVIDER,
-            CUJ_LAUNCHER_UNFOLD_ANIM,
-            CUJ_PREDICTIVE_BACK_CROSS_ACTIVITY,
-            CUJ_PREDICTIVE_BACK_CROSS_TASK,
-            CUJ_PREDICTIVE_BACK_HOME,
-    })
-    @Retention(RetentionPolicy.SOURCE)
-    public @interface CujType {
-    }
-
     /**
      * Get the singleton of InteractionJankMonitor.
      *
@@ -511,71 +204,44 @@
     @VisibleForTesting
     @RequiresPermission(Manifest.permission.READ_DEVICE_CONFIG)
     public InteractionJankMonitor(@NonNull HandlerThread worker) {
-        mRunningTrackers = new SparseArray<>();
-        mTimeoutActions = new SparseArray<>();
-        mWorker = worker;
-        mWorker.start();
-        mDisplayResolutionTracker = new DisplayResolutionTracker(worker.getThreadHandler());
-        mSamplingInterval = DEFAULT_SAMPLING_INTERVAL;
-        mEnabled = DEFAULT_ENABLED;
+        worker.start();
+        mWorker = worker.getThreadHandler();
+        mDisplayResolutionTracker = new DisplayResolutionTracker(mWorker);
 
         final Context context = ActivityThread.currentApplication();
-        if (context.checkCallingOrSelfPermission(READ_DEVICE_CONFIG) != PERMISSION_GRANTED) {
-            if (DEBUG) {
-                Log.d(TAG, "Initialized the InteractionJankMonitor."
-                        + " (No READ_DEVICE_CONFIG permission to change configs)"
-                        + " enabled=" + mEnabled + ", interval=" + mSamplingInterval
-                        + ", missedFrameThreshold=" + mTraceThresholdMissedFrames
-                        + ", frameTimeThreshold=" + mTraceThresholdFrameTimeMillis
-                        + ", package=" + context.getPackageName());
-            }
+        if (context == null || context.checkCallingOrSelfPermission(READ_DEVICE_CONFIG) != PERMISSION_GRANTED) {
+            Log.w(TAG, "Initializing without READ_DEVICE_CONFIG permission."
+                    + " enabled=" + mEnabled + ", interval=" + mSamplingInterval
+                    + ", missedFrameThreshold=" + mTraceThresholdMissedFrames
+                    + ", frameTimeThreshold=" + mTraceThresholdFrameTimeMillis
+                    + ", package=" + (context == null ? "null" : context.getPackageName()));
             return;
         }
 
         // Post initialization to the background in case we're running on the main thread.
-        mWorker.getThreadHandler().post(
-                () -> {
-                    try {
-                        mPropertiesChangedListener.onPropertiesChanged(
-                                DeviceConfig.getProperties(NAMESPACE_INTERACTION_JANK_MONITOR));
-                        DeviceConfig.addOnPropertiesChangedListener(
-                                NAMESPACE_INTERACTION_JANK_MONITOR,
-                                new HandlerExecutor(mWorker.getThreadHandler()),
-                                mPropertiesChangedListener);
-                    } catch (SecurityException ex) {
-                        Log.d(TAG, "Can't get properties: READ_DEVICE_CONFIG granted="
-                                + context.checkCallingOrSelfPermission(READ_DEVICE_CONFIG)
-                                + ", package=" + context.getPackageName());
-                    }
-                });
+        mWorker.post(() -> {
+            try {
+                updateProperties(DeviceConfig.getProperties(NAMESPACE_INTERACTION_JANK_MONITOR));
+                DeviceConfig.addOnPropertiesChangedListener(NAMESPACE_INTERACTION_JANK_MONITOR,
+                        new HandlerExecutor(mWorker), this::updateProperties);
+            } catch (SecurityException ex) {
+                Log.d(TAG, "Can't get properties: READ_DEVICE_CONFIG granted="
+                        + context.checkCallingOrSelfPermission(READ_DEVICE_CONFIG)
+                        + ", package=" + context.getPackageName());
+            }
+        });
     }
 
     /**
      * Creates a {@link FrameTracker} instance.
      *
-     * @param config the config used in instrumenting
-     * @param session the session associates with this tracker
+     * @param config the conifg associates with this tracker
      * @return instance of the FrameTracker
      */
     @VisibleForTesting
-    public FrameTracker createFrameTracker(Configuration config, Session session) {
+    public FrameTracker createFrameTracker(Configuration config) {
         final View view = config.mView;
 
-        if (!config.hasValidView()) {
-            boolean attached = false;
-            boolean hasViewRoot = false;
-            boolean hasRenderer = false;
-            if (view != null) {
-                attached = view.isAttachedToWindow();
-                hasViewRoot = view.getViewRootImpl() != null;
-                hasRenderer = view.getThreadedRenderer() != null;
-            }
-            Log.d(TAG, "create FrameTracker fails: view=" + view
-                    + ", attached=" + attached + ", hasViewRoot=" + hasViewRoot
-                    + ", hasRenderer=" + hasRenderer, new Throwable());
-            return null;
-        }
-
         final ThreadedRendererWrapper threadedRenderer =
                 view == null ? null : new ThreadedRendererWrapper(view.getThreadedRenderer());
         final ViewRootWrapper viewRoot =
@@ -583,52 +249,50 @@
         final SurfaceControlWrapper surfaceControl = new SurfaceControlWrapper();
         final ChoreographerWrapper choreographer =
                 new ChoreographerWrapper(Choreographer.getInstance());
-        final FrameTrackerListener eventsListener = (s, act) -> handleCujEvents(act, s);
+        final FrameTrackerListener eventsListener = new FrameTrackerListener() {
+            @Override
+            public void onCujEvents(FrameTracker tracker, String action, int reason) {
+                config.getHandler().runWithScissors(() ->
+                        handleCujEvents(config.mCujType, tracker, action, reason),
+                        EXECUTOR_TASK_TIMEOUT);
+            }
+
+            @Override
+            public void triggerPerfetto(Configuration config) {
+                mWorker.post(() -> PerfettoTrigger.trigger(config.getPerfettoTrigger()));
+            }
+        };
         final FrameMetricsWrapper frameMetrics = new FrameMetricsWrapper();
 
-        return new FrameTracker(this, session, config.getHandler(), threadedRenderer, viewRoot,
+        return new FrameTracker(config, threadedRenderer, viewRoot,
                 surfaceControl, choreographer, frameMetrics,
                 new FrameTracker.StatsLogWrapper(mDisplayResolutionTracker),
                 mTraceThresholdMissedFrames, mTraceThresholdFrameTimeMillis,
-                eventsListener, config);
+                eventsListener);
     }
 
     @UiThread
-    private void handleCujEvents(String action, Session session) {
+    private void handleCujEvents(
+            @Cuj.CujType int cuj, FrameTracker tracker, String action, @Reasons int reason) {
         // Clear the running and timeout tasks if the end / cancel was fired within the tracker.
         // Or we might have memory leaks.
-        if (needRemoveTasks(action, session)) {
-            getTracker(session.getCuj()).getHandler().runWithScissors(() -> {
-                removeTimeout(session.getCuj());
-                removeTracker(session.getCuj(), session.getReason());
-            }, EXECUTOR_TASK_TIMEOUT);
+        if (needRemoveTasks(action, reason)) {
+            removeTrackerIfCurrent(cuj, tracker, reason);
         }
     }
 
-    private boolean needRemoveTasks(String action, Session session) {
-        final boolean badEnd = action.equals(ACTION_SESSION_END)
-                && session.getReason() != REASON_END_NORMAL;
+    private static boolean needRemoveTasks(String action, @Reasons int reason) {
+        final boolean badEnd = action.equals(ACTION_SESSION_END) && reason != REASON_END_NORMAL;
         final boolean badCancel = action.equals(ACTION_SESSION_CANCEL)
-                && !(session.getReason() == REASON_CANCEL_NORMAL
-                || session.getReason() == REASON_CANCEL_TIMEOUT);
+                && !(reason == REASON_CANCEL_NORMAL || reason == REASON_CANCEL_TIMEOUT);
         return badEnd || badCancel;
     }
 
-    private void removeTimeout(@CujType int cujType) {
-        synchronized (mLock) {
-            Runnable timeout = mTimeoutActions.get(cujType);
-            if (timeout != null) {
-                getTracker(cujType).getHandler().removeCallbacks(timeout);
-                mTimeoutActions.remove(cujType);
-            }
-        }
-    }
-
     /**
      * @param cujType cuj type
      * @return true if the cuj is under instrumenting, false otherwise.
      */
-    public boolean isInstrumenting(@CujType int cujType) {
+    public boolean isInstrumenting(@Cuj.CujType int cujType) {
         synchronized (mLock) {
             return mRunningTrackers.contains(cujType);
         }
@@ -638,10 +302,10 @@
      * Begins a trace session.
      *
      * @param v an attached view.
-     * @param cujType the specific {@link InteractionJankMonitor.CujType}.
+     * @param cujType the specific {@link Cuj.CujType}.
      * @return boolean true if the tracker is started successfully, false otherwise.
      */
-    public boolean begin(View v, @CujType int cujType) {
+    public boolean begin(View v, @Cuj.CujType int cujType) {
         try {
             return begin(Configuration.Builder.withView(cujType, v));
         } catch (IllegalArgumentException ex) {
@@ -667,7 +331,7 @@
             final boolean success = config.getHandler().runWithScissors(
                     () -> result.mResult = beginInternal(config), EXECUTOR_TASK_TIMEOUT);
             if (!success) {
-                Log.d(TAG, "begin failed due to timeout, CUJ=" + getNameOfCuj(config.mCujType));
+                Log.d(TAG, "begin failed due to timeout, CUJ=" + Cuj.getNameOfCuj(config.mCujType));
                 return false;
             }
             return result.mResult;
@@ -680,75 +344,59 @@
     @UiThread
     private boolean beginInternal(@NonNull Configuration conf) {
         int cujType = conf.mCujType;
-        if (!shouldMonitor(cujType)) return false;
-        FrameTracker tracker = getTracker(cujType);
-        // Skip subsequent calls if we already have an ongoing tracing.
-        if (tracker != null) return false;
+        if (!shouldMonitor()) {
+            return false;
+        }
 
-        // begin a new trace session.
-        tracker = createFrameTracker(conf, new Session(cujType, conf.mTag));
-        if (tracker == null) return false;
-        putTracker(cujType, tracker);
-        tracker.begin();
+        RunningTracker tracker = putTrackerIfNoCurrent(cujType, () ->
+                new RunningTracker(
+                    conf, createFrameTracker(conf), () -> cancel(cujType, REASON_CANCEL_TIMEOUT)));
+        if (tracker == null) {
+            return false;
+        }
 
+        tracker.mTracker.begin();
         // Cancel the trace if we don't get an end() call in specified duration.
-        scheduleTimeoutAction(
-                cujType, conf.mTimeout, () -> cancel(cujType, REASON_CANCEL_TIMEOUT));
+        scheduleTimeoutAction(tracker.mConfig, tracker.mTimeoutAction);
+
         return true;
     }
 
     /**
      * Check if the monitoring is enabled and if it should be sampled.
      */
-    @SuppressWarnings("RandomModInteger")
     @VisibleForTesting
-    public boolean shouldMonitor(@CujType int cujType) {
-        boolean shouldSample = ThreadLocalRandom.current().nextInt() % mSamplingInterval == 0;
-        if (!mEnabled || !shouldSample) {
-            if (DEBUG) {
-                Log.d(TAG, "Skip monitoring cuj: " + getNameOfCuj(cujType)
-                        + ", enable=" + mEnabled + ", debuggable=" + DEFAULT_ENABLED
-                        + ", sample=" + shouldSample + ", interval=" + mSamplingInterval);
-            }
-            return false;
-        }
-        return true;
+    public boolean shouldMonitor() {
+        return mEnabled && (ThreadLocalRandom.current().nextInt(mSamplingInterval) == 0);
     }
 
-    /**
-     * Schedules a timeout action.
-     * @param cuj cuj type
-     * @param timeout duration to timeout
-     * @param action action once timeout
-     */
     @VisibleForTesting
-    public void scheduleTimeoutAction(@CujType int cuj, long timeout, Runnable action) {
-        synchronized (mLock) {
-            mTimeoutActions.put(cuj, action);
-            getTracker(cuj).getHandler().postDelayed(action, timeout);
-        }
+    public void scheduleTimeoutAction(Configuration config, Runnable action) {
+        config.getHandler().postDelayed(action, config.mTimeout);
     }
 
     /**
      * Ends a trace session.
      *
-     * @param cujType the specific {@link InteractionJankMonitor.CujType}.
+     * @param cujType the specific {@link Cuj.CujType}.
      * @return boolean true if the tracker is ended successfully, false otherwise.
      */
-    public boolean end(@CujType int cujType) {
+    public boolean end(@Cuj.CujType int cujType) {
         postEventLogToWorkerThread((unixNanos, elapsedNanos, realtimeNanos) -> {
             EventLogTags.writeJankCujEventsEndRequest(
                     cujType, unixNanos, elapsedNanos, realtimeNanos);
         });
-        FrameTracker tracker = getTracker(cujType);
+        RunningTracker tracker = getTracker(cujType);
         // Skip this call since we haven't started a trace yet.
-        if (tracker == null) return false;
+        if (tracker == null) {
+            return false;
+        }
         try {
             final TrackerResult result = new TrackerResult();
-            final boolean success = tracker.getHandler().runWithScissors(
-                    () -> result.mResult = endInternal(cujType), EXECUTOR_TASK_TIMEOUT);
+            final boolean success = tracker.mConfig.getHandler().runWithScissors(
+                    () -> result.mResult = endInternal(tracker), EXECUTOR_TASK_TIMEOUT);
             if (!success) {
-                Log.d(TAG, "end failed due to timeout, CUJ=" + getNameOfCuj(cujType));
+                Log.d(TAG, "end failed due to timeout, CUJ=" + Cuj.getNameOfCuj(cujType));
                 return false;
             }
             return result.mResult;
@@ -759,15 +407,11 @@
     }
 
     @UiThread
-    private boolean endInternal(@CujType int cujType) {
-        // remove the timeout action first.
-        removeTimeout(cujType);
-        FrameTracker tracker = getTracker(cujType);
-        if (tracker == null) return false;
-        // if the end call doesn't return true, another thread is handling end of the cuj.
-        if (tracker.end(REASON_END_NORMAL)) {
-            removeTracker(cujType, REASON_END_NORMAL);
+    private boolean endInternal(RunningTracker tracker) {
+        if (removeTrackerIfCurrent(tracker, REASON_END_NORMAL)) {
+            return false;
         }
+        tracker.mTracker.end(REASON_END_NORMAL);
         return true;
     }
 
@@ -776,7 +420,7 @@
      *
      * @return boolean true if the tracker is cancelled successfully, false otherwise.
      */
-    public boolean cancel(@CujType int cujType) {
+    public boolean cancel(@Cuj.CujType int cujType) {
         postEventLogToWorkerThread((unixNanos, elapsedNanos, realtimeNanos) -> {
             EventLogTags.writeJankCujEventsCancelRequest(
                     cujType, unixNanos, elapsedNanos, realtimeNanos);
@@ -790,16 +434,18 @@
      * @return boolean true if the tracker is cancelled successfully, false otherwise.
      */
     @VisibleForTesting
-    public boolean cancel(@CujType int cujType, @Reasons int reason) {
-        FrameTracker tracker = getTracker(cujType);
+    public boolean cancel(@Cuj.CujType int cujType, @Reasons int reason) {
+        RunningTracker tracker = getTracker(cujType);
         // Skip this call since we haven't started a trace yet.
-        if (tracker == null) return false;
+        if (tracker == null) {
+            return false;
+        }
         try {
             final TrackerResult result = new TrackerResult();
-            final boolean success = tracker.getHandler().runWithScissors(
-                    () -> result.mResult = cancelInternal(cujType, reason), EXECUTOR_TASK_TIMEOUT);
+            final boolean success = tracker.mConfig.getHandler().runWithScissors(
+                    () -> result.mResult = cancelInternal(tracker, reason), EXECUTOR_TASK_TIMEOUT);
             if (!success) {
-                Log.d(TAG, "cancel failed due to timeout, CUJ=" + getNameOfCuj(cujType));
+                Log.d(TAG, "cancel failed due to timeout, CUJ=" + Cuj.getNameOfCuj(cujType));
                 return false;
             }
             return result.mResult;
@@ -810,71 +456,86 @@
     }
 
     @UiThread
-    private boolean cancelInternal(@CujType int cujType, @Reasons int reason) {
-        // remove the timeout action first.
-        removeTimeout(cujType);
-        FrameTracker tracker = getTracker(cujType);
-        if (tracker == null) return false;
-        // if the cancel call doesn't return true, another thread is handling cancel of the cuj.
-        if (tracker.cancel(reason)) {
-            removeTracker(cujType, reason);
+    private boolean cancelInternal(RunningTracker tracker, @Reasons int reason) {
+        if (removeTrackerIfCurrent(tracker, reason)) {
+            return false;
         }
+        tracker.mTracker.cancel(reason);
         return true;
     }
 
     @UiThread
-    private void putTracker(@CujType int cuj, @NonNull FrameTracker tracker) {
+    private RunningTracker putTrackerIfNoCurrent(
+            @Cuj.CujType int cuj, Supplier<RunningTracker> supplier) {
         synchronized (mLock) {
+            if (mRunningTrackers.contains(cuj)) {
+                return null;
+            }
+
+            RunningTracker tracker = supplier.get();
+            if (tracker == null) {
+                return null;
+            }
+
             mRunningTrackers.put(cuj, tracker);
             if (mDebugOverlay != null) {
                 mDebugOverlay.onTrackerAdded(cuj, tracker);
             }
-            if (DEBUG) {
-                Log.d(TAG, "Added tracker for " + getNameOfCuj(cuj)
-                        + ". mRunningTrackers=" + listNamesOfCujs(mRunningTrackers));
-            }
+
+            return tracker;
         }
     }
 
-    private FrameTracker getTracker(@CujType int cuj) {
+    private RunningTracker getTracker(@Cuj.CujType int cuj) {
         synchronized (mLock) {
             return mRunningTrackers.get(cuj);
         }
     }
 
+    /**
+     * @return {@code true} if another tracker is current
+     */
     @UiThread
-    private void removeTracker(@CujType int cuj, int reason) {
+    private boolean removeTrackerIfCurrent(RunningTracker tracker, int reason) {
+        return removeTrackerIfCurrent(tracker.mConfig.mCujType, tracker.mTracker, reason);
+    }
+
+    /**
+     * @return {@code true} if another tracker is current
+     */
+    @UiThread
+    private boolean removeTrackerIfCurrent(@Cuj.CujType int cuj, FrameTracker tracker, int reason) {
         synchronized (mLock) {
+            RunningTracker running = mRunningTrackers.get(cuj);
+            if (running == null || running.mTracker != tracker) {
+                return true;
+            }
+
+            running.mConfig.getHandler().removeCallbacks(running.mTimeoutAction);
             mRunningTrackers.remove(cuj);
             if (mDebugOverlay != null) {
                 mDebugOverlay.onTrackerRemoved(cuj, reason, mRunningTrackers);
             }
-            if (DEBUG) {
-                Log.d(TAG, "Removed tracker for " + getNameOfCuj(cuj)
-                        + ". mRunningTrackers=" + listNamesOfCujs(mRunningTrackers));
-            }
+            return false;
         }
     }
 
     @WorkerThread
-    private void updateProperties(DeviceConfig.Properties properties) {
+    @VisibleForTesting
+    public void updateProperties(DeviceConfig.Properties properties) {
         for (String property : properties.getKeyset()) {
             switch (property) {
-                case SETTINGS_SAMPLING_INTERVAL_KEY:
-                    mSamplingInterval = properties.getInt(property, DEFAULT_SAMPLING_INTERVAL);
-                    break;
-                case SETTINGS_THRESHOLD_MISSED_FRAMES_KEY:
-                    mTraceThresholdMissedFrames =
-                            properties.getInt(property, DEFAULT_TRACE_THRESHOLD_MISSED_FRAMES);
-                    break;
-                case SETTINGS_THRESHOLD_FRAME_TIME_MILLIS_KEY:
-                    mTraceThresholdFrameTimeMillis =
-                            properties.getInt(property, DEFAULT_TRACE_THRESHOLD_FRAME_TIME_MILLIS);
-                    break;
-                case SETTINGS_ENABLED_KEY:
-                    mEnabled = properties.getBoolean(property, DEFAULT_ENABLED);
-                    break;
-                case SETTINGS_DEBUG_OVERLAY_ENABLED_KEY:
+                case SETTINGS_SAMPLING_INTERVAL_KEY ->
+                        mSamplingInterval = properties.getInt(property, DEFAULT_SAMPLING_INTERVAL);
+                case SETTINGS_THRESHOLD_MISSED_FRAMES_KEY ->
+                        mTraceThresholdMissedFrames =
+                                properties.getInt(property, DEFAULT_TRACE_THRESHOLD_MISSED_FRAMES);
+                case SETTINGS_THRESHOLD_FRAME_TIME_MILLIS_KEY ->
+                        mTraceThresholdFrameTimeMillis =
+                                properties.getInt(property, DEFAULT_TRACE_THRESHOLD_FRAME_TIME_MILLIS);
+                case SETTINGS_ENABLED_KEY ->
+                        mEnabled = properties.getBoolean(property, DEFAULT_ENABLED);
+                case SETTINGS_DEBUG_OVERLAY_ENABLED_KEY -> {
                     // Never allow the debug overlay to be used on user builds
                     boolean debugOverlayEnabled = Build.IS_DEBUGGABLE
                             && properties.getBoolean(property, DEFAULT_DEBUG_OVERLAY_ENABLED);
@@ -885,49 +546,35 @@
                         mDebugOverlay.dispose();
                         mDebugOverlay = null;
                     }
-                    break;
-                default:
-                    if (DEBUG) {
-                        Log.d(TAG, "Got a change event for an unknown property: "
-                                + property + " => " + properties.getString(property, ""));
-                    }
+                }
+                default -> Log.w(TAG, "Got a change event for an unknown property: "
+                        + property + " => " + properties.getString(property, ""));
             }
         }
     }
 
-    @VisibleForTesting
-    public DeviceConfig.OnPropertiesChangedListener getPropertiesChangedListener() {
-        return mPropertiesChangedListener;
-    }
-
-    /**
-     * Triggers the perfetto daemon to collect and upload data.
-     */
-    @VisibleForTesting
-    public void trigger(Session session) {
-        mWorker.getThreadHandler().post(
-                () -> PerfettoTrigger.trigger(session.getPerfettoTrigger()));
-    }
-
     /**
      * A helper method to translate interaction type to CUJ name.
      *
      * @param interactionType the interaction type defined in AtomsProto.java
      * @return the name of the interaction type
+     * @deprecated use {@link Cuj#getNameOfInteraction(int)}
      */
+    @Deprecated
     public static String getNameOfInteraction(int interactionType) {
-        // There is an offset amount of 1 between cujType and interactionType.
-        return getNameOfCuj(getCujTypeFromInteraction(interactionType));
+        return Cuj.getNameOfInteraction(interactionType);
     }
 
     /**
-     * A helper method to translate interaction type to CUJ type.
+     * A helper method to translate CUJ type to CUJ name.
      *
-     * @param interactionType the interaction type defined in AtomsProto.java
-     * @return the integer in {@link CujType}
+     * @param cujType the cuj type defined in this file
+     * @return the name of the cuj type
+     * @deprecated use {@link Cuj#getNameOfCuj(int)}
      */
-    private static int getCujTypeFromInteraction(int interactionType) {
-        return interactionType - 1;
+    @Deprecated
+    public static String getNameOfCuj(int cujType) {
+        return Cuj.getNameOfCuj(cujType);
     }
 
     /**
@@ -943,195 +590,14 @@
         mDebugYOffset = yOffset;
     }
 
-    /**
-     * A helper method for getting a string representation of all running CUJs. For example,
-     * "(LOCKSCREEN_TRANSITION_FROM_AOD, IME_INSETS_ANIMATION)"
-     */
-    private static String listNamesOfCujs(SparseArray<FrameTracker> trackers) {
-        if (!DEBUG) {
-            return null;
-        }
-        StringBuilder sb = new StringBuilder();
-        sb.append('(');
-        for (int i = 0; i < trackers.size(); i++) {
-            sb.append(getNameOfCuj(trackers.keyAt(i)));
-            if (i < trackers.size() - 1) {
-                sb.append(", ");
-            }
-        }
-        sb.append(')');
-        return sb.toString();
-    }
+    private void postEventLogToWorkerThread(TimeFunction logFunction) {
+        final Instant now = Instant.now();
+        final long unixNanos = TimeUnit.NANOSECONDS.convert(now.getEpochSecond(), TimeUnit.SECONDS)
+                + now.getNano();
+        final long elapsedNanos = SystemClock.elapsedRealtimeNanos();
+        final long realtimeNanos = SystemClock.uptimeNanos();
 
-    /**
-     * A helper method to translate CUJ type to CUJ name.
-     *
-     * @param cujType the cuj type defined in this file
-     * @return the name of the cuj type
-     */
-    public static String getNameOfCuj(int cujType) {
-        // Please note:
-        // 1. The length of the returned string shouldn't exceed MAX_LENGTH_OF_CUJ_NAME.
-        // 2. The returned string should be the same with the name defined in atoms.proto.
-        switch (cujType) {
-            case CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE:
-                return "NOTIFICATION_SHADE_EXPAND_COLLAPSE";
-            case CUJ_NOTIFICATION_SHADE_SCROLL_FLING:
-                return "NOTIFICATION_SHADE_SCROLL_FLING";
-            case CUJ_NOTIFICATION_SHADE_ROW_EXPAND:
-                return "NOTIFICATION_SHADE_ROW_EXPAND";
-            case CUJ_NOTIFICATION_SHADE_ROW_SWIPE:
-                return "NOTIFICATION_SHADE_ROW_SWIPE";
-            case CUJ_NOTIFICATION_SHADE_QS_EXPAND_COLLAPSE:
-                return "NOTIFICATION_SHADE_QS_EXPAND_COLLAPSE";
-            case CUJ_NOTIFICATION_SHADE_QS_SCROLL_SWIPE:
-                return "NOTIFICATION_SHADE_QS_SCROLL_SWIPE";
-            case CUJ_LAUNCHER_APP_LAUNCH_FROM_RECENTS:
-                return "LAUNCHER_APP_LAUNCH_FROM_RECENTS";
-            case CUJ_LAUNCHER_APP_LAUNCH_FROM_ICON:
-                return "LAUNCHER_APP_LAUNCH_FROM_ICON";
-            case CUJ_LAUNCHER_APP_CLOSE_TO_HOME:
-                return "LAUNCHER_APP_CLOSE_TO_HOME";
-            case CUJ_LAUNCHER_APP_CLOSE_TO_PIP:
-                return "LAUNCHER_APP_CLOSE_TO_PIP";
-            case CUJ_LAUNCHER_QUICK_SWITCH:
-                return "LAUNCHER_QUICK_SWITCH";
-            case CUJ_NOTIFICATION_HEADS_UP_APPEAR:
-                return "NOTIFICATION_HEADS_UP_APPEAR";
-            case CUJ_NOTIFICATION_HEADS_UP_DISAPPEAR:
-                return "NOTIFICATION_HEADS_UP_DISAPPEAR";
-            case CUJ_NOTIFICATION_ADD:
-                return "NOTIFICATION_ADD";
-            case CUJ_NOTIFICATION_REMOVE:
-                return "NOTIFICATION_REMOVE";
-            case CUJ_NOTIFICATION_APP_START:
-                return "NOTIFICATION_APP_START";
-            case CUJ_LOCKSCREEN_PASSWORD_APPEAR:
-                return "LOCKSCREEN_PASSWORD_APPEAR";
-            case CUJ_LOCKSCREEN_PATTERN_APPEAR:
-                return "LOCKSCREEN_PATTERN_APPEAR";
-            case CUJ_LOCKSCREEN_PIN_APPEAR:
-                return "LOCKSCREEN_PIN_APPEAR";
-            case CUJ_LOCKSCREEN_PASSWORD_DISAPPEAR:
-                return "LOCKSCREEN_PASSWORD_DISAPPEAR";
-            case CUJ_LOCKSCREEN_PATTERN_DISAPPEAR:
-                return "LOCKSCREEN_PATTERN_DISAPPEAR";
-            case CUJ_LOCKSCREEN_PIN_DISAPPEAR:
-                return "LOCKSCREEN_PIN_DISAPPEAR";
-            case CUJ_LOCKSCREEN_TRANSITION_FROM_AOD:
-                return "LOCKSCREEN_TRANSITION_FROM_AOD";
-            case CUJ_LOCKSCREEN_TRANSITION_TO_AOD:
-                return "LOCKSCREEN_TRANSITION_TO_AOD";
-            case CUJ_LAUNCHER_OPEN_ALL_APPS :
-                return "LAUNCHER_OPEN_ALL_APPS";
-            case CUJ_LAUNCHER_ALL_APPS_SCROLL:
-                return "LAUNCHER_ALL_APPS_SCROLL";
-            case CUJ_LAUNCHER_APP_LAUNCH_FROM_WIDGET:
-                return "LAUNCHER_APP_LAUNCH_FROM_WIDGET";
-            case CUJ_SETTINGS_PAGE_SCROLL:
-                return "SETTINGS_PAGE_SCROLL";
-            case CUJ_LOCKSCREEN_UNLOCK_ANIMATION:
-                return "LOCKSCREEN_UNLOCK_ANIMATION";
-            case CUJ_SHADE_APP_LAUNCH_FROM_HISTORY_BUTTON:
-                return "SHADE_APP_LAUNCH_FROM_HISTORY_BUTTON";
-            case CUJ_SHADE_APP_LAUNCH_FROM_MEDIA_PLAYER:
-                return "SHADE_APP_LAUNCH_FROM_MEDIA_PLAYER";
-            case CUJ_SHADE_APP_LAUNCH_FROM_QS_TILE:
-                return "SHADE_APP_LAUNCH_FROM_QS_TILE";
-            case CUJ_SHADE_APP_LAUNCH_FROM_SETTINGS_BUTTON:
-                return "SHADE_APP_LAUNCH_FROM_SETTINGS_BUTTON";
-            case CUJ_STATUS_BAR_APP_LAUNCH_FROM_CALL_CHIP:
-                return "STATUS_BAR_APP_LAUNCH_FROM_CALL_CHIP";
-            case CUJ_PIP_TRANSITION:
-                return "PIP_TRANSITION";
-            case CUJ_WALLPAPER_TRANSITION:
-                return "WALLPAPER_TRANSITION";
-            case CUJ_USER_SWITCH:
-                return "USER_SWITCH";
-            case CUJ_SPLASHSCREEN_AVD:
-                return "SPLASHSCREEN_AVD";
-            case CUJ_SPLASHSCREEN_EXIT_ANIM:
-                return "SPLASHSCREEN_EXIT_ANIM";
-            case CUJ_SCREEN_OFF:
-                return "SCREEN_OFF";
-            case CUJ_SCREEN_OFF_SHOW_AOD:
-                return "SCREEN_OFF_SHOW_AOD";
-            case CUJ_ONE_HANDED_ENTER_TRANSITION:
-                return "ONE_HANDED_ENTER_TRANSITION";
-            case CUJ_ONE_HANDED_EXIT_TRANSITION:
-                return "ONE_HANDED_EXIT_TRANSITION";
-            case CUJ_UNFOLD_ANIM:
-                return "UNFOLD_ANIM";
-            case CUJ_SUW_LOADING_TO_SHOW_INFO_WITH_ACTIONS:
-                return "SUW_LOADING_TO_SHOW_INFO_WITH_ACTIONS";
-            case CUJ_SUW_SHOW_FUNCTION_SCREEN_WITH_ACTIONS:
-                return "SUW_SHOW_FUNCTION_SCREEN_WITH_ACTIONS";
-            case CUJ_SUW_LOADING_TO_NEXT_FLOW:
-                return "SUW_LOADING_TO_NEXT_FLOW";
-            case CUJ_SUW_LOADING_SCREEN_FOR_STATUS:
-                return "SUW_LOADING_SCREEN_FOR_STATUS";
-            case CUJ_SPLIT_SCREEN_ENTER:
-                return "SPLIT_SCREEN_ENTER";
-            case CUJ_SPLIT_SCREEN_EXIT:
-                return "SPLIT_SCREEN_EXIT";
-            case CUJ_LOCKSCREEN_LAUNCH_CAMERA:
-                return "LOCKSCREEN_LAUNCH_CAMERA";
-            case CUJ_SPLIT_SCREEN_RESIZE:
-                return "SPLIT_SCREEN_RESIZE";
-            case CUJ_SETTINGS_SLIDER:
-                return "SETTINGS_SLIDER";
-            case CUJ_TAKE_SCREENSHOT:
-                return "TAKE_SCREENSHOT";
-            case CUJ_VOLUME_CONTROL:
-                return "VOLUME_CONTROL";
-            case CUJ_BIOMETRIC_PROMPT_TRANSITION:
-                return "BIOMETRIC_PROMPT_TRANSITION";
-            case CUJ_SETTINGS_TOGGLE:
-                return "SETTINGS_TOGGLE";
-            case CUJ_SHADE_DIALOG_OPEN:
-                return "SHADE_DIALOG_OPEN";
-            case CUJ_USER_DIALOG_OPEN:
-                return "USER_DIALOG_OPEN";
-            case CUJ_TASKBAR_EXPAND:
-                return "TASKBAR_EXPAND";
-            case CUJ_TASKBAR_COLLAPSE:
-                return "TASKBAR_COLLAPSE";
-            case CUJ_SHADE_CLEAR_ALL:
-                return "SHADE_CLEAR_ALL";
-            case CUJ_LAUNCHER_UNLOCK_ENTRANCE_ANIMATION:
-                return "LAUNCHER_UNLOCK_ENTRANCE_ANIMATION";
-            case CUJ_LOCKSCREEN_OCCLUSION:
-                return "LOCKSCREEN_OCCLUSION";
-            case CUJ_RECENTS_SCROLLING:
-                return "RECENTS_SCROLLING";
-            case CUJ_LAUNCHER_APP_SWIPE_TO_RECENTS:
-                return "LAUNCHER_APP_SWIPE_TO_RECENTS";
-            case CUJ_LAUNCHER_CLOSE_ALL_APPS_SWIPE:
-                return "LAUNCHER_CLOSE_ALL_APPS_SWIPE";
-            case CUJ_LAUNCHER_CLOSE_ALL_APPS_TO_HOME:
-                return "LAUNCHER_CLOSE_ALL_APPS_TO_HOME";
-            case CUJ_LOCKSCREEN_CLOCK_MOVE_ANIMATION:
-                return "LOCKSCREEN_CLOCK_MOVE_ANIMATION";
-            case CUJ_LAUNCHER_OPEN_SEARCH_RESULT:
-                return "LAUNCHER_OPEN_SEARCH_RESULT";
-            case CUJ_LAUNCHER_APP_CLOSE_TO_HOME_FALLBACK:
-                return "LAUNCHER_APP_CLOSE_TO_HOME_FALLBACK";
-            case CUJ_IME_INSETS_SHOW_ANIMATION:
-                return "IME_INSETS_SHOW_ANIMATION";
-            case CUJ_IME_INSETS_HIDE_ANIMATION:
-                return "IME_INSETS_HIDE_ANIMATION";
-            case CUJ_SPLIT_SCREEN_DOUBLE_TAP_DIVIDER:
-                return "SPLIT_SCREEN_DOUBLE_TAP_DIVIDER";
-            case CUJ_LAUNCHER_UNFOLD_ANIM:
-                return "LAUNCHER_UNFOLD_ANIM";
-            case CUJ_PREDICTIVE_BACK_CROSS_ACTIVITY:
-                return "PREDICTIVE_BACK_CROSS_ACTIVITY";
-            case CUJ_PREDICTIVE_BACK_CROSS_TASK:
-                return "PREDICTIVE_BACK_CROSS_TASK";
-            case CUJ_PREDICTIVE_BACK_HOME:
-                return "PREDICTIVE_BACK_HOME";
-        }
-        return "UNKNOWN";
+        mWorker.post(() -> logFunction.invoke(unixNanos, elapsedNanos, realtimeNanos));
     }
 
     private static class TrackerResult {
@@ -1147,9 +613,10 @@
         private final Context mContext;
         private final long mTimeout;
         private final String mTag;
+        private final String mSessionName;
         private final boolean mSurfaceOnly;
         private final SurfaceControl mSurfaceControl;
-        private final @CujType int mCujType;
+        private final @Cuj.CujType int mCujType;
         private final boolean mDeferMonitor;
         private final Handler mHandler;
 
@@ -1167,17 +634,17 @@
             private String mAttrTag = "";
             private boolean mAttrSurfaceOnly;
             private SurfaceControl mAttrSurfaceControl;
-            private @CujType int mAttrCujType;
+            private final @Cuj.CujType int mAttrCujType;
             private boolean mAttrDeferMonitor = true;
 
             /**
              * Creates a builder which instruments only surface.
-             * @param cuj The enum defined in {@link InteractionJankMonitor.CujType}.
+             * @param cuj The enum defined in {@link Cuj.CujType}.
              * @param context context
              * @param surfaceControl surface control
              * @return builder
              */
-            public static Builder withSurface(@CujType int cuj, @NonNull Context context,
+            public static Builder withSurface(@Cuj.CujType int cuj, @NonNull Context context,
                     @NonNull SurfaceControl surfaceControl) {
                 return new Builder(cuj)
                         .setContext(context)
@@ -1187,16 +654,17 @@
 
             /**
              * Creates a builder which instruments both surface and view.
-             * @param cuj The enum defined in {@link InteractionJankMonitor.CujType}.
+             * @param cuj The enum defined in {@link Cuj.CujType}.
              * @param view view
              * @return builder
              */
-            public static Builder withView(@CujType int cuj, @NonNull View view) {
-                return new Builder(cuj).setView(view)
+            public static Builder withView(@Cuj.CujType int cuj, @NonNull View view) {
+                return new Builder(cuj)
+                        .setView(view)
                         .setContext(view.getContext());
             }
 
-            private Builder(@CujType int cuj) {
+            private Builder(@Cuj.CujType int cuj) {
                 mAttrCujType = cuj;
             }
 
@@ -1281,11 +749,12 @@
             }
         }
 
-        private Configuration(@CujType int cuj, View view, String tag, long timeout,
+        private Configuration(@Cuj.CujType int cuj, View view, @NonNull String tag, long timeout,
                 boolean surfaceOnly, Context context, SurfaceControl surfaceControl,
                 boolean deferMonitor) {
             mCujType = cuj;
             mTag = tag;
+            mSessionName = generateSessionName(Cuj.getNameOfCuj(cuj), tag);
             mTimeout = timeout;
             mView = view;
             mSurfaceOnly = surfaceOnly;
@@ -1298,6 +767,23 @@
             mHandler = mSurfaceOnly ? mContext.getMainThreadHandler() : mView.getHandler();
         }
 
+        @VisibleForTesting
+        public static String generateSessionName(
+                @NonNull String cujName, @NonNull String cujPostfix) {
+            final boolean hasPostfix = !TextUtils.isEmpty(cujPostfix);
+            if (hasPostfix) {
+                final int remaining = MAX_LENGTH_SESSION_NAME - cujName.length();
+                if (cujPostfix.length() > remaining) {
+                    cujPostfix = cujPostfix.substring(0, remaining - 3).concat("...");
+                }
+            }
+            // The max length of the whole string should be:
+            // 105 with postfix, 83 without postfix
+            return hasPostfix
+                    ? TextUtils.formatSimple("J<%s::%s>", cujName, cujPostfix)
+                    : TextUtils.formatSimple("J<%s>", cujName);
+        }
+
         private void validate() {
             boolean shouldThrow = false;
             final StringBuilder msg = new StringBuilder();
@@ -1360,10 +846,10 @@
             return mSurfaceControl;
         }
 
-        @VisibleForTesting
         /**
          * @return a view which is attached to the view tree.
          */
+        @VisibleForTesting
         public View getView() {
             return mView;
         }
@@ -1375,7 +861,7 @@
             return mDeferMonitor;
         }
 
-        @VisibleForTesting
+        @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
         public Handler getHandler() {
             return mHandler;
         }
@@ -1387,79 +873,27 @@
         public int getDisplayId() {
             return (mSurfaceOnly ? mContext : mView.getContext()).getDisplayId();
         }
-    }
 
-    /**
-     * A class to represent a session.
-     */
-    public static class Session {
-        @CujType
-        private final int mCujType;
-        private final long mTimeStamp;
-        @Reasons
-        private int mReason = REASON_END_UNKNOWN;
-        private final String mName;
-
-        public Session(@CujType int cujType, @NonNull String postfix) {
-            mCujType = cujType;
-            mTimeStamp = System.nanoTime();
-            mName = generateSessionName(getNameOfCuj(cujType), postfix);
-        }
-
-        private String generateSessionName(@NonNull String cujName, @NonNull String cujPostfix) {
-            final boolean hasPostfix = !TextUtils.isEmpty(cujPostfix);
-            // We assert that the cujName shouldn't exceed MAX_LENGTH_OF_CUJ_NAME.
-            if (cujName.length() > MAX_LENGTH_OF_CUJ_NAME) {
-                throw new IllegalArgumentException(TextUtils.formatSimple(
-                        "The length of cuj name <%s> exceeds %d", cujName, MAX_LENGTH_OF_CUJ_NAME));
-            }
-            if (hasPostfix) {
-                final int remaining = MAX_LENGTH_SESSION_NAME - cujName.length();
-                if (cujPostfix.length() > remaining) {
-                    cujPostfix = cujPostfix.substring(0, remaining - 3).concat("...");
-                }
-            }
-            // The max length of the whole string should be:
-            // 105 with postfix, 83 without postfix
-            return hasPostfix
-                    ? TextUtils.formatSimple("J<%s::%s>", cujName, cujPostfix)
-                    : TextUtils.formatSimple("J<%s>", cujName);
-        }
-
-        @CujType
-        public int getCuj() {
-            return mCujType;
+        public String getSessionName() {
+            return mSessionName;
         }
 
         public int getStatsdInteractionType() {
-            return CUJ_TO_STATSD_INTERACTION_TYPE[mCujType];
+            return Cuj.getStatsdInteractionType(mCujType);
         }
 
         /** Describes whether the measurement from this session should be written to statsd. */
         public boolean logToStatsd() {
-            return getStatsdInteractionType() != NO_STATSD_LOGGING;
+            return Cuj.logToStatsd(mCujType);
         }
 
         public String getPerfettoTrigger() {
-            return String.format(Locale.US, "com.android.telemetry.interaction-jank-monitor-%d",
-                    mCujType);
+            return TextUtils.formatSimple(
+                    "com.android.telemetry.interaction-jank-monitor-%d", mCujType);
         }
 
-        public String getName() {
-            return mName;
-        }
-
-        public long getTimeStamp() {
-            return mTimeStamp;
-        }
-
-        public void setReason(@Reasons int reason) {
-            mReason = reason;
-        }
-
-        @Reasons
-        public int getReason() {
-            return mReason;
+        public @Cuj.CujType int getCujType() {
+            return mCujType;
         }
     }
 
@@ -1468,15 +902,15 @@
         void invoke(long unixNanos, long elapsedNanos, long realtimeNanos);
     }
 
-    private void postEventLogToWorkerThread(TimeFunction logFunction) {
-        final Instant now = Instant.now();
-        final long unixNanos = TimeUnit.NANOSECONDS.convert(now.getEpochSecond(), TimeUnit.SECONDS)
-                + now.getNano();
-        final long elapsedNanos = SystemClock.elapsedRealtimeNanos();
-        final long realtimeNanos = SystemClock.uptimeNanos();
+    static class RunningTracker {
+        public final Configuration mConfig;
+        public final FrameTracker mTracker;
+        public final Runnable mTimeoutAction;
 
-        mWorker.getThreadHandler().post(() -> {
-            logFunction.invoke(unixNanos, elapsedNanos, realtimeNanos);
-        });
+        RunningTracker(Configuration config, FrameTracker tracker, Runnable timeoutAction) {
+            this.mConfig = config;
+            this.mTracker = tracker;
+            this.mTimeoutAction = timeoutAction;
+        }
     }
 }
diff --git a/core/java/com/android/internal/jank/InteractionMonitorDebugOverlay.java b/core/java/com/android/internal/jank/InteractionMonitorDebugOverlay.java
index ef7944c..f3f16a0 100644
--- a/core/java/com/android/internal/jank/InteractionMonitorDebugOverlay.java
+++ b/core/java/com/android/internal/jank/InteractionMonitorDebugOverlay.java
@@ -34,7 +34,6 @@
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.jank.FrameTracker.Reasons;
-import com.android.internal.jank.InteractionJankMonitor.CujType;
 
 /**
  * An overlay that uses WindowCallbacks to draw the names of all running CUJs to the window
@@ -94,14 +93,14 @@
     }
 
     @UiThread
-    private boolean attachViewRootIfNeeded(FrameTracker tracker) {
-        FrameTracker.ViewRootWrapper viewRoot = tracker.getViewRoot();
+    private boolean attachViewRootIfNeeded(InteractionJankMonitor.RunningTracker tracker) {
+        FrameTracker.ViewRootWrapper viewRoot = tracker.mTracker.getViewRoot();
         if (mViewRoot == null && viewRoot != null) {
             // Add a trace marker so we can identify traces that were captured while the debug
             // overlay was enabled. Traces that use the debug overlay should NOT be used for
             // performance analysis.
             Trace.asyncTraceForTrackBegin(Trace.TRACE_TAG_APP, TRACK_NAME, "DEBUG_OVERLAY_DRAW", 0);
-            mHandler = tracker.getHandler();
+            mHandler = tracker.mConfig.getHandler();
             mViewRoot = viewRoot;
             mHandler.runWithScissors(() -> viewRoot.addWindowCallbacks(this),
                     InteractionJankMonitor.EXECUTOR_TASK_TIMEOUT);
@@ -111,11 +110,12 @@
         return false;
     }
 
+    @GuardedBy("mLock")
     private float getWidthOfLongestCujName(int cujFontSize) {
         mDebugPaint.setTextSize(cujFontSize);
         float maxLength = 0;
         for (int i = 0; i < mRunningCujs.size(); i++) {
-            String cujName = InteractionJankMonitor.getNameOfCuj(mRunningCujs.keyAt(i));
+            String cujName = Cuj.getNameOfCuj(mRunningCujs.keyAt(i));
             float textLength = mDebugPaint.measureText(cujName);
             if (textLength > maxLength) {
                 maxLength = textLength;
@@ -149,8 +149,8 @@
     }
 
     @UiThread
-    void onTrackerRemoved(@CujType int removedCuj, @Reasons int reason,
-                          SparseArray<FrameTracker> runningTrackers) {
+    void onTrackerRemoved(@Cuj.CujType int removedCuj, @Reasons int reason,
+                          SparseArray<InteractionJankMonitor.RunningTracker> runningTrackers) {
         synchronized (mLock) {
             mRunningCujs.put(removedCuj, reason);
             // If REASON_STILL_RUNNING is not in mRunningCujs, then all CUJs have ended
@@ -164,7 +164,7 @@
                     // trackers
                     for (int i = 0; i < runningTrackers.size(); i++) {
                         if (mViewRoot.equals(
-                                runningTrackers.valueAt(i).getViewRoot())) {
+                                runningTrackers.valueAt(i).mTracker.getViewRoot())) {
                             needsNewViewRoot = false;
                             break;
                         }
@@ -185,7 +185,7 @@
     }
 
     @UiThread
-    void onTrackerAdded(@CujType int addedCuj, FrameTracker tracker) {
+    void onTrackerAdded(@Cuj.CujType int addedCuj, InteractionJankMonitor.RunningTracker tracker) {
         synchronized (mLock) {
             // Use REASON_STILL_RUNNING (not technically one of the '@Reasons') to indicate the CUJ
             // is still running
@@ -230,41 +230,44 @@
         int cujFontSize = dipToPx(18);
         final float cujNameTextHeight = getTextHeight(cujFontSize);
         final float packageNameTextHeight = getTextHeight(packageNameFontSize);
-        float maxLength = getWidthOfLongestCujName(cujFontSize);
 
-        final int dx = (int) ((w - maxLength) / 2f);
-        canvas.translate(dx, dy);
-        // Draw background rectangle for displaying the text showing the CUJ name
-        mDebugPaint.setColor(mBgColor);
-        canvas.drawRect(
-                -padding * 2, // more padding on top so we can draw the package name
-                -padding,
-                padding * 2 + maxLength,
-                padding * 2 + packageNameTextHeight + cujNameTextHeight * mRunningCujs.size(),
-                mDebugPaint);
-        mDebugPaint.setTextSize(packageNameFontSize);
-        mDebugPaint.setColor(Color.BLACK);
-        mDebugPaint.setStrikeThruText(false);
-        canvas.translate(0, packageNameTextHeight);
-        canvas.drawText("package:" + mPackageName, 0, 0, mDebugPaint);
-        mDebugPaint.setTextSize(cujFontSize);
-        // Draw text for CUJ names
-        for (int i = 0; i < mRunningCujs.size(); i++) {
-            int status = mRunningCujs.valueAt(i);
-            if (status == REASON_STILL_RUNNING) {
-                mDebugPaint.setColor(Color.BLACK);
-                mDebugPaint.setStrikeThruText(false);
-            } else if (status == REASON_END_NORMAL) {
-                mDebugPaint.setColor(Color.GRAY);
-                mDebugPaint.setStrikeThruText(false);
-            } else {
-                // Cancelled, or otherwise ended for a bad reason
-                mDebugPaint.setColor(Color.RED);
-                mDebugPaint.setStrikeThruText(true);
+        synchronized (mLock) {
+            float maxLength = getWidthOfLongestCujName(cujFontSize);
+
+            final int dx = (int) ((w - maxLength) / 2f);
+            canvas.translate(dx, dy);
+            // Draw background rectangle for displaying the text showing the CUJ name
+            mDebugPaint.setColor(mBgColor);
+            canvas.drawRect(
+                    -padding * 2, // more padding on top so we can draw the package name
+                    -padding,
+                    padding * 2 + maxLength,
+                    padding * 2 + packageNameTextHeight + cujNameTextHeight * mRunningCujs.size(),
+                    mDebugPaint);
+            mDebugPaint.setTextSize(packageNameFontSize);
+            mDebugPaint.setColor(Color.BLACK);
+            mDebugPaint.setStrikeThruText(false);
+            canvas.translate(0, packageNameTextHeight);
+            canvas.drawText("package:" + mPackageName, 0, 0, mDebugPaint);
+            mDebugPaint.setTextSize(cujFontSize);
+            // Draw text for CUJ names
+            for (int i = 0; i < mRunningCujs.size(); i++) {
+                int status = mRunningCujs.valueAt(i);
+                if (status == REASON_STILL_RUNNING) {
+                    mDebugPaint.setColor(Color.BLACK);
+                    mDebugPaint.setStrikeThruText(false);
+                } else if (status == REASON_END_NORMAL) {
+                    mDebugPaint.setColor(Color.GRAY);
+                    mDebugPaint.setStrikeThruText(false);
+                } else {
+                    // Cancelled, or otherwise ended for a bad reason
+                    mDebugPaint.setColor(Color.RED);
+                    mDebugPaint.setStrikeThruText(true);
+                }
+                String cujName = Cuj.getNameOfCuj(mRunningCujs.keyAt(i));
+                canvas.translate(0, cujNameTextHeight);
+                canvas.drawText(cujName, 0, 0, mDebugPaint);
             }
-            String cujName = InteractionJankMonitor.getNameOfCuj(mRunningCujs.keyAt(i));
-            canvas.translate(0, cujNameTextHeight);
-            canvas.drawText(cujName, 0, 0, mDebugPaint);
         }
     }
 }
diff --git a/core/java/com/android/internal/os/LongArrayMultiStateCounter.java b/core/java/com/android/internal/os/LongArrayMultiStateCounter.java
index 1f44b33..ed943cb 100644
--- a/core/java/com/android/internal/os/LongArrayMultiStateCounter.java
+++ b/core/java/com/android/internal/os/LongArrayMultiStateCounter.java
@@ -55,15 +55,20 @@
  *
  * @hide
  */
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
+@android.ravenwood.annotation.RavenwoodNativeSubstitutionClass(
+        "com.android.hoststubgen.nativesubstitution.LongArrayMultiStateCounter_host")
 public final class LongArrayMultiStateCounter implements Parcelable {
 
     /**
      * Container for a native equivalent of a long[].
      */
+    @android.ravenwood.annotation.RavenwoodKeepWholeClass
+    @android.ravenwood.annotation.RavenwoodNativeSubstitutionClass(
+            "com.android.hoststubgen.nativesubstitution"
+            + ".LongArrayMultiStateCounter_host$LongArrayContainer_host")
     public static class LongArrayContainer {
-        private static final NativeAllocationRegistry sRegistry =
-                NativeAllocationRegistry.createMalloced(
-                        LongArrayContainer.class.getClassLoader(), native_getReleaseFunc());
+        private static NativeAllocationRegistry sRegistry;
 
         // Visible to other objects in this package so that it can be passed to @CriticalNative
         // methods.
@@ -73,9 +78,26 @@
         public LongArrayContainer(int length) {
             mLength = length;
             mNativeObject = native_init(length);
+            registerNativeAllocation();
+        }
+
+        @android.ravenwood.annotation.RavenwoodReplace
+        private void registerNativeAllocation() {
+            if (sRegistry == null) {
+                synchronized (LongArrayMultiStateCounter.class) {
+                    if (sRegistry == null) {
+                        sRegistry = NativeAllocationRegistry.createMalloced(
+                                LongArrayContainer.class.getClassLoader(), native_getReleaseFunc());
+                    }
+                }
+            }
             sRegistry.registerNativeAllocation(this, mNativeObject);
         }
 
+        private void registerNativeAllocation$ravenwood() {
+            // No-op under ravenwood
+        }
+
         /**
          * Copies the supplied values into the underlying native array.
          */
@@ -124,19 +146,17 @@
         private static native long native_getReleaseFunc();
 
         @FastNative
-        private native void native_setValues(long nativeObject, long[] array);
+        private static native void native_setValues(long nativeObject, long[] array);
 
         @FastNative
-        private native void native_getValues(long nativeObject, long[] array);
+        private static native void native_getValues(long nativeObject, long[] array);
 
         @FastNative
-        private native boolean native_combineValues(long nativeObject, long[] array,
+        private static native boolean native_combineValues(long nativeObject, long[] array,
                 int[] indexMap);
     }
 
-    private static final NativeAllocationRegistry sRegistry =
-            NativeAllocationRegistry.createMalloced(
-                    LongArrayMultiStateCounter.class.getClassLoader(), native_getReleaseFunc());
+    private static volatile NativeAllocationRegistry sRegistry;
     private static final AtomicReference<LongArrayContainer> sTmpArrayContainer =
             new AtomicReference<>();
 
@@ -152,12 +172,30 @@
         mStateCount = stateCount;
         mLength = arrayLength;
         mNativeObject = native_init(stateCount, arrayLength);
+        registerNativeAllocation();
+    }
+
+    @android.ravenwood.annotation.RavenwoodReplace
+    private void registerNativeAllocation() {
+        if (sRegistry == null) {
+            synchronized (LongArrayMultiStateCounter.class) {
+                if (sRegistry == null) {
+                    sRegistry = NativeAllocationRegistry.createMalloced(
+                            LongArrayMultiStateCounter.class.getClassLoader(),
+                            native_getReleaseFunc());
+                }
+            }
+        }
         sRegistry.registerNativeAllocation(this, mNativeObject);
     }
 
+    private void registerNativeAllocation$ravenwood() {
+        // No-op under ravenwood
+    }
+
     private LongArrayMultiStateCounter(Parcel in) {
         mNativeObject = native_initFromParcel(in);
-        sRegistry.registerNativeAllocation(this, mNativeObject);
+        registerNativeAllocation();
 
         mStateCount = native_getStateCount(mNativeObject);
         mLength = native_getArrayLength(mNativeObject);
@@ -361,10 +399,10 @@
             long longArrayContainerNativeObject, int state);
 
     @FastNative
-    private native String native_toString(long nativeObject);
+    private static native String native_toString(long nativeObject);
 
     @FastNative
-    private native void native_writeToParcel(long nativeObject, Parcel dest, int flags);
+    private static native void native_writeToParcel(long nativeObject, Parcel dest, int flags);
 
     @FastNative
     private static native long native_initFromParcel(Parcel parcel);
diff --git a/core/java/com/android/internal/pm/pkg/parsing/ParsingPackage.java b/core/java/com/android/internal/pm/pkg/parsing/ParsingPackage.java
index 4ed361f..6c09b7c 100644
--- a/core/java/com/android/internal/pm/pkg/parsing/ParsingPackage.java
+++ b/core/java/com/android/internal/pm/pkg/parsing/ParsingPackage.java
@@ -112,7 +112,7 @@
     ParsingPackage addUsesOptionalNativeLibrary(String libraryName);
 
     ParsingPackage addUsesSdkLibrary(String libraryName, long versionMajor,
-            String[] certSha256Digests);
+            String[] certSha256Digests, boolean usesSdkLibrariesOptional);
 
     ParsingPackage addUsesStaticLibrary(String libraryName, long version,
             String[] certSha256Digests);
diff --git a/core/java/com/android/internal/protolog/ProtoLogGroup.java b/core/java/com/android/internal/protolog/ProtoLogGroup.java
index 4bb7c33..8c2a525 100644
--- a/core/java/com/android/internal/protolog/ProtoLogGroup.java
+++ b/core/java/com/android/internal/protolog/ProtoLogGroup.java
@@ -93,6 +93,7 @@
     WM_DEBUG_DREAM(Consts.ENABLE_DEBUG, Consts.ENABLE_LOG_TO_PROTO_DEBUG, true, Consts.TAG_WM),
 
     WM_DEBUG_DIMMER(Consts.ENABLE_DEBUG, Consts.ENABLE_LOG_TO_PROTO_DEBUG, false, Consts.TAG_WM),
+    WM_DEBUG_TPL(Consts.ENABLE_DEBUG, Consts.ENABLE_LOG_TO_PROTO_DEBUG, false, Consts.TAG_WM),
     TEST_GROUP(true, true, false, "WindowManagerProtoLogTest");
 
     private final boolean mEnabled;
diff --git a/core/java/com/android/server/pm/pkg/AndroidPackage.java b/core/java/com/android/server/pm/pkg/AndroidPackage.java
index 4e4f26c..f86595f 100644
--- a/core/java/com/android/server/pm/pkg/AndroidPackage.java
+++ b/core/java/com/android/server/pm/pkg/AndroidPackage.java
@@ -1340,6 +1340,15 @@
     @Nullable
     long[] getUsesSdkLibrariesVersionsMajor();
 
+
+    /**
+     * @see R.styleable#AndroidManifestUsesSdkLibrary_optional
+     * @hide
+     */
+    @Immutable.Ignore
+    @Nullable
+    boolean[] getUsesSdkLibrariesOptional();
+
     /**
      * TODO(b/135203078): Move static library stuff to an inner data class
      *
diff --git a/core/jni/Android.bp b/core/jni/Android.bp
index f365dbb..2a744e3 100644
--- a/core/jni/Android.bp
+++ b/core/jni/Android.bp
@@ -49,8 +49,6 @@
         "-Wno-unused-parameter",
         "-Wunused",
         "-Wunreachable-code",
-
-        "-DNAMESPACE_FOR_HASH_FUNCTIONS=farmhash",
     ],
 
     cppflags: ["-Wno-conversion-null"],
@@ -284,8 +282,6 @@
                 "libscrypt_static",
                 "libstatssocket_lazy",
                 "libskia",
-                "libtextclassifier_hash_static",
-                "libexpresslog_jni",
             ],
 
             shared_libs: [
@@ -372,7 +368,6 @@
                 "bionic_libc_platform_headers",
                 "dnsproxyd_protocol_headers",
                 "flatbuffer_headers",
-                "libtextclassifier_hash_headers",
                 "tensorflow_headers",
             ],
             runtime_libs: [
diff --git a/core/jni/AndroidRuntime.cpp b/core/jni/AndroidRuntime.cpp
index 50253cf..c24d21d 100644
--- a/core/jni/AndroidRuntime.cpp
+++ b/core/jni/AndroidRuntime.cpp
@@ -201,7 +201,6 @@
 extern int register_com_android_internal_content_NativeLibraryHelper(JNIEnv *env);
 extern int register_com_android_internal_content_om_OverlayConfig(JNIEnv *env);
 extern int register_com_android_internal_content_om_OverlayManagerImpl(JNIEnv* env);
-extern int register_com_android_modules_expresslog_Utils(JNIEnv* env);
 extern int register_com_android_internal_net_NetworkUtilsInternal(JNIEnv* env);
 extern int register_com_android_internal_os_ClassLoaderFactory(JNIEnv* env);
 extern int register_com_android_internal_os_FuseAppLoop(JNIEnv* env);
@@ -1590,7 +1589,6 @@
         REG_JNI(register_android_os_incremental_IncrementalManager),
         REG_JNI(register_com_android_internal_content_om_OverlayConfig),
         REG_JNI(register_com_android_internal_content_om_OverlayManagerImpl),
-        REG_JNI(register_com_android_modules_expresslog_Utils),
         REG_JNI(register_com_android_internal_net_NetworkUtilsInternal),
         REG_JNI(register_com_android_internal_os_ClassLoaderFactory),
         REG_JNI(register_com_android_internal_os_LongArrayMultiStateCounter),
diff --git a/core/jni/android_os_Debug.cpp b/core/jni/android_os_Debug.cpp
index e0bcef6..de1ce4e 100644
--- a/core/jni/android_os_Debug.cpp
+++ b/core/jni/android_os_Debug.cpp
@@ -1014,6 +1014,10 @@
     return cfg_state == CONFIG_SET;
 }
 
+static jboolean android_os_Debug_logAllocatorStats(JNIEnv*, jobject) {
+    return mallopt(M_LOG_STATS, 0) == 1 ? JNI_TRUE : JNI_FALSE;
+}
+
 /*
  * JNI registration.
  */
@@ -1056,6 +1060,7 @@
         {"getDmabufHeapPoolsSizeKb", "()J", (void*)android_os_Debug_getDmabufHeapPoolsSizeKb},
         {"getGpuTotalUsageKb", "()J", (void*)android_os_Debug_getGpuTotalUsageKb},
         {"isVmapStack", "()Z", (void*)android_os_Debug_isVmapStack},
+        {"logAllocatorStats", "()Z", (void*)android_os_Debug_logAllocatorStats},
 };
 
 int register_android_os_Debug(JNIEnv *env)
diff --git a/core/jni/com_android_internal_os_LongArrayMultiStateCounter.cpp b/core/jni/com_android_internal_os_LongArrayMultiStateCounter.cpp
index dab47e9..76b05ea 100644
--- a/core/jni/com_android_internal_os_LongArrayMultiStateCounter.cpp
+++ b/core/jni/com_android_internal_os_LongArrayMultiStateCounter.cpp
@@ -107,7 +107,7 @@
     *vector = counter->getCount(state);
 }
 
-static jobject native_toString(JNIEnv *env, jobject self, jlong nativePtr) {
+static jobject native_toString(JNIEnv *env, jclass, jlong nativePtr) {
     battery::LongArrayMultiStateCounter *counter =
             reinterpret_cast<battery::LongArrayMultiStateCounter *>(nativePtr);
     return env->NewStringUTF(counter->toString().c_str());
@@ -127,7 +127,7 @@
         }                                     \
     }
 
-static void native_writeToParcel(JNIEnv *env, jobject self, jlong nativePtr, jobject jParcel,
+static void native_writeToParcel(JNIEnv *env, jclass, jlong nativePtr, jobject jParcel,
                                  jint flags) {
     battery::LongArrayMultiStateCounter *counter =
             reinterpret_cast<battery::LongArrayMultiStateCounter *>(nativePtr);
@@ -161,7 +161,7 @@
         }                                    \
     }
 
-static jlong native_initFromParcel(JNIEnv *env, jclass theClass, jobject jParcel) {
+static jlong native_initFromParcel(JNIEnv *env, jclass, jobject jParcel) {
     ndk::ScopedAParcel parcel(AParcel_fromJavaParcel(env, jParcel));
 
     int32_t stateCount;
@@ -253,7 +253,7 @@
     return reinterpret_cast<jlong>(native_dispose_LongArrayContainer);
 }
 
-static void native_setValues_LongArrayContainer(JNIEnv *env, jobject self, jlong nativePtr,
+static void native_setValues_LongArrayContainer(JNIEnv *env, jclass, jlong nativePtr,
                                                 jlongArray jarray) {
     std::vector<uint64_t> *vector = reinterpret_cast<std::vector<uint64_t> *>(nativePtr);
     ScopedLongArrayRO scopedArray(env, jarray);
@@ -264,7 +264,7 @@
     std::copy(array, array + size, vector->data());
 }
 
-static void native_getValues_LongArrayContainer(JNIEnv *env, jobject self, jlong nativePtr,
+static void native_getValues_LongArrayContainer(JNIEnv *env, jclass, jlong nativePtr,
                                                 jlongArray jarray) {
     std::vector<uint64_t> *vector = reinterpret_cast<std::vector<uint64_t> *>(nativePtr);
     ScopedLongArrayRW scopedArray(env, jarray);
@@ -273,7 +273,7 @@
     std::copy(vector->data(), vector->data() + vector->size(), scopedArray.get());
 }
 
-static jboolean native_combineValues_LongArrayContainer(JNIEnv *env, jobject self, jlong nativePtr,
+static jboolean native_combineValues_LongArrayContainer(JNIEnv *env, jclass, jlong nativePtr,
                                                         jlongArray jarray, jintArray jindexMap) {
     std::vector<uint64_t> *vector = reinterpret_cast<std::vector<uint64_t> *>(nativePtr);
     ScopedLongArrayRW scopedArray(env, jarray);
diff --git a/core/res/res/values/attrs_manifest.xml b/core/res/res/values/attrs_manifest.xml
index f8546b7..596cfe5 100644
--- a/core/res/res/values/attrs_manifest.xml
+++ b/core/res/res/values/attrs_manifest.xml
@@ -2515,6 +2515,10 @@
         <attr name="versionMajor" format="integer" />
         <!-- The SHA-256 digest of the SDK library signing certificate. -->
         <attr name="certDigest" format="string" />
+        <!-- Specify whether the SDK is optional. The default is false, false means app can be
+        installed even if the SDK library doesn't exist, and the SDK library can be uninstalled
+        when the app is still installed. -->
+        <attr name="optional" format="boolean" />
     </declare-styleable>
 
     <!-- The <code>static-library</code> tag declares that this apk is providing itself
diff --git a/core/res/res/values/public-staging.xml b/core/res/res/values/public-staging.xml
index b12f302..f10e7f8 100644
--- a/core/res/res/values/public-staging.xml
+++ b/core/res/res/values/public-staging.xml
@@ -115,6 +115,8 @@
     <!-- @FlaggedApi("android.companion.virtual.flags.vdm_custom_ime")
          @hide @SystemApi -->
     <public name="isVirtualDeviceOnly"/>
+    <!-- @FlaggedApi("android.content.pm.sdk_lib_independence") -->
+    <public name="optional"/>
   </staging-public-group>
 
   <staging-public-group type="id" first-id="0x01bc0000">
diff --git a/core/tests/coretests/Android.bp b/core/tests/coretests/Android.bp
index 0ad349b..6706c91 100644
--- a/core/tests/coretests/Android.bp
+++ b/core/tests/coretests/Android.bp
@@ -179,10 +179,13 @@
         "androidx.test.ext.junit",
         "mockito_ravenwood",
         "platform-test-annotations",
+        "flag-junit",
     ],
     srcs: [
+        "src/android/os/BuildTest.java",
         "src/android/os/FileUtilsTest.java",
         "src/android/util/**/*.java",
+        "src/com/android/internal/os/LongArrayMultiStateCounterTest.java",
         "src/com/android/internal/util/**/*.java",
         "testdoubles/src/com/android/internal/util/**/*.java",
     ],
diff --git a/core/tests/coretests/src/android/os/BuildTest.java b/core/tests/coretests/src/android/os/BuildTest.java
index 2295eb9..3162e6d 100644
--- a/core/tests/coretests/src/android/os/BuildTest.java
+++ b/core/tests/coretests/src/android/os/BuildTest.java
@@ -16,19 +16,37 @@
 
 package android.os;
 
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+import android.platform.test.annotations.IgnoreUnderRavenwood;
+import android.platform.test.flag.junit.SetFlagsRule;
+import android.platform.test.ravenwood.RavenwoodRule;
+
 import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
 
 import junit.framework.Assert;
-import junit.framework.TestCase;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
 
 /**
  * Provides test cases for android.os.Build and, in turn, many of the
  * system properties set by the build system.
  */
-public class BuildTest extends TestCase {
-
+@RunWith(AndroidJUnit4.class)
+public class BuildTest {
     private static final String TAG = "BuildTest";
 
+    @Rule
+    public final RavenwoodRule mRavenwood = new RavenwoodRule();
+
+    @Rule
+    public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
+
     /**
      * Asserts that a String is non-null and non-empty.  If it is not,
      * an AssertionFailedError is thrown with the given message.
@@ -50,7 +68,9 @@
     /**
      * Asserts that all android.os.Build fields are non-empty and/or in a valid range.
      */
+    @Test
     @SmallTest
+    @IgnoreUnderRavenwood(blockedBy = Build.class)
     public void testBuildFields() throws Exception {
         assertNotEmpty("ID", Build.ID);
         assertNotEmpty("DISPLAY", Build.DISPLAY);
@@ -72,4 +92,16 @@
         // (e.g., must be a C identifier, must be a valid filename, must not contain any spaces)
         // add tests for them.
     }
+
+    @Test
+    public void testFlagEnabled() throws Exception {
+        mSetFlagsRule.enableFlags(Flags.FLAG_ANDROID_OS_BUILD_VANILLA_ICE_CREAM);
+        assertTrue(Flags.androidOsBuildVanillaIceCream());
+    }
+
+    @Test
+    public void testFlagDisabled() throws Exception {
+        mSetFlagsRule.disableFlags(Flags.FLAG_ANDROID_OS_BUILD_VANILLA_ICE_CREAM);
+        assertFalse(Flags.androidOsBuildVanillaIceCream());
+    }
 }
diff --git a/core/tests/coretests/src/android/provider/DeviceConfigTest.java b/core/tests/coretests/src/android/provider/DeviceConfigTest.java
index 7c4136d..9300d1e 100644
--- a/core/tests/coretests/src/android/provider/DeviceConfigTest.java
+++ b/core/tests/coretests/src/android/provider/DeviceConfigTest.java
@@ -831,6 +831,7 @@
     }
 
     @Test
+    @FlakyTest(bugId = 299483542)
     public void onPropertiesChangedListener_setPropertiesCallback() {
         DeviceConfig.setProperty(NAMESPACE, KEY, VALUE, false);
         DeviceConfig.setProperty(NAMESPACE, KEY2, VALUE2, false);
diff --git a/core/tests/coretests/src/android/view/ViewRootImplTest.java b/core/tests/coretests/src/android/view/ViewRootImplTest.java
index 6172622..b30a0c8 100644
--- a/core/tests/coretests/src/android/view/ViewRootImplTest.java
+++ b/core/tests/coretests/src/android/view/ViewRootImplTest.java
@@ -472,6 +472,7 @@
      * Test the value of the frame rate cateogry based on the visibility of a view
      * Invsible: FRAME_RATE_CATEGORY_NO_PREFERENCE
      * Visible: FRAME_RATE_CATEGORY_NORMAL
+     * Also, mIsFrameRateBoosting should be true when the visibility becomes visible
      */
     @Test
     @RequiresFlagsEnabled(FLAG_TOOLKIT_SET_FRAME_RATE_READ_ONLY)
@@ -485,6 +486,7 @@
             assertEquals(viewRootImpl.getPreferredFrameRateCategory(),
                     FRAME_RATE_CATEGORY_NO_PREFERENCE);
         });
+        sInstrumentation.waitForIdleSync();
 
         sInstrumentation.runOnMainSync(() -> {
             view.setVisibility(View.VISIBLE);
@@ -492,6 +494,11 @@
             assertEquals(viewRootImpl.getPreferredFrameRateCategory(),
                     FRAME_RATE_CATEGORY_NORMAL);
         });
+        sInstrumentation.waitForIdleSync();
+
+        sInstrumentation.runOnMainSync(() -> {
+            assertEquals(viewRootImpl.getIsFrameRateBoosting(), true);
+        });
     }
 
     /**
diff --git a/core/tests/coretests/src/com/android/internal/jank/CujTest.java b/core/tests/coretests/src/com/android/internal/jank/CujTest.java
new file mode 100644
index 0000000..bf35ed0
--- /dev/null
+++ b/core/tests/coretests/src/com/android/internal/jank/CujTest.java
@@ -0,0 +1,156 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.jank;
+
+import static android.text.TextUtils.formatSimple;
+
+import static com.android.internal.jank.Cuj.getNameOfCuj;
+
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.internal.util.FrameworkStatsLog;
+
+import com.google.common.truth.Expect;
+
+import org.junit.Rule;
+import org.junit.Test;
+
+import java.lang.reflect.Field;
+import java.lang.reflect.Modifier;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+@SmallTest
+public class CujTest {
+    private static final String ENUM_NAME_PREFIX =
+            "UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__";
+    private static final Set<String> DEPRECATED_VALUES = new HashSet<>() {
+        {
+            add(ENUM_NAME_PREFIX + "IME_INSETS_ANIMATION");
+        }
+    };
+    private static final Map<Integer, String> ENUM_NAME_EXCEPTION_MAP = new HashMap<>() {
+        {
+            put(Cuj.CUJ_NOTIFICATION_ADD, getEnumName("SHADE_NOTIFICATION_ADD"));
+            put(Cuj.CUJ_NOTIFICATION_HEADS_UP_APPEAR, getEnumName("SHADE_HEADS_UP_APPEAR"));
+            put(Cuj.CUJ_NOTIFICATION_APP_START, getEnumName("SHADE_APP_LAUNCH"));
+            put(Cuj.CUJ_NOTIFICATION_HEADS_UP_DISAPPEAR, getEnumName("SHADE_HEADS_UP_DISAPPEAR"));
+            put(Cuj.CUJ_NOTIFICATION_REMOVE, getEnumName("SHADE_NOTIFICATION_REMOVE"));
+            put(Cuj.CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE, getEnumName("NOTIFICATION_SHADE_SWIPE"));
+            put(Cuj.CUJ_NOTIFICATION_SHADE_QS_EXPAND_COLLAPSE, getEnumName("SHADE_QS_EXPAND_COLLAPSE"));
+            put(Cuj.CUJ_NOTIFICATION_SHADE_QS_SCROLL_SWIPE, getEnumName("SHADE_QS_SCROLL_SWIPE"));
+            put(Cuj.CUJ_NOTIFICATION_SHADE_ROW_EXPAND, getEnumName("SHADE_ROW_EXPAND"));
+            put(Cuj.CUJ_NOTIFICATION_SHADE_ROW_SWIPE, getEnumName("SHADE_ROW_SWIPE"));
+            put(Cuj.CUJ_NOTIFICATION_SHADE_SCROLL_FLING, getEnumName("SHADE_SCROLL_FLING"));
+        }
+    };
+
+    @Rule
+    public final Expect mExpect = Expect.create();
+
+    @Test
+    public void testCujNameLimit() {
+        getCujConstants().forEach(f -> {
+            final int cuj = getIntFieldChecked(f);
+            mExpect.withMessage(formatSimple("Too long CUJ(%d) name: %s", cuj, getNameOfCuj(cuj)))
+                    .that(getNameOfCuj(cuj).length())
+                    .isAtMost(Cuj.MAX_LENGTH_OF_CUJ_NAME);
+        });
+    }
+
+    @Test
+    public void testCujTypeEnumCorrectlyDefined() throws Exception {
+        List<Field> cujEnumFields = getCujConstants().toList();
+
+        HashSet<Integer> allValues = new HashSet<>();
+        for (Field field : cujEnumFields) {
+            int fieldValue = field.getInt(null);
+            assertWithMessage("All CujType values must be unique. Field %s repeats existing value.",
+                    field.getName())
+                    .that(allValues.add(fieldValue))
+                    .isTrue();
+            assertWithMessage("Field %s must have a value <= LAST_CUJ", field.getName())
+                    .that(fieldValue)
+                    .isAtMost(Cuj.LAST_CUJ);
+            assertWithMessage("Field %s must have a statsd mapping.", field.getName())
+                    .that(Cuj.logToStatsd(fieldValue))
+                    .isTrue();
+        }
+    }
+
+    @Test
+    public void testCujsMapToEnumsCorrectly() {
+        List<Field> cujs = getCujConstants().toList();
+
+        Map<Integer, String> enumsMap = Arrays.stream(FrameworkStatsLog.class.getDeclaredFields())
+                .filter(f -> f.getName().startsWith(ENUM_NAME_PREFIX)
+                        && !DEPRECATED_VALUES.contains(f.getName())
+                        && Modifier.isStatic(f.getModifiers())
+                        && f.getType() == int.class)
+                .collect(Collectors.toMap(CujTest::getIntFieldChecked, Field::getName));
+
+        assertThat(enumsMap.size() - 1).isEqualTo(cujs.size());
+
+        cujs.forEach(f -> {
+            final int cuj = getIntFieldChecked(f);
+            final String cujName = f.getName();
+            final String expectedEnumName =
+                    ENUM_NAME_EXCEPTION_MAP.getOrDefault(cuj, getEnumName(cujName.substring(4)));
+            final int enumKey = Cuj.getStatsdInteractionType(cuj);
+            final String enumName = enumsMap.get(enumKey);
+            final String expectedNameOfCuj = formatSimple("CUJ_%s", getNameOfCuj(cuj));
+
+            mExpect.withMessage(
+                    formatSimple("%s (%d) not matches %s (%d)", cujName, cuj, enumName, enumKey))
+                    .that(expectedEnumName.equals(enumName))
+                    .isTrue();
+            mExpect.withMessage(
+                    formatSimple("getNameOfCuj(%d) not matches: %s, expected=%s",
+                            cuj, cujName, expectedNameOfCuj))
+                    .that(cujName.equals(expectedNameOfCuj))
+                    .isTrue();
+        });
+    }
+
+    private static String getEnumName(String name) {
+        return formatSimple("%s%s", ENUM_NAME_PREFIX, name);
+    }
+
+    private static int getIntFieldChecked(Field field) {
+        try {
+            return field.getInt(null);
+        } catch (IllegalAccessException ex) {
+            throw new RuntimeException(ex);
+        }
+    }
+
+    private static Stream<Field> getCujConstants() {
+        return Arrays.stream(Cuj.class.getDeclaredFields())
+                .filter(f -> f.getName().startsWith("CUJ_")
+                        && Modifier.isStatic(f.getModifiers())
+                        && f.getType() == int.class);
+    }
+}
diff --git a/core/tests/coretests/src/com/android/internal/jank/FrameTrackerTest.java b/core/tests/coretests/src/com/android/internal/jank/FrameTrackerTest.java
index 1b9717a..1a7117e 100644
--- a/core/tests/coretests/src/com/android/internal/jank/FrameTrackerTest.java
+++ b/core/tests/coretests/src/com/android/internal/jank/FrameTrackerTest.java
@@ -22,9 +22,8 @@
 
 import static com.android.internal.jank.FrameTracker.SurfaceControlWrapper;
 import static com.android.internal.jank.FrameTracker.ViewRootWrapper;
-import static com.android.internal.jank.InteractionJankMonitor.CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE;
-import static com.android.internal.jank.InteractionJankMonitor.CUJ_TO_STATSD_INTERACTION_TYPE;
-import static com.android.internal.jank.InteractionJankMonitor.CUJ_WALLPAPER_TRANSITION;
+import static com.android.internal.jank.Cuj.CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE;
+import static com.android.internal.jank.Cuj.CUJ_WALLPAPER_TRANSITION;
 import static com.android.internal.util.FrameworkStatsLog.UI_INTERACTION_FRAME_INFO_REPORTED;
 
 import static com.google.common.truth.Truth.assertThat;
@@ -49,15 +48,14 @@
 import android.view.View;
 import android.view.ViewAttachTestActivity;
 
+import androidx.test.ext.junit.rules.ActivityScenarioRule;
 import androidx.test.filters.SmallTest;
-import androidx.test.rule.ActivityTestRule;
 
 import com.android.internal.jank.FrameTracker.ChoreographerWrapper;
 import com.android.internal.jank.FrameTracker.FrameMetricsWrapper;
 import com.android.internal.jank.FrameTracker.StatsLogWrapper;
 import com.android.internal.jank.FrameTracker.ThreadedRendererWrapper;
 import com.android.internal.jank.InteractionJankMonitor.Configuration;
-import com.android.internal.jank.InteractionJankMonitor.Session;
 
 import org.junit.Before;
 import org.junit.Rule;
@@ -69,14 +67,14 @@
 
 @SmallTest
 public class FrameTrackerTest {
-    private static final String CUJ_POSTFIX = "";
+    private static final String SESSION_NAME = "SessionName";
     private static final long FRAME_TIME_60Hz = (long) 1e9 / 60;
 
     private ViewAttachTestActivity mActivity;
 
     @Rule
-    public ActivityTestRule<ViewAttachTestActivity> mRule =
-            new ActivityTestRule<>(ViewAttachTestActivity.class);
+    public ActivityScenarioRule<ViewAttachTestActivity> mRule =
+            new ActivityScenarioRule<>(ViewAttachTestActivity.class);
 
     private ThreadedRendererWrapper mRenderer;
     private FrameMetricsWrapper mWrapper;
@@ -86,12 +84,13 @@
     private StatsLogWrapper mStatsLog;
     private ArgumentCaptor<OnJankDataListener> mListenerCapture;
     private SurfaceControl mSurfaceControl;
+    private FrameTracker.FrameTrackerListener mTrackerListener;
     private ArgumentCaptor<Runnable> mRunnableArgumentCaptor;
 
     @Before
     public void setup() {
         // Prepare an activity for getting ThreadedRenderer later.
-        mActivity = mRule.getActivity();
+        mRule.getScenario().onActivity(activity -> mActivity = activity);
         View view = mActivity.getWindow().getDecorView();
         assertThat(view.isAttachedToWindow()).isTrue();
 
@@ -116,36 +115,38 @@
         mChoreographer = mock(ChoreographerWrapper.class);
         mStatsLog = mock(StatsLogWrapper.class);
         mRunnableArgumentCaptor = ArgumentCaptor.forClass(Runnable.class);
+        mTrackerListener = mock(FrameTracker.FrameTrackerListener.class);
     }
 
-    private FrameTracker spyFrameTracker(int cuj, String postfix, boolean surfaceOnly) {
-        InteractionJankMonitor monitor = mock(InteractionJankMonitor.class);
-        Handler handler = mRule.getActivity().getMainThreadHandler();
-        Session session = new Session(cuj, postfix);
+    private FrameTracker spyFrameTracker(boolean surfaceOnly) {
+        Handler handler = mActivity.getMainThreadHandler();
         Configuration config = mock(Configuration.class);
+        when(config.getSessionName()).thenReturn(SESSION_NAME);
         when(config.isSurfaceOnly()).thenReturn(surfaceOnly);
         when(config.getSurfaceControl()).thenReturn(mSurfaceControl);
         when(config.shouldDeferMonitor()).thenReturn(true);
         when(config.getDisplayId()).thenReturn(42);
-        View view = mRule.getActivity().getWindow().getDecorView();
+        View view = mActivity.getWindow().getDecorView();
         Handler spyHandler = spy(new Handler(handler.getLooper()));
         when(config.getView()).thenReturn(surfaceOnly ? null : view);
         when(config.getHandler()).thenReturn(spyHandler);
+        when(config.logToStatsd()).thenReturn(true);
+        when(config.getStatsdInteractionType()).thenReturn(surfaceOnly
+                ? Cuj.getStatsdInteractionType(CUJ_WALLPAPER_TRANSITION)
+                : Cuj.getStatsdInteractionType(CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE));
         FrameTracker frameTracker = Mockito.spy(
-                new FrameTracker(monitor, session, spyHandler, mRenderer, mViewRootWrapper,
+                new FrameTracker(config, mRenderer, mViewRootWrapper,
                         mSurfaceControlWrapper, mChoreographer, mWrapper, mStatsLog,
                         /* traceThresholdMissedFrames= */ 1,
                         /* traceThresholdFrameTimeMillis= */ -1,
-                        /* FrameTrackerListener= */ null, config));
-        doNothing().when(frameTracker).triggerPerfetto();
+                        mTrackerListener));
         doNothing().when(frameTracker).postTraceStartMarker(mRunnableArgumentCaptor.capture());
         return frameTracker;
     }
 
     @Test
     public void testOnlyFirstWindowFrameOverThreshold() {
-        FrameTracker tracker = spyFrameTracker(
-                CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE, CUJ_POSTFIX, /* surfaceOnly= */ false);
+        FrameTracker tracker = spyFrameTracker(/* surfaceOnly= */ false);
 
         // Just provide current timestamp anytime mWrapper asked for VSYNC_TIMESTAMP
         when(mWrapper.getMetric(FrameMetrics.VSYNC_TIMESTAMP))
@@ -169,11 +170,11 @@
         sendFrame(tracker, 500, JANK_APP_DEADLINE_MISSED, 103L);
 
         verify(tracker).removeObservers();
-        verify(tracker, never()).triggerPerfetto();
+        verify(mTrackerListener, never()).triggerPerfetto(any());
         verify(mStatsLog).write(eq(UI_INTERACTION_FRAME_INFO_REPORTED),
                 eq(42), /* displayId */
                 eq(DisplayRefreshRate.REFRESH_RATE_60_HZ),
-                eq(CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE]),
+                eq(Cuj.getStatsdInteractionType(CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE)),
                 eq(2L) /* totalFrames */,
                 eq(0L) /* missedFrames */,
                 eq(5000000L) /* maxFrameTimeNanos */,
@@ -184,8 +185,7 @@
 
     @Test
     public void testSfJank() {
-        FrameTracker tracker = spyFrameTracker(
-                CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE, CUJ_POSTFIX, /* surfaceOnly= */ false);
+        FrameTracker tracker = spyFrameTracker(/* surfaceOnly= */ false);
 
         when(mChoreographer.getVsyncId()).thenReturn(100L);
         tracker.begin();
@@ -206,12 +206,12 @@
         verify(tracker).removeObservers();
 
         // We detected a janky frame - trigger Perfetto
-        verify(tracker).triggerPerfetto();
+        verify(mTrackerListener).triggerPerfetto(any());
 
         verify(mStatsLog).write(eq(UI_INTERACTION_FRAME_INFO_REPORTED),
                 eq(42), /* displayId */
                 eq(DisplayRefreshRate.REFRESH_RATE_60_HZ),
-                eq(CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE]),
+                eq(Cuj.getStatsdInteractionType(CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE)),
                 eq(2L) /* totalFrames */,
                 eq(1L) /* missedFrames */,
                 eq(40000000L) /* maxFrameTimeNanos */,
@@ -222,8 +222,7 @@
 
     @Test
     public void testFirstFrameJankyNoTrigger() {
-        FrameTracker tracker = spyFrameTracker(
-                CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE, CUJ_POSTFIX, /* surfaceOnly= */ false);
+        FrameTracker tracker = spyFrameTracker(/* surfaceOnly= */ false);
 
         when(mChoreographer.getVsyncId()).thenReturn(100L);
         tracker.begin();
@@ -243,13 +242,12 @@
 
         verify(tracker).removeObservers();
 
-        // We detected a janky frame - trigger Perfetto
-        verify(tracker, never()).triggerPerfetto();
+        verify(mTrackerListener, never()).triggerPerfetto(any());
 
         verify(mStatsLog).write(eq(UI_INTERACTION_FRAME_INFO_REPORTED),
                 eq(42), /* displayId */
                 eq(DisplayRefreshRate.REFRESH_RATE_60_HZ),
-                eq(CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE]),
+                eq(Cuj.getStatsdInteractionType(CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE)),
                 eq(2L) /* totalFrames */,
                 eq(0L) /* missedFrames */,
                 eq(4000000L) /* maxFrameTimeNanos */,
@@ -260,8 +258,7 @@
 
     @Test
     public void testOtherFrameOverThreshold() {
-        FrameTracker tracker = spyFrameTracker(
-                CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE, CUJ_POSTFIX, /* surfaceOnly= */ false);
+        FrameTracker tracker = spyFrameTracker(/* surfaceOnly= */ false);
 
         when(mChoreographer.getVsyncId()).thenReturn(100L);
         tracker.begin();
@@ -282,12 +279,12 @@
         verify(tracker).removeObservers();
 
         // We detected a janky frame - trigger Perfetto
-        verify(tracker).triggerPerfetto();
+        verify(mTrackerListener).triggerPerfetto(any());
 
         verify(mStatsLog).write(eq(UI_INTERACTION_FRAME_INFO_REPORTED),
                 eq(42), /* displayId */
                 eq(DisplayRefreshRate.REFRESH_RATE_60_HZ),
-                eq(CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE]),
+                eq(Cuj.getStatsdInteractionType(CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE)),
                 eq(2L) /* totalFrames */,
                 eq(1L) /* missedFrames */,
                 eq(40000000L) /* maxFrameTimeNanos */,
@@ -298,8 +295,7 @@
 
     @Test
     public void testLastFrameOverThresholdBeforeEnd() {
-        FrameTracker tracker = spyFrameTracker(
-                CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE, CUJ_POSTFIX, /* surfaceOnly= */ false);
+        FrameTracker tracker = spyFrameTracker(/* surfaceOnly= */ false);
 
         when(mChoreographer.getVsyncId()).thenReturn(100L);
         tracker.begin();
@@ -323,12 +319,12 @@
         verify(tracker).removeObservers();
 
         // We detected a janky frame - trigger Perfetto
-        verify(tracker).triggerPerfetto();
+        verify(mTrackerListener).triggerPerfetto(any());
 
         verify(mStatsLog).write(eq(UI_INTERACTION_FRAME_INFO_REPORTED),
                 eq(42), /* displayId */
                 eq(DisplayRefreshRate.REFRESH_RATE_60_HZ),
-                eq(CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE]),
+                eq(Cuj.getStatsdInteractionType(CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE)),
                 eq(2L) /* totalFrames */,
                 eq(1L) /* missedFrames */,
                 eq(50000000L) /* maxFrameTimeNanos */,
@@ -342,8 +338,7 @@
      */
     @Test
     public void testNoOvercountingAfterEnd() {
-        FrameTracker tracker = spyFrameTracker(
-                CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE, CUJ_POSTFIX, /* surfaceOnly= */ false);
+        FrameTracker tracker = spyFrameTracker(/* surfaceOnly= */ false);
 
         when(mChoreographer.getVsyncId()).thenReturn(100L);
         tracker.begin();
@@ -367,11 +362,11 @@
         sendFrame(tracker, 50, JANK_APP_DEADLINE_MISSED, 103L);
 
         verify(tracker).removeObservers();
-        verify(tracker, never()).triggerPerfetto();
+        verify(mTrackerListener, never()).triggerPerfetto(any());
         verify(mStatsLog).write(eq(UI_INTERACTION_FRAME_INFO_REPORTED),
                 eq(42), /* displayId */
                 eq(DisplayRefreshRate.REFRESH_RATE_60_HZ),
-                eq(CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE]),
+                eq(Cuj.getStatsdInteractionType(CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE)),
                 eq(2L) /* totalFrames */,
                 eq(0L) /* missedFrames */,
                 eq(4000000L) /* maxFrameTimeNanos */,
@@ -382,8 +377,7 @@
 
     @Test
     public void testBeginCancel() {
-        FrameTracker tracker = spyFrameTracker(
-                CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE, CUJ_POSTFIX, /* surfaceOnly= */ false);
+        FrameTracker tracker = spyFrameTracker(/* surfaceOnly= */ false);
 
         when(mChoreographer.getVsyncId()).thenReturn(100L);
         tracker.begin();
@@ -402,13 +396,12 @@
         tracker.cancel(FrameTracker.REASON_CANCEL_NORMAL);
         verify(tracker).removeObservers();
         // Since the tracker has been cancelled, shouldn't trigger perfetto.
-        verify(tracker, never()).triggerPerfetto();
+        verify(mTrackerListener, never()).triggerPerfetto(any());
     }
 
     @Test
     public void testCancelIfEndVsyncIdEqualsToBeginVsyncId() {
-        FrameTracker tracker = spyFrameTracker(
-                CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE, CUJ_POSTFIX, /* surfaceOnly= */ false);
+        FrameTracker tracker = spyFrameTracker(/* surfaceOnly= */ false);
 
         when(mChoreographer.getVsyncId()).thenReturn(100L);
         tracker.begin();
@@ -426,13 +419,12 @@
         verify(tracker).removeObservers();
 
         // Should never trigger Perfetto since it is a cancel.
-        verify(tracker, never()).triggerPerfetto();
+        verify(mTrackerListener, never()).triggerPerfetto(any());
     }
 
     @Test
     public void testCancelIfEndVsyncIdLessThanBeginVsyncId() {
-        FrameTracker tracker = spyFrameTracker(
-                CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE, CUJ_POSTFIX, /* surfaceOnly= */ false);
+        FrameTracker tracker = spyFrameTracker(/* surfaceOnly= */ false);
 
         when(mChoreographer.getVsyncId()).thenReturn(100L);
         tracker.begin();
@@ -450,13 +442,12 @@
         verify(tracker).removeObservers();
 
         // Should never trigger Perfetto since it is a cancel.
-        verify(tracker, never()).triggerPerfetto();
+        verify(mTrackerListener, never()).triggerPerfetto(any());
     }
 
     @Test
     public void testCancelWhenSessionNeverBegun() {
-        FrameTracker tracker = spyFrameTracker(
-                CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE, CUJ_POSTFIX, /* surfaceOnly= */ false);
+        FrameTracker tracker = spyFrameTracker(/* surfaceOnly= */ false);
 
         tracker.cancel(FrameTracker.REASON_CANCEL_NORMAL);
         verify(tracker).removeObservers();
@@ -464,8 +455,7 @@
 
     @Test
     public void testEndWhenSessionNeverBegun() {
-        FrameTracker tracker = spyFrameTracker(
-                CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE, CUJ_POSTFIX, /* surfaceOnly= */ false);
+        FrameTracker tracker = spyFrameTracker(/* surfaceOnly= */ false);
 
         tracker.end(FrameTracker.REASON_END_NORMAL);
         verify(tracker).removeObservers();
@@ -473,8 +463,7 @@
 
     @Test
     public void testSurfaceOnlyOtherFrameJanky() {
-        FrameTracker tracker = spyFrameTracker(
-                CUJ_WALLPAPER_TRANSITION, CUJ_POSTFIX, /* surfaceOnly= */ true);
+        FrameTracker tracker = spyFrameTracker(/* surfaceOnly= */ true);
 
         when(mChoreographer.getVsyncId()).thenReturn(100L);
         tracker.begin();
@@ -495,12 +484,12 @@
         sendFrame(tracker, JANK_NONE, 103L);
 
         verify(mSurfaceControlWrapper).removeJankStatsListener(any());
-        verify(tracker).triggerPerfetto();
+        verify(mTrackerListener).triggerPerfetto(any());
 
         verify(mStatsLog).write(eq(UI_INTERACTION_FRAME_INFO_REPORTED),
                 eq(42), /* displayId */
                 eq(DisplayRefreshRate.REFRESH_RATE_60_HZ),
-                eq(CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_WALLPAPER_TRANSITION]),
+                eq(Cuj.getStatsdInteractionType(CUJ_WALLPAPER_TRANSITION)),
                 eq(2L) /* totalFrames */,
                 eq(1L) /* missedFrames */,
                 eq(0L) /* maxFrameTimeNanos */,
@@ -511,8 +500,7 @@
 
     @Test
     public void testSurfaceOnlyFirstFrameJanky() {
-        FrameTracker tracker = spyFrameTracker(
-                CUJ_WALLPAPER_TRANSITION, CUJ_POSTFIX, /* surfaceOnly= */ true);
+        FrameTracker tracker = spyFrameTracker(/* surfaceOnly= */ true);
 
         when(mChoreographer.getVsyncId()).thenReturn(100L);
         tracker.begin();
@@ -533,12 +521,12 @@
         sendFrame(tracker, JANK_NONE, 103L);
 
         verify(mSurfaceControlWrapper).removeJankStatsListener(any());
-        verify(tracker, never()).triggerPerfetto();
+        verify(mTrackerListener, never()).triggerPerfetto(any());
 
         verify(mStatsLog).write(eq(UI_INTERACTION_FRAME_INFO_REPORTED),
                 eq(42), /* displayId */
                 eq(DisplayRefreshRate.REFRESH_RATE_60_HZ),
-                eq(CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_WALLPAPER_TRANSITION]),
+                eq(Cuj.getStatsdInteractionType(CUJ_WALLPAPER_TRANSITION)),
                 eq(2L) /* totalFrames */,
                 eq(0L) /* missedFrames */,
                 eq(0L) /* maxFrameTimeNanos */,
@@ -549,8 +537,7 @@
 
     @Test
     public void testSurfaceOnlyLastFrameJanky() {
-        FrameTracker tracker = spyFrameTracker(
-                CUJ_WALLPAPER_TRANSITION, CUJ_POSTFIX, /* surfaceOnly= */ true);
+        FrameTracker tracker = spyFrameTracker(/* surfaceOnly= */ true);
 
         when(mChoreographer.getVsyncId()).thenReturn(100L);
         tracker.begin();
@@ -571,12 +558,12 @@
         sendFrame(tracker, JANK_APP_DEADLINE_MISSED, 103L);
 
         verify(mSurfaceControlWrapper).removeJankStatsListener(any());
-        verify(tracker, never()).triggerPerfetto();
+        verify(mTrackerListener, never()).triggerPerfetto(any());
 
         verify(mStatsLog).write(eq(UI_INTERACTION_FRAME_INFO_REPORTED),
                 eq(42), /* displayId */
                 eq(DisplayRefreshRate.REFRESH_RATE_60_HZ),
-                eq(CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_WALLPAPER_TRANSITION]),
+                eq(Cuj.getStatsdInteractionType(CUJ_WALLPAPER_TRANSITION)),
                 eq(2L) /* totalFrames */,
                 eq(0L) /* missedFrames */,
                 eq(0L) /* maxFrameTimeNanos */,
@@ -587,8 +574,7 @@
 
     @Test
     public void testMaxSuccessiveMissedFramesCount() {
-        FrameTracker tracker = spyFrameTracker(
-                CUJ_WALLPAPER_TRANSITION, CUJ_POSTFIX, /* surfaceOnly= */ true);
+        FrameTracker tracker = spyFrameTracker(/* surfaceOnly= */ true);
         when(mChoreographer.getVsyncId()).thenReturn(100L);
         tracker.begin();
         mRunnableArgumentCaptor.getValue().run();
@@ -604,11 +590,11 @@
         sendFrame(tracker, JANK_SURFACEFLINGER_DEADLINE_MISSED, 106L);
         sendFrame(tracker, JANK_SURFACEFLINGER_DEADLINE_MISSED, 107L);
         verify(mSurfaceControlWrapper).removeJankStatsListener(any());
-        verify(tracker).triggerPerfetto();
+        verify(mTrackerListener).triggerPerfetto(any());
         verify(mStatsLog).write(eq(UI_INTERACTION_FRAME_INFO_REPORTED),
                 eq(42), /* displayId */
                 eq(DisplayRefreshRate.REFRESH_RATE_60_HZ),
-                eq(CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_WALLPAPER_TRANSITION]),
+                eq(Cuj.getStatsdInteractionType(CUJ_WALLPAPER_TRANSITION)),
                 eq(6L) /* totalFrames */,
                 eq(5L) /* missedFrames */,
                 eq(0L) /* maxFrameTimeNanos */,
diff --git a/core/tests/coretests/src/com/android/internal/jank/InteractionJankMonitorTest.java b/core/tests/coretests/src/com/android/internal/jank/InteractionJankMonitorTest.java
index b61f995..68095e5 100644
--- a/core/tests/coretests/src/com/android/internal/jank/InteractionJankMonitorTest.java
+++ b/core/tests/coretests/src/com/android/internal/jank/InteractionJankMonitorTest.java
@@ -20,23 +20,9 @@
 
 import static com.android.internal.jank.FrameTracker.REASON_CANCEL_TIMEOUT;
 import static com.android.internal.jank.FrameTracker.REASON_END_NORMAL;
-import static com.android.internal.jank.InteractionJankMonitor.CUJ_NOTIFICATION_ADD;
-import static com.android.internal.jank.InteractionJankMonitor.CUJ_NOTIFICATION_APP_START;
-import static com.android.internal.jank.InteractionJankMonitor.CUJ_NOTIFICATION_HEADS_UP_APPEAR;
-import static com.android.internal.jank.InteractionJankMonitor.CUJ_NOTIFICATION_HEADS_UP_DISAPPEAR;
-import static com.android.internal.jank.InteractionJankMonitor.CUJ_NOTIFICATION_REMOVE;
-import static com.android.internal.jank.InteractionJankMonitor.CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE;
-import static com.android.internal.jank.InteractionJankMonitor.CUJ_NOTIFICATION_SHADE_QS_EXPAND_COLLAPSE;
-import static com.android.internal.jank.InteractionJankMonitor.CUJ_NOTIFICATION_SHADE_QS_SCROLL_SWIPE;
-import static com.android.internal.jank.InteractionJankMonitor.CUJ_NOTIFICATION_SHADE_ROW_EXPAND;
-import static com.android.internal.jank.InteractionJankMonitor.CUJ_NOTIFICATION_SHADE_ROW_SWIPE;
-import static com.android.internal.jank.InteractionJankMonitor.CUJ_NOTIFICATION_SHADE_SCROLL_FLING;
-import static com.android.internal.jank.InteractionJankMonitor.CUJ_TO_STATSD_INTERACTION_TYPE;
-import static com.android.internal.jank.InteractionJankMonitor.MAX_LENGTH_OF_CUJ_NAME;
-import static com.android.internal.jank.InteractionJankMonitor.getNameOfCuj;
+import static com.android.internal.jank.InteractionJankMonitor.Configuration.generateSessionName;
 
 import static com.google.common.truth.Truth.assertThat;
-import static com.google.common.truth.Truth.assertWithMessage;
 
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyInt;
@@ -52,12 +38,11 @@
 import android.os.HandlerThread;
 import android.os.SystemClock;
 import android.provider.DeviceConfig;
-import android.util.SparseArray;
 import android.view.View;
 import android.view.ViewAttachTestActivity;
 
+import androidx.test.ext.junit.rules.ActivityScenarioRule;
 import androidx.test.filters.SmallTest;
-import androidx.test.rule.ActivityTestRule;
 
 import com.android.internal.jank.FrameTracker.ChoreographerWrapper;
 import com.android.internal.jank.FrameTracker.FrameMetricsWrapper;
@@ -66,101 +51,54 @@
 import com.android.internal.jank.FrameTracker.ThreadedRendererWrapper;
 import com.android.internal.jank.FrameTracker.ViewRootWrapper;
 import com.android.internal.jank.InteractionJankMonitor.Configuration;
-import com.android.internal.jank.InteractionJankMonitor.Session;
-import com.android.internal.util.FrameworkStatsLog;
 
 import com.google.common.truth.Expect;
 
 import org.junit.Before;
-import org.junit.BeforeClass;
 import org.junit.Rule;
 import org.junit.Test;
 import org.mockito.ArgumentCaptor;
 
-import java.lang.reflect.Field;
-import java.lang.reflect.Modifier;
-import java.util.ArrayList;
-import java.util.Arrays;
 import java.util.HashMap;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Map;
-import java.util.stream.Collectors;
 
 @SmallTest
 public class InteractionJankMonitorTest {
-    private static final String CUJ_POSTFIX = "";
-    private static final SparseArray<String> ENUM_NAME_EXCEPTION_MAP = new SparseArray<>();
-    private static final String ENUM_NAME_PREFIX =
-            "UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__";
-
-    private static final ArrayList<String> DEPRECATED_VALUES = new ArrayList<>();
-
     private ViewAttachTestActivity mActivity;
     private View mView;
+    private Handler mHandler;
     private HandlerThread mWorker;
 
     @Rule
-    public ActivityTestRule<ViewAttachTestActivity> mRule =
-            new ActivityTestRule<>(ViewAttachTestActivity.class);
+    public ActivityScenarioRule<ViewAttachTestActivity> mRule =
+            new ActivityScenarioRule<>(ViewAttachTestActivity.class);
 
     @Rule
     public final Expect mExpect = Expect.create();
 
-    @BeforeClass
-    public static void initialize() {
-        ENUM_NAME_EXCEPTION_MAP.put(CUJ_NOTIFICATION_ADD, getEnumName("SHADE_NOTIFICATION_ADD"));
-        ENUM_NAME_EXCEPTION_MAP.put(CUJ_NOTIFICATION_APP_START, getEnumName("SHADE_APP_LAUNCH"));
-        ENUM_NAME_EXCEPTION_MAP.put(
-                CUJ_NOTIFICATION_HEADS_UP_APPEAR, getEnumName("SHADE_HEADS_UP_APPEAR"));
-        ENUM_NAME_EXCEPTION_MAP.put(
-                CUJ_NOTIFICATION_HEADS_UP_DISAPPEAR, getEnumName("SHADE_HEADS_UP_DISAPPEAR"));
-        ENUM_NAME_EXCEPTION_MAP.put(
-                CUJ_NOTIFICATION_REMOVE, getEnumName("SHADE_NOTIFICATION_REMOVE"));
-        ENUM_NAME_EXCEPTION_MAP.put(
-                CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE, getEnumName("NOTIFICATION_SHADE_SWIPE"));
-        ENUM_NAME_EXCEPTION_MAP.put(
-                CUJ_NOTIFICATION_SHADE_QS_EXPAND_COLLAPSE, getEnumName("SHADE_QS_EXPAND_COLLAPSE"));
-        ENUM_NAME_EXCEPTION_MAP.put(
-                CUJ_NOTIFICATION_SHADE_QS_SCROLL_SWIPE, getEnumName("SHADE_QS_SCROLL_SWIPE"));
-        ENUM_NAME_EXCEPTION_MAP.put(
-                CUJ_NOTIFICATION_SHADE_ROW_EXPAND, getEnumName("SHADE_ROW_EXPAND"));
-        ENUM_NAME_EXCEPTION_MAP.put(
-                CUJ_NOTIFICATION_SHADE_ROW_SWIPE, getEnumName("SHADE_ROW_SWIPE"));
-        ENUM_NAME_EXCEPTION_MAP.put(
-                CUJ_NOTIFICATION_SHADE_SCROLL_FLING, getEnumName("SHADE_SCROLL_FLING"));
-        DEPRECATED_VALUES.add(ENUM_NAME_PREFIX + "IME_INSETS_ANIMATION");
-    }
-
-    private static String getEnumName(String name) {
-        return formatSimple("%s%s", ENUM_NAME_PREFIX, name);
-    }
-
     @Before
     public void setup() {
-        // Prepare an activity for getting ThreadedRenderer later.
-        mActivity = mRule.getActivity();
+        mRule.getScenario().onActivity(activity -> mActivity = activity);
         mView = mActivity.getWindow().getDecorView();
         assertThat(mView.isAttachedToWindow()).isTrue();
 
-        Handler handler = spy(new Handler(mActivity.getMainLooper()));
-        doReturn(true).when(handler).sendMessageAtTime(any(), anyLong());
+        mHandler = spy(new Handler(mActivity.getMainLooper()));
+        doReturn(true).when(mHandler).sendMessageAtTime(any(), anyLong());
         mWorker = mock(HandlerThread.class);
-        doReturn(handler).when(mWorker).getThreadHandler();
+        doReturn(mHandler).when(mWorker).getThreadHandler();
     }
 
     @Test
     public void testBeginEnd() {
         InteractionJankMonitor monitor = createMockedInteractionJankMonitor();
-        FrameTracker tracker = createMockedFrameTracker(monitor, null);
-        doReturn(tracker).when(monitor).createFrameTracker(any(), any());
+        FrameTracker tracker = createMockedFrameTracker();
+        doReturn(tracker).when(monitor).createFrameTracker(any());
         doNothing().when(tracker).begin();
         doReturn(true).when(tracker).end(anyInt());
 
         // Simulate a trace session and see if begin / end are invoked.
-        assertThat(monitor.begin(mView, CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE)).isTrue();
+        assertThat(monitor.begin(mView, Cuj.CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE)).isTrue();
         verify(tracker).begin();
-        assertThat(monitor.end(CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE)).isTrue();
+        assertThat(monitor.end(Cuj.CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE)).isTrue();
         verify(tracker).end(REASON_END_NORMAL);
     }
 
@@ -172,10 +110,10 @@
         propertiesValues.put("enabled", "false");
         DeviceConfig.Properties properties = new DeviceConfig.Properties(
                 DeviceConfig.NAMESPACE_INTERACTION_JANK_MONITOR, propertiesValues);
-        monitor.getPropertiesChangedListener().onPropertiesChanged(properties);
+        monitor.updateProperties(properties);
 
-        assertThat(monitor.begin(mView, CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE)).isFalse();
-        assertThat(monitor.end(CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE)).isFalse();
+        assertThat(monitor.begin(mView, Cuj.CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE)).isFalse();
+        assertThat(monitor.end(Cuj.CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE)).isFalse();
     }
 
     @Test
@@ -185,145 +123,57 @@
         assertThat(view.isAttachedToWindow()).isFalse();
 
         // Should return false if the view passed in is not attached to window yet.
-        assertThat(monitor.begin(view, CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE)).isFalse();
-        assertThat(monitor.end(CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE)).isFalse();
+        assertThat(monitor.begin(view, Cuj.CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE)).isFalse();
+        assertThat(monitor.end(Cuj.CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE)).isFalse();
     }
 
     @Test
     public void testBeginTimeout() {
         ArgumentCaptor<Runnable> captor = ArgumentCaptor.forClass(Runnable.class);
         InteractionJankMonitor monitor = createMockedInteractionJankMonitor();
-        FrameTracker tracker = createMockedFrameTracker(monitor, null);
-        doReturn(tracker).when(monitor).createFrameTracker(any(), any());
+        FrameTracker tracker = createMockedFrameTracker();
+        doReturn(tracker).when(monitor).createFrameTracker(any());
         doNothing().when(tracker).begin();
         doReturn(true).when(tracker).cancel(anyInt());
 
-        assertThat(monitor.begin(mView, CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE)).isTrue();
+        assertThat(monitor.begin(mView, Cuj.CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE)).isTrue();
         verify(tracker).begin();
-        verify(monitor).scheduleTimeoutAction(anyInt(), anyLong(), captor.capture());
+        verify(monitor).scheduleTimeoutAction(any(), captor.capture());
         Runnable runnable = captor.getValue();
         assertThat(runnable).isNotNull();
-        mWorker.getThreadHandler().removeCallbacks(runnable);
         runnable.run();
-        verify(monitor).cancel(CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE, REASON_CANCEL_TIMEOUT);
+        verify(monitor).cancel(Cuj.CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE, REASON_CANCEL_TIMEOUT);
         verify(tracker).cancel(REASON_CANCEL_TIMEOUT);
     }
 
     @Test
-    public void testCujTypeEnumCorrectlyDefined() throws Exception {
-        List<Field> cujEnumFields =
-                Arrays.stream(InteractionJankMonitor.class.getDeclaredFields())
-                        .filter(field -> field.getName().startsWith("CUJ_")
-                                && Modifier.isStatic(field.getModifiers())
-                                && field.getType() == int.class)
-                        .collect(Collectors.toList());
-
-        HashSet<Integer> allValues = new HashSet<>();
-        for (Field field : cujEnumFields) {
-            int fieldValue = field.getInt(null);
-            assertWithMessage(
-                    "Field %s must have a mapping to a value in CUJ_TO_STATSD_INTERACTION_TYPE",
-                    field.getName())
-                    .that(fieldValue < CUJ_TO_STATSD_INTERACTION_TYPE.length)
-                    .isTrue();
-            assertWithMessage("All CujType values must be unique. Field %s repeats existing value.",
-                    field.getName())
-                    .that(allValues.add(fieldValue))
-                    .isTrue();
-        }
-    }
-
-    @Test
-    public void testCujsMapToEnumsCorrectly() {
-        List<Field> cujs = Arrays.stream(InteractionJankMonitor.class.getDeclaredFields())
-                .filter(f -> f.getName().startsWith("CUJ_")
-                        && Modifier.isStatic(f.getModifiers())
-                        && f.getType() == int.class)
-                .collect(Collectors.toList());
-
-        Map<Integer, String> enumsMap = Arrays.stream(FrameworkStatsLog.class.getDeclaredFields())
-                .filter(f -> f.getName().startsWith(ENUM_NAME_PREFIX)
-                        && !DEPRECATED_VALUES.contains(f.getName())
-                        && Modifier.isStatic(f.getModifiers())
-                        && f.getType() == int.class)
-                .collect(Collectors.toMap(this::getIntFieldChecked, Field::getName));
-
-        assertThat(enumsMap.size() - 1).isEqualTo(cujs.size());
-
-        cujs.forEach(f -> {
-            final int cuj = getIntFieldChecked(f);
-            final String cujName = f.getName();
-            final String expectedEnumName = ENUM_NAME_EXCEPTION_MAP.contains(cuj)
-                    ? ENUM_NAME_EXCEPTION_MAP.get(cuj)
-                    : formatSimple("%s%s", ENUM_NAME_PREFIX, cujName.substring(4));
-            final int enumKey = CUJ_TO_STATSD_INTERACTION_TYPE[cuj];
-            final String enumName = enumsMap.get(enumKey);
-            final String expectedNameOfCuj = formatSimple("CUJ_%s", getNameOfCuj(cuj));
-
-            mExpect
-                    .withMessage(formatSimple(
-                            "%s (%d) not matches %s (%d)", cujName, cuj, enumName, enumKey))
-                    .that(expectedEnumName.equals(enumName))
-                    .isTrue();
-            mExpect
-                    .withMessage(
-                            formatSimple("getNameOfCuj(%d) not matches: %s, expected=%s",
-                                    cuj, cujName, expectedNameOfCuj))
-                    .that(cujName.equals(expectedNameOfCuj))
-                    .isTrue();
-        });
-    }
-
-    @Test
-    public void testCujNameLimit() {
-        Arrays.stream(InteractionJankMonitor.class.getDeclaredFields())
-                .filter(f -> f.getName().startsWith("CUJ_")
-                        && Modifier.isStatic(f.getModifiers())
-                        && f.getType() == int.class)
-                .forEach(f -> {
-                    final int cuj = getIntFieldChecked(f);
-                    mExpect
-                            .withMessage(formatSimple(
-                                    "Too long CUJ(%d) name: %s", cuj, getNameOfCuj(cuj)))
-                            .that(getNameOfCuj(cuj).length())
-                            .isAtMost(MAX_LENGTH_OF_CUJ_NAME);
-                });
-    }
-
-    @Test
     public void testSessionNameLengthLimit() {
-        final int cujType = CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE;
-        final String cujName = getNameOfCuj(cujType);
+        final int cujType = Cuj.CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE;
+        final String cujName = Cuj.getNameOfCuj(cujType);
         final String cujTag = "ThisIsTheCujTag";
         final String tooLongTag = cujTag.repeat(10);
 
         // Normal case, no postfix.
-        Session noPostfix = new Session(cujType, "");
-        assertThat(noPostfix.getName()).isEqualTo(formatSimple("J<%s>", cujName));
+        assertThat(generateSessionName(cujName, "")).isEqualTo(formatSimple("J<%s>", cujName));
 
         // Normal case, with postfix.
-        Session withPostfix = new Session(cujType, cujTag);
-        assertThat(withPostfix.getName()).isEqualTo(formatSimple("J<%s::%s>", cujName, cujTag));
+        assertThat(generateSessionName(cujName, cujTag))
+                .isEqualTo(formatSimple("J<%s::%s>", cujName, cujTag));
 
         // Since the length of the cuj name is tested in another test, no need to test it here.
         // Too long postfix case, should trim the postfix and keep the cuj name completed.
         final String expectedTrimmedName = formatSimple("J<%s::%s>", cujName,
                 "ThisIsTheCujTagThisIsTheCujTagThisIsTheCujTagThisIsTheCujTagThi...");
-        Session longPostfix = new Session(cujType, tooLongTag);
-        assertThat(longPostfix.getName()).isEqualTo(expectedTrimmedName);
+        assertThat(generateSessionName(cujName, tooLongTag)).isEqualTo(expectedTrimmedName);
     }
 
     private InteractionJankMonitor createMockedInteractionJankMonitor() {
         InteractionJankMonitor monitor = spy(new InteractionJankMonitor(mWorker));
-        doReturn(true).when(monitor).shouldMonitor(anyInt());
+        doReturn(true).when(monitor).shouldMonitor();
         return monitor;
     }
 
-    private FrameTracker createMockedFrameTracker(InteractionJankMonitor monitor,
-            FrameTracker.FrameTrackerListener listener) {
-        Session session = spy(new Session(CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE, CUJ_POSTFIX));
-        doReturn(false).when(session).logToStatsd();
-
+    private FrameTracker createMockedFrameTracker() {
         ThreadedRendererWrapper threadedRenderer = mock(ThreadedRendererWrapper.class);
         doNothing().when(threadedRenderer).addObserver(any());
         doNothing().when(threadedRenderer).removeObserver(any());
@@ -342,27 +192,19 @@
         Configuration configuration = mock(Configuration.class);
         when(configuration.isSurfaceOnly()).thenReturn(false);
         when(configuration.getView()).thenReturn(mView);
-        when(configuration.getHandler()).thenReturn(mView.getHandler());
         when(configuration.getDisplayId()).thenReturn(42);
+        when(configuration.logToStatsd()).thenReturn(false);
+        when(configuration.getHandler()).thenReturn(mHandler);
 
-        FrameTracker tracker = spy(new FrameTracker(monitor, session, mWorker.getThreadHandler(),
+        FrameTracker tracker = spy(new FrameTracker(configuration,
                 threadedRenderer, viewRoot, surfaceControl, choreographer,
                 new FrameMetricsWrapper(), new StatsLogWrapper(null),
                 /* traceThresholdMissedFrames= */ 1,
-                /* traceThresholdFrameTimeMillis= */ -1, listener, configuration));
+                /* traceThresholdFrameTimeMillis= */ -1,
+                /* listener */ null));
 
         doNothing().when(tracker).postTraceStartMarker(any());
-        doNothing().when(tracker).triggerPerfetto();
-        doReturn(configuration.getHandler()).when(tracker).getHandler();
 
         return tracker;
     }
-
-    private int getIntFieldChecked(Field field) {
-        try {
-            return field.getInt(null);
-        } catch (IllegalAccessException ex) {
-            throw new RuntimeException(ex);
-        }
-    }
 }
diff --git a/data/etc/services.core.protolog.json b/data/etc/services.core.protolog.json
index 2237ba1..aaddf0e 100644
--- a/data/etc/services.core.protolog.json
+++ b/data/etc/services.core.protolog.json
@@ -169,6 +169,12 @@
       "group": "WM_DEBUG_WINDOW_ORGANIZER",
       "at": "com\/android\/server\/wm\/TaskOrganizerController.java"
     },
+    "-1961637874": {
+      "message": "DeferredDisplayUpdater: applying DisplayInfo immediately",
+      "level": "DEBUG",
+      "group": "WM_DEBUG_WINDOW_TRANSITIONS",
+      "at": "com\/android\/server\/wm\/DeferredDisplayUpdater.java"
+    },
     "-1949279037": {
       "message": "Attempted to add input method window with bad token %s.  Aborting.",
       "level": "WARN",
@@ -313,6 +319,12 @@
       "group": "WM_DEBUG_RESIZE",
       "at": "com\/android\/server\/wm\/WindowState.java"
     },
+    "-1818910559": {
+      "message": "DeferredDisplayUpdater: applied DisplayInfo after deferring",
+      "level": "DEBUG",
+      "group": "WM_DEBUG_WINDOW_TRANSITIONS",
+      "at": "com\/android\/server\/wm\/DeferredDisplayUpdater.java"
+    },
     "-1814361639": {
       "message": "Set IME snapshot position: (%d, %d)",
       "level": "INFO",
@@ -595,6 +607,12 @@
       "group": "WM_ERROR",
       "at": "com\/android\/server\/wm\/WindowManagerService.java"
     },
+    "-1518132958": {
+      "message": "fractionRendered boundsOverSource=%f",
+      "level": "VERBOSE",
+      "group": "WM_DEBUG_TPL",
+      "at": "com\/android\/server\/wm\/TrustedPresentationListenerController.java"
+    },
     "-1517908912": {
       "message": "requestScrollCapture: caught exception dispatching to window.token=%s",
       "level": "WARN",
@@ -961,6 +979,12 @@
       "group": "WM_DEBUG_CONTENT_RECORDING",
       "at": "com\/android\/server\/wm\/ContentRecorder.java"
     },
+    "-1209762265": {
+      "message": "Registering listener=%s with id=%d for window=%s with %s",
+      "level": "DEBUG",
+      "group": "WM_DEBUG_TPL",
+      "at": "com\/android\/server\/wm\/TrustedPresentationListenerController.java"
+    },
     "-1209252064": {
       "message": "Clear animatingExit: reason=clearAnimatingFlags win=%s",
       "level": "DEBUG",
@@ -1333,6 +1357,12 @@
       "group": "WM_DEBUG_WINDOW_TRANSITIONS",
       "at": "com\/android\/server\/wm\/Transition.java"
     },
+    "-888703350": {
+      "message": "Skipping %s",
+      "level": "VERBOSE",
+      "group": "WM_DEBUG_TPL",
+      "at": "com\/android\/server\/wm\/TrustedPresentationListenerController.java"
+    },
     "-883738232": {
       "message": "Adding more than one toast window for UID at a time.",
       "level": "WARN",
@@ -1909,6 +1939,12 @@
       "group": "WM_DEBUG_FOCUS_LIGHT",
       "at": "com\/android\/server\/wm\/DisplayContent.java"
     },
+    "-415346336": {
+      "message": "DeferredDisplayUpdater: partially applying DisplayInfo immediately",
+      "level": "DEBUG",
+      "group": "WM_DEBUG_WINDOW_TRANSITIONS",
+      "at": "com\/android\/server\/wm\/DeferredDisplayUpdater.java"
+    },
     "-401282500": {
       "message": "destroyIfPossible: r=%s destroy returned removed=%s",
       "level": "DEBUG",
@@ -1957,6 +1993,12 @@
       "group": "WM_DEBUG_APP_TRANSITIONS",
       "at": "com\/android\/server\/wm\/AppTransitionController.java"
     },
+    "-376950429": {
+      "message": "DeferredDisplayUpdater: deferring DisplayInfo update",
+      "level": "DEBUG",
+      "group": "WM_DEBUG_WINDOW_TRANSITIONS",
+      "at": "com\/android\/server\/wm\/DeferredDisplayUpdater.java"
+    },
     "-374767836": {
       "message": "setAppVisibility(%s, visible=%b): %s visible=%b mVisibleRequested=%b Callers=%s",
       "level": "VERBOSE",
@@ -2803,6 +2845,12 @@
       "group": "WM_DEBUG_ORIENTATION",
       "at": "com\/android\/server\/wm\/WindowManagerService.java"
     },
+    "360319850": {
+      "message": "fractionRendered scale=%f",
+      "level": "VERBOSE",
+      "group": "WM_DEBUG_TPL",
+      "at": "com\/android\/server\/wm\/TrustedPresentationListenerController.java"
+    },
     "364992694": {
       "message": "freezeDisplayRotation: current rotation=%d, new rotation=%d, caller=%s",
       "level": "VERBOSE",
@@ -2983,6 +3031,12 @@
       "group": "WM_DEBUG_BACK_PREVIEW",
       "at": "com\/android\/server\/wm\/BackNavigationController.java"
     },
+    "532771960": {
+      "message": "Adding untrusted state listener=%s with id=%d",
+      "level": "DEBUG",
+      "group": "WM_DEBUG_TPL",
+      "at": "com\/android\/server\/wm\/TrustedPresentationListenerController.java"
+    },
     "535103992": {
       "message": "Wallpaper may change!  Adjusting",
       "level": "VERBOSE",
@@ -3061,6 +3115,12 @@
       "group": "WM_DEBUG_DREAM",
       "at": "com\/android\/server\/wm\/ActivityTaskManagerService.java"
     },
+    "605179032": {
+      "message": "checkIfInThreshold fractionRendered=%f alpha=%f currTimeMs=%d",
+      "level": "VERBOSE",
+      "group": "WM_DEBUG_TPL",
+      "at": "com\/android\/server\/wm\/TrustedPresentationListenerController.java"
+    },
     "608694300": {
       "message": "  NEW SURFACE SESSION %s",
       "level": "INFO",
@@ -3289,6 +3349,12 @@
       "group": "WM_SHOW_TRANSACTIONS",
       "at": "com\/android\/server\/wm\/WindowState.java"
     },
+    "824532141": {
+      "message": "lastState=%s newState=%s alpha=%f minAlpha=%f fractionRendered=%f minFractionRendered=%f",
+      "level": "VERBOSE",
+      "group": "WM_DEBUG_TPL",
+      "at": "com\/android\/server\/wm\/TrustedPresentationListenerController.java"
+    },
     "829434921": {
       "message": "Draw state now committed in %s",
       "level": "VERBOSE",
@@ -3583,6 +3649,12 @@
       "group": "WM_SHOW_SURFACE_ALLOC",
       "at": "com\/android\/server\/wm\/ScreenRotationAnimation.java"
     },
+    "1090378847": {
+      "message": "Checking %d windows",
+      "level": "VERBOSE",
+      "group": "WM_DEBUG_TPL",
+      "at": "com\/android\/server\/wm\/TrustedPresentationListenerController.java"
+    },
     "1100065297": {
       "message": "Attempted to get IME policy of a display that does not exist: %d",
       "level": "WARN",
@@ -3715,6 +3787,12 @@
       "group": "WM_DEBUG_FOCUS",
       "at": "com\/android\/server\/wm\/ActivityRecord.java"
     },
+    "1251721200": {
+      "message": "unregister failed, couldn't find deathRecipient for %s with id=%d",
+      "level": "ERROR",
+      "group": "WM_DEBUG_TPL",
+      "at": "com\/android\/server\/wm\/TrustedPresentationListenerController.java"
+    },
     "1252594551": {
       "message": "Window types in WindowContext and LayoutParams.type should match! Type from LayoutParams is %d, but type from WindowContext is %d",
       "level": "WARN",
@@ -3853,6 +3931,12 @@
       "group": "WM_DEBUG_ORIENTATION",
       "at": "com\/android\/server\/wm\/TaskDisplayArea.java"
     },
+    "1382634842": {
+      "message": "Unregistering listener=%s with id=%d",
+      "level": "DEBUG",
+      "group": "WM_DEBUG_TPL",
+      "at": "com\/android\/server\/wm\/TrustedPresentationListenerController.java"
+    },
     "1393721079": {
       "message": "Starting remote display change: from [rot = %d], to [%dx%d, rot = %d]",
       "level": "VERBOSE",
@@ -3901,6 +3985,12 @@
       "group": "WM_ERROR",
       "at": "com\/android\/server\/wm\/WindowManagerService.java"
     },
+    "1445704347": {
+      "message": "coveredRegionsAbove updated with %s frame:%s region:%s",
+      "level": "VERBOSE",
+      "group": "WM_DEBUG_TPL",
+      "at": "com\/android\/server\/wm\/TrustedPresentationListenerController.java"
+    },
     "1448683958": {
       "message": "Override pending remote transitionSet=%b adapter=%s",
       "level": "INFO",
@@ -4201,6 +4291,12 @@
       "group": "WM_DEBUG_RECENTS_ANIMATIONS",
       "at": "com\/android\/server\/wm\/RecentsAnimation.java"
     },
+    "1786463281": {
+      "message": "Adding trusted state listener=%s with id=%d",
+      "level": "DEBUG",
+      "group": "WM_DEBUG_TPL",
+      "at": "com\/android\/server\/wm\/TrustedPresentationListenerController.java"
+    },
     "1789321832": {
       "message": "Then token:%s is invalid. It might be removed",
       "level": "WARN",
@@ -4375,6 +4471,12 @@
       "group": "WM_DEBUG_TASKS",
       "at": "com\/android\/server\/wm\/RootWindowContainer.java"
     },
+    "1955470028": {
+      "message": "computeFractionRendered: visibleRegion=%s screenBounds=%s contentSize=%s scale=%f,%f",
+      "level": "VERBOSE",
+      "group": "WM_DEBUG_TPL",
+      "at": "com\/android\/server\/wm\/TrustedPresentationListenerController.java"
+    },
     "1964565370": {
       "message": "Starting remote animation",
       "level": "INFO",
@@ -4659,6 +4761,9 @@
     "WM_DEBUG_TASKS": {
       "tag": "WindowManager"
     },
+    "WM_DEBUG_TPL": {
+      "tag": "WindowManager"
+    },
     "WM_DEBUG_WALLPAPER": {
       "tag": "WindowManager"
     },
diff --git a/graphics/java/android/graphics/Paint.java b/graphics/java/android/graphics/Paint.java
index 92c4de6..f10cdb8 100644
--- a/graphics/java/android/graphics/Paint.java
+++ b/graphics/java/android/graphics/Paint.java
@@ -1739,10 +1739,6 @@
     /**
      * Get the elegant metrics flag.
      *
-     * From API {@link Build.VERSION_CODES#VANILLA_ICE_CREAM}, the default value will be true by
-     * default if the app has a target SDK of API {@link Build.VERSION_CODES#VANILLA_ICE_CREAM} or
-     * later.
-     *
      * @return true if elegant metrics are enabled for text drawing.
      */
     public boolean isElegantTextHeight() {
diff --git a/keystore/java/android/security/Authorization.java b/keystore/java/android/security/Authorization.java
index b4b3e92..4ec5e1b 100644
--- a/keystore/java/android/security/Authorization.java
+++ b/keystore/java/android/security/Authorization.java
@@ -26,7 +26,6 @@
 import android.os.ServiceSpecificException;
 import android.os.StrictMode;
 import android.security.authorization.IKeystoreAuthorization;
-import android.security.authorization.LockScreenEvent;
 import android.system.keystore2.ResponseCode;
 import android.util.Log;
 
@@ -76,26 +75,37 @@
     }
 
     /**
-     * Informs keystore2 about lock screen event.
+     * Tells Keystore that the device is now unlocked for a user.
      *
-     * @param locked            - whether it is a lock (true) or unlock (false) event
-     * @param syntheticPassword - if it is an unlock event with the password, pass the synthetic
-     *                            password provided by the LockSettingService
-     * @param unlockingSids     - KeyMint secure user IDs that should be permitted to unlock
-     *                            UNLOCKED_DEVICE_REQUIRED keys.
-     *
+     * @param userId - the user's Android user ID
+     * @param password - a secret derived from the user's synthetic password, if the unlock method
+     *                   is LSKF (or equivalent) and thus has made the synthetic password available
      * @return 0 if successful or a {@code ResponseCode}.
      */
-    public static int onLockScreenEvent(@NonNull boolean locked, @NonNull int userId,
-            @Nullable byte[] syntheticPassword, @Nullable long[] unlockingSids) {
+    public static int onDeviceUnlocked(int userId, @Nullable byte[] password) {
         StrictMode.noteDiskWrite();
         try {
-            if (locked) {
-                getService().onLockScreenEvent(LockScreenEvent.LOCK, userId, null, unlockingSids);
-            } else {
-                getService().onLockScreenEvent(
-                        LockScreenEvent.UNLOCK, userId, syntheticPassword, unlockingSids);
-            }
+            getService().onDeviceUnlocked(userId, password);
+            return 0;
+        } catch (RemoteException | NullPointerException e) {
+            Log.w(TAG, "Can not connect to keystore", e);
+            return SYSTEM_ERROR;
+        } catch (ServiceSpecificException e) {
+            return e.errorCode;
+        }
+    }
+
+    /**
+     * Tells Keystore that the device is now locked for a user.
+     *
+     * @param userId - the user's Android user ID
+     * @param unlockingSids - list of biometric SIDs with which the device may be unlocked again
+     * @return 0 if successful or a {@code ResponseCode}.
+     */
+    public static int onDeviceLocked(int userId, @NonNull long[] unlockingSids) {
+        StrictMode.noteDiskWrite();
+        try {
+            getService().onDeviceLocked(userId, unlockingSids);
             return 0;
         } catch (RemoteException | NullPointerException e) {
             Log.w(TAG, "Can not connect to keystore", e);
diff --git a/keystore/java/android/security/keystore/KeyGenParameterSpec.java b/keystore/java/android/security/keystore/KeyGenParameterSpec.java
index 231fa48..4982f37 100644
--- a/keystore/java/android/security/keystore/KeyGenParameterSpec.java
+++ b/keystore/java/android/security/keystore/KeyGenParameterSpec.java
@@ -618,7 +618,7 @@
      * @see #isMgf1DigestsSpecified()
      */
     @NonNull
-    @FlaggedApi("MGF1_DIGEST_SETTER")
+    @FlaggedApi(android.security.Flags.FLAG_MGF1_DIGEST_SETTER)
     public @KeyProperties.DigestEnum Set<String> getMgf1Digests() {
         if (mMgf1Digests.isEmpty()) {
             throw new IllegalStateException("Mask generation function (MGF) not specified");
@@ -633,7 +633,7 @@
      * @see #getMgf1Digests()
      */
     @NonNull
-    @FlaggedApi("MGF1_DIGEST_SETTER")
+    @FlaggedApi(android.security.Flags.FLAG_MGF1_DIGEST_SETTER)
     public boolean isMgf1DigestsSpecified() {
         return !mMgf1Digests.isEmpty();
     }
@@ -1292,7 +1292,7 @@
          * <p>See {@link KeyProperties}.{@code DIGEST} constants.
          */
         @NonNull
-        @FlaggedApi("MGF1_DIGEST_SETTER")
+        @FlaggedApi(android.security.Flags.FLAG_MGF1_DIGEST_SETTER)
         public Builder setMgf1Digests(@NonNull @KeyProperties.DigestEnum String... mgf1Digests) {
             mMgf1Digests = Set.of(mgf1Digests);
             return this;
diff --git a/keystore/java/android/security/keystore/KeyProtection.java b/keystore/java/android/security/keystore/KeyProtection.java
index c1e3bab..7b6b2d1 100644
--- a/keystore/java/android/security/keystore/KeyProtection.java
+++ b/keystore/java/android/security/keystore/KeyProtection.java
@@ -401,7 +401,7 @@
      * @see #isMgf1DigestsSpecified()
      */
     @NonNull
-    @FlaggedApi("MGF1_DIGEST_SETTER")
+    @FlaggedApi(android.security.Flags.FLAG_MGF1_DIGEST_SETTER)
     public @KeyProperties.DigestEnum Set<String> getMgf1Digests() {
         if (mMgf1Digests.isEmpty()) {
             throw new IllegalStateException("Mask generation function (MGF) not specified");
@@ -416,7 +416,7 @@
      * @see #getMgf1Digests()
      */
     @NonNull
-    @FlaggedApi("MGF1_DIGEST_SETTER")
+    @FlaggedApi(android.security.Flags.FLAG_MGF1_DIGEST_SETTER)
     public boolean isMgf1DigestsSpecified() {
         return !mMgf1Digests.isEmpty();
     }
@@ -799,7 +799,7 @@
          * <p>See {@link KeyProperties}.{@code DIGEST} constants.
          */
         @NonNull
-        @FlaggedApi("MGF1_DIGEST_SETTER")
+        @FlaggedApi(android.security.Flags.FLAG_MGF1_DIGEST_SETTER)
         public Builder setMgf1Digests(@Nullable @KeyProperties.DigestEnum String... mgf1Digests) {
             mMgf1Digests = Set.of(mgf1Digests);
             return this;
diff --git a/keystore/java/android/security/keystore2/AndroidKeyStoreKeyPairGeneratorSpi.java b/keystore/java/android/security/keystore2/AndroidKeyStoreKeyPairGeneratorSpi.java
index ed4b485..9c05a3a 100644
--- a/keystore/java/android/security/keystore2/AndroidKeyStoreKeyPairGeneratorSpi.java
+++ b/keystore/java/android/security/keystore2/AndroidKeyStoreKeyPairGeneratorSpi.java
@@ -28,6 +28,7 @@
 import android.hardware.security.keymint.Tag;
 import android.os.Build;
 import android.os.StrictMode;
+import android.security.Flags;
 import android.security.KeyPairGeneratorSpec;
 import android.security.KeyStore2;
 import android.security.KeyStoreException;
@@ -853,6 +854,22 @@
                             KeymasterDefs.KM_TAG_RSA_OAEP_MGF_DIGEST, mgf1Digest
                     ));
                 });
+
+                /* If the MGF1 Digest setter is not set, fall back to the previous behaviour:
+                 * Add, as MGF1 Digest function, all the primary digests.
+                 * Avoid adding the default MGF1 digest as it will have been included in the
+                 * mKeymasterMgf1Digests field.
+                 */
+                if (!getMgf1DigestSetterFlag()) {
+                    final int defaultMgf1Digest = KeyProperties.Digest.toKeymaster(
+                            DEFAULT_MGF1_DIGEST);
+                    ArrayUtils.forEach(mKeymasterDigests, (digest) -> {
+                        if (digest != defaultMgf1Digest) {
+                            params.add(KeyStore2ParameterUtils.makeEnum(
+                                    KeymasterDefs.KM_TAG_RSA_OAEP_MGF_DIGEST, digest));
+                        }
+                    });
+                }
             }
         });
         ArrayUtils.forEach(mKeymasterSignaturePaddings, (padding) -> {
@@ -928,6 +945,16 @@
         return params;
     }
 
+    private static boolean getMgf1DigestSetterFlag() {
+        try {
+            return Flags.mgf1DigestSetter();
+        } catch (SecurityException e) {
+            Log.w(TAG, "Cannot read MGF1 Digest setter flag value", e);
+            return false;
+        }
+    }
+
+
     private void addAlgorithmSpecificParameters(List<KeyParameter> params) {
         switch (mKeymasterAlgorithm) {
             case KeymasterDefs.KM_ALGORITHM_RSA:
diff --git a/keystore/java/android/security/keystore2/AndroidKeyStoreSpi.java b/keystore/java/android/security/keystore2/AndroidKeyStoreSpi.java
index ddbd93e..2d8c5a3 100644
--- a/keystore/java/android/security/keystore2/AndroidKeyStoreSpi.java
+++ b/keystore/java/android/security/keystore2/AndroidKeyStoreSpi.java
@@ -25,6 +25,7 @@
 import android.hardware.security.keymint.KeyParameter;
 import android.hardware.security.keymint.SecurityLevel;
 import android.os.StrictMode;
+import android.security.Flags;
 import android.security.GateKeeper;
 import android.security.KeyStore2;
 import android.security.KeyStoreParameter;
@@ -256,6 +257,15 @@
         }
     }
 
+    private static boolean getMgf1DigestSetterFlag() {
+        try {
+            return Flags.mgf1DigestSetter();
+        } catch (SecurityException e) {
+            Log.w(NAME, "Cannot read MGF1 Digest setter flag value", e);
+            return false;
+        }
+    }
+
     @Override
     public Date engineGetCreationDate(String alias) {
         KeyEntryResponse response = getKeyMetadata(alias);
@@ -537,11 +547,31 @@
                         /* Because of default MGF1 digest is SHA-1. It has to be added in Key
                          * characteristics. Otherwise, crypto operations will fail with Incompatible
                          * MGF1 digest.
+                         * If the MGF1 Digest setter flag isn't set, then the condition in the
+                         * if clause above must be false (cannot have MGF1 digests specified if the
+                         * flag was off). In that case, in addition to adding the default MGF1
+                         * digest, we have to add all the other digests as MGF1 Digests.
+                         *
                          */
                         importArgs.add(KeyStore2ParameterUtils.makeEnum(
                                 KeymasterDefs.KM_TAG_RSA_OAEP_MGF_DIGEST,
                                 KeyProperties.Digest.toKeymaster(DEFAULT_MGF1_DIGEST)
                         ));
+                        if (!getMgf1DigestSetterFlag()) {
+                            final int defaultMgf1Digest = KeyProperties.Digest.toKeymaster(
+                                    DEFAULT_MGF1_DIGEST);
+                            for (String digest : spec.getDigests()) {
+                                int digestToAddAsMgf1Digest = KeyProperties.Digest.toKeymaster(
+                                        digest);
+                                // Do not add the default MGF1 digest as it has been added above.
+                                if (digestToAddAsMgf1Digest != defaultMgf1Digest) {
+                                    importArgs.add(KeyStore2ParameterUtils.makeEnum(
+                                            KeymasterDefs.KM_TAG_RSA_OAEP_MGF_DIGEST,
+                                            digestToAddAsMgf1Digest
+                                    ));
+                                }
+                            }
+                        }
                     }
                 }
             }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationRunner.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationRunner.java
index dc413b0..a32b435 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationRunner.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationRunner.java
@@ -28,7 +28,7 @@
 import android.window.IBackAnimationRunner;
 import android.window.IOnBackInvokedCallback;
 
-import com.android.internal.jank.InteractionJankMonitor;
+import com.android.internal.jank.Cuj.CujType;
 import com.android.wm.shell.common.InteractionJankMonitorUtils;
 
 /**
@@ -42,7 +42,7 @@
 
     private final IOnBackInvokedCallback mCallback;
     private final IRemoteAnimationRunner mRunner;
-    private final @InteractionJankMonitor.CujType int mCujType;
+    private final @CujType int mCujType;
     private final Context mContext;
 
     // Whether we are waiting to receive onAnimationStart
@@ -55,7 +55,7 @@
             @NonNull IOnBackInvokedCallback callback,
             @NonNull IRemoteAnimationRunner runner,
             @NonNull Context context,
-            @InteractionJankMonitor.CujType int cujType) {
+            @CujType int cujType) {
         mCallback = callback;
         mRunner = runner;
         mCujType = cujType;
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 85ea809..7a3210e 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
@@ -637,7 +637,7 @@
      * @return the last time this bubble was updated or accessed, whichever is most recent.
      */
     long getLastActivity() {
-        return isAppBubble() ? Long.MAX_VALUE : Math.max(mLastUpdated, mLastAccessed);
+        return Math.max(mLastUpdated, mLastAccessed);
     }
 
     /**
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 595a4af..bbb4b74 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
@@ -784,8 +784,7 @@
         if (bubble.getPendingIntentCanceled()
                 || !(reason == Bubbles.DISMISS_AGED
                 || reason == Bubbles.DISMISS_USER_GESTURE
-                || reason == Bubbles.DISMISS_RELOAD_FROM_DISK)
-                || bubble.isAppBubble()) {
+                || reason == Bubbles.DISMISS_RELOAD_FROM_DISK)) {
             return;
         }
         if (DEBUG_BUBBLE_DATA) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/InteractionJankMonitorUtils.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/InteractionJankMonitorUtils.java
index ec344d3..86f00b8 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/InteractionJankMonitorUtils.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/InteractionJankMonitorUtils.java
@@ -23,6 +23,7 @@
 import android.view.SurfaceControl;
 import android.view.View;
 
+import com.android.internal.jank.Cuj.CujType;
 import com.android.internal.jank.InteractionJankMonitor;
 
 /** Utils class for simplfy InteractionJank trancing call */
@@ -31,11 +32,11 @@
     /**
      * Begin a trace session.
      *
-     * @param cujType the specific {@link InteractionJankMonitor.CujType}.
+     * @param cujType the specific {@link CujType}.
      * @param view the view to trace
      * @param tag the tag to distinguish different flow of same type CUJ.
      */
-    public static void beginTracing(@InteractionJankMonitor.CujType int cujType,
+    public static void beginTracing(@CujType int cujType,
             @NonNull View view, @Nullable String tag) {
         final InteractionJankMonitor.Configuration.Builder builder =
                 InteractionJankMonitor.Configuration.Builder.withView(cujType, view);
@@ -48,12 +49,12 @@
     /**
      * Begin a trace session.
      *
-     * @param cujType the specific {@link InteractionJankMonitor.CujType}.
+     * @param cujType the specific {@link CujType}.
      * @param context the context
      * @param surface the surface to trace
      * @param tag the tag to distinguish different flow of same type CUJ.
      */
-    public static void beginTracing(@InteractionJankMonitor.CujType int cujType,
+    public static void beginTracing(@CujType int cujType,
             @NonNull Context context, @NonNull SurfaceControl surface, @Nullable String tag) {
         final InteractionJankMonitor.Configuration.Builder builder =
                 InteractionJankMonitor.Configuration.Builder.withSurface(cujType, context, surface);
@@ -66,18 +67,18 @@
     /**
      * End a trace session.
      *
-     * @param cujType the specific {@link InteractionJankMonitor.CujType}.
+     * @param cujType the specific {@link CujType}.
      */
-    public static void endTracing(@InteractionJankMonitor.CujType int cujType) {
+    public static void endTracing(@CujType int cujType) {
         InteractionJankMonitor.getInstance().end(cujType);
     }
 
     /**
      * Cancel the trace session.
      *
-     * @param cujType the specific {@link InteractionJankMonitor.CujType}.
+     * @param cujType the specific {@link CujType}.
      */
-    public static void cancelTracing(@InteractionJankMonitor.CujType int cujType) {
+    public static void cancelTracing(@CujType int cujType) {
         InteractionJankMonitor.getInstance().cancel(cujType);
     }
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedDisplayAreaOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedDisplayAreaOrganizer.java
index 38ce164..d157ca8 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedDisplayAreaOrganizer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedDisplayAreaOrganizer.java
@@ -16,8 +16,8 @@
 
 package com.android.wm.shell.onehanded;
 
-import static com.android.internal.jank.InteractionJankMonitor.CUJ_ONE_HANDED_ENTER_TRANSITION;
-import static com.android.internal.jank.InteractionJankMonitor.CUJ_ONE_HANDED_EXIT_TRANSITION;
+import static com.android.internal.jank.Cuj.CUJ_ONE_HANDED_ENTER_TRANSITION;
+import static com.android.internal.jank.Cuj.CUJ_ONE_HANDED_EXIT_TRANSITION;
 import static com.android.wm.shell.onehanded.OneHandedAnimationController.TRANSITION_DIRECTION_EXIT;
 import static com.android.wm.shell.onehanded.OneHandedAnimationController.TRANSITION_DIRECTION_TRIGGER;
 
@@ -37,6 +37,7 @@
 import androidx.annotation.Nullable;
 import androidx.annotation.VisibleForTesting;
 
+import com.android.internal.jank.Cuj.CujType;
 import com.android.internal.jank.InteractionJankMonitor;
 import com.android.wm.shell.R;
 import com.android.wm.shell.common.DisplayLayout;
@@ -327,7 +328,7 @@
         mTransitionCallbacks.add(callback);
     }
 
-    void beginCUJTracing(@InteractionJankMonitor.CujType int cujType, @Nullable String tag) {
+    void beginCUJTracing(@CujType int cujType, @Nullable String tag) {
         final Map.Entry<WindowContainerToken, SurfaceControl> firstEntry =
                 getDisplayAreaTokenMap().entrySet().iterator().next();
         final InteractionJankMonitor.Configuration.Builder builder =
@@ -339,11 +340,11 @@
         mJankMonitor.begin(builder);
     }
 
-    void endCUJTracing(@InteractionJankMonitor.CujType int cujType) {
+    void endCUJTracing(@CujType int cujType) {
         mJankMonitor.end(cujType);
     }
 
-    void cancelCUJTracing(@InteractionJankMonitor.CujType int cujType) {
+    void cancelCUJTracing(@CujType int cujType) {
         mJankMonitor.cancel(cujType);
     }
 
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java
index 0f168c7..0367ba1 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java
@@ -255,6 +255,7 @@
         private ArrayList<TaskState> mOpeningTasks = null;
 
         private WindowContainerToken mPipTask = null;
+        private int mPipTaskId = -1;
         private WindowContainerToken mRecentsTask = null;
         private int mRecentsTaskId = -1;
         private TransitionInfo mInfo = null;
@@ -904,12 +905,14 @@
         @Override
         public void setFinishTaskTransaction(int taskId,
                 PictureInPictureSurfaceTransaction finishTransaction, SurfaceControl overlay) {
-            ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION,
-                    "[%d] RecentsController.setFinishTaskTransaction: taskId=%d",
-                    mInstanceId, taskId);
             mExecutor.execute(() -> {
+                ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION,
+                        "[%d] RecentsController.setFinishTaskTransaction: taskId=%d,"
+                                + " [mFinishCB is non-null]=%b",
+                        mInstanceId, taskId, mFinishCB != null);
                 if (mFinishCB == null) return;
                 mPipTransaction = finishTransaction;
+                mPipTaskId = taskId;
             });
         }
 
@@ -1003,10 +1006,35 @@
                     // the leaf-tasks are not "independent" so aren't hidden by normal setup).
                     t.hide(mPausingTasks.get(i).mTaskSurface);
                 }
-                if (mPipTask != null && mPipTransaction != null && sendUserLeaveHint) {
-                    t.show(mInfo.getChange(mPipTask).getLeash());
-                    PictureInPictureSurfaceTransaction.apply(mPipTransaction,
-                            mInfo.getChange(mPipTask).getLeash(), t);
+                if (mPipTransaction != null && sendUserLeaveHint) {
+                    SurfaceControl pipLeash = null;
+                    if (mPipTask != null) {
+                        pipLeash = mInfo.getChange(mPipTask).getLeash();
+                    } else if (mPipTaskId != -1) {
+                        // find a task with taskId from #setFinishTaskTransaction()
+                        for (TransitionInfo.Change change : mInfo.getChanges()) {
+                            if (change.getTaskInfo() != null
+                                    && change.getTaskInfo().taskId == mPipTaskId) {
+                                pipLeash = change.getLeash();
+                                ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION,
+                                        "RecentsController.finishInner:"
+                                                + " found a change with taskId=%d", mPipTaskId);
+                            }
+                        }
+                    }
+                    if (pipLeash == null) {
+                        ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION,
+                                "RecentsController.finishInner: no valid PiP leash;"
+                                        + "mPipTransaction=%s, mPipTask=%s, mPipTaskId=%d",
+                                mPipTransaction.toString(), mPipTask.toString(), mPipTaskId);
+                    } else {
+                        t.show(pipLeash);
+                        PictureInPictureSurfaceTransaction.apply(mPipTransaction, pipLeash, t);
+                        ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION,
+                                "RecentsController.finishInner: PiP transaction %s merged",
+                                mPipTransaction);
+                    }
+                    mPipTaskId = -1;
                     mPipTask = null;
                     mPipTransaction = null;
                 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java
index 56f1c78..7b57097 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java
@@ -847,9 +847,12 @@
                 .map(recentTasks -> recentTasks.findTaskInBackground(component, userId1))
                 .orElse(null);
         if (taskInfo != null) {
-            startTask(taskInfo.taskId, position, options);
-            ProtoLog.v(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN,
-                    "Start task in background");
+            if (ENABLE_SHELL_TRANSITIONS) {
+                mStageCoordinator.startTask(taskInfo.taskId, position, options);
+            } else {
+                startTask(taskInfo.taskId, position, options);
+            }
+            ProtoLog.v(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN, "Start task in background");
             return;
         }
         if (samePackage(packageName1, packageName2, userId1, userId2)) {
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 be685b5..449bef5 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
@@ -263,6 +263,10 @@
             mStartIntent2 = startIntent2;
             mActivatePosition = position;
         }
+        SplitRequest(int taskId1, int position) {
+            mActivateTaskId = taskId1;
+            mActivatePosition = position;
+        }
         SplitRequest(int taskId1, int taskId2, int position) {
             mActivateTaskId = taskId1;
             mActivateTaskId2 = taskId2;
@@ -556,6 +560,27 @@
         }
     }
 
+    /** Use this method to launch an existing Task via a taskId */
+    void startTask(int taskId, @SplitPosition int position, @Nullable Bundle options) {
+        mSplitRequest = new SplitRequest(taskId, position);
+        final WindowContainerTransaction wct = new WindowContainerTransaction();
+        options = resolveStartStage(STAGE_TYPE_UNDEFINED, position, options, null /* wct */);
+        wct.startTask(taskId, options);
+        // If this should be mixed, send the task to avoid split handle transition directly.
+        if (mMixedHandler != null && mMixedHandler.shouldSplitEnterMixed(taskId, mTaskOrganizer)) {
+            mTaskOrganizer.applyTransaction(wct);
+            return;
+        }
+
+        // If split screen is not activated, we're expecting to open a pair of apps to split.
+        final int extraTransitType = mMainStage.isActive()
+                ? TRANSIT_SPLIT_SCREEN_OPEN_TO_SIDE : TRANSIT_SPLIT_SCREEN_PAIR_OPEN;
+        prepareEnterSplitScreen(wct, null /* taskInfo */, position, !mIsDropEntering);
+
+        mSplitTransitions.startEnterTransition(TRANSIT_TO_FRONT, wct, null, this,
+                extraTransitType, !mIsDropEntering);
+    }
+
     /** Launches an activity into split. */
     void startIntent(PendingIntent intent, Intent fillInIntent, @SplitPosition int position,
             @Nullable Bundle options) {
@@ -1593,7 +1618,9 @@
             // Ensure to evict old splitting tasks because the new split pair might be composed by
             // one of the splitting tasks, evicting the task when finishing entering transition
             // won't guarantee to put the task to the indicated new position.
-            mMainStage.evictAllChildren(wct);
+            if (!mIsDropEntering) {
+                mMainStage.evictAllChildren(wct);
+            }
             mMainStage.reparentTopTask(wct);
             prepareSplitLayout(wct, resizeAnim);
         }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java
index ce7fef2..9f20f49 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java
@@ -48,9 +48,9 @@
 import android.window.WindowContainerTransaction;
 
 import com.android.internal.protolog.common.ProtoLog;
+import com.android.wm.shell.ShellTaskOrganizer;
 import com.android.wm.shell.activityembedding.ActivityEmbeddingController;
 import com.android.wm.shell.common.split.SplitScreenUtils;
-import com.android.wm.shell.desktopmode.DesktopModeStatus;
 import com.android.wm.shell.desktopmode.DesktopTasksController;
 import com.android.wm.shell.keyguard.KeyguardTransitionHandler;
 import com.android.wm.shell.pip.PipTransitionController;
@@ -298,7 +298,7 @@
             mixed.mLeftoversHandler = handler.first;
             mActiveTransitions.add(mixed);
             return handler.second;
-        } else if (mUnfoldHandler != null && mUnfoldHandler.hasUnfold(request)) {
+        } else if (mUnfoldHandler != null && mUnfoldHandler.shouldPlayUnfoldAnimation(request)) {
             final WindowContainerTransaction wct =
                     mUnfoldHandler.handleRequest(transition, request);
             if (wct != null) {
@@ -902,6 +902,18 @@
         return false;
     }
 
+    /** Use to when split use taskId to enter, check if this enter transition should be mixed or
+     * not.*/
+    public boolean shouldSplitEnterMixed(int taskId, ShellTaskOrganizer shellTaskOrganizer) {
+        // Check if this intent package is same as pip one or not, if true we want let the pip
+        // task enter split.
+        if (mPipHandler != null) {
+            return mPipHandler.isInPipPackage(
+                    SplitScreenUtils.getPackageName(taskId, shellTaskOrganizer));
+        }
+        return false;
+    }
+
     /** @return whether the transition-request represents a pip-entry. */
     public boolean requestHasPipEnter(TransitionRequestInfo request) {
         return mPipHandler.requestHasPipEnter(request);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/UnfoldTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/UnfoldTransitionHandler.java
index 07c5429..20ff79f 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/UnfoldTransitionHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/UnfoldTransitionHandler.java
@@ -106,7 +106,7 @@
             @NonNull SurfaceControl.Transaction startTransaction,
             @NonNull SurfaceControl.Transaction finishTransaction,
             @NonNull TransitionFinishCallback finishCallback) {
-        if (hasUnfold(info) && transition != mTransition) {
+        if (shouldPlayUnfoldAnimation(info) && transition != mTransition) {
             // Take over transition that has unfold, we might receive it if no other handler
             // accepted request in handleRequest, e.g. for rotation + unfold or
             // TRANSIT_NONE + unfold transitions
@@ -213,14 +213,36 @@
     }
 
     /** Whether `request` contains an unfold action. */
-    public boolean hasUnfold(@NonNull TransitionRequestInfo request) {
+    public boolean shouldPlayUnfoldAnimation(@NonNull TransitionRequestInfo request) {
+        // Unfold animation won't play when animations are disabled
+        if (!ValueAnimator.areAnimatorsEnabled()) return false;
+
         return (request.getType() == TRANSIT_CHANGE
                 && request.getDisplayChange() != null
-                && request.getDisplayChange().isPhysicalDisplayChanged());
+                && isUnfoldDisplayChange(request.getDisplayChange()));
+    }
+
+    private boolean isUnfoldDisplayChange(
+            @NonNull TransitionRequestInfo.DisplayChange displayChange) {
+        if (!displayChange.isPhysicalDisplayChanged()) {
+            return false;
+        }
+
+        if (displayChange.getStartAbsBounds() == null || displayChange.getEndAbsBounds() == null) {
+            return false;
+        }
+
+        // Handle only unfolding, currently we don't have an animation when folding
+        final int endArea =
+                displayChange.getEndAbsBounds().width() * displayChange.getEndAbsBounds().height();
+        final int startArea = displayChange.getStartAbsBounds().width()
+                * displayChange.getStartAbsBounds().height();
+
+        return endArea > startArea;
     }
 
     /** Whether `transitionInfo` contains an unfold action. */
-    public boolean hasUnfold(@NonNull TransitionInfo transitionInfo) {
+    public boolean shouldPlayUnfoldAnimation(@NonNull TransitionInfo transitionInfo) {
         // Unfold animation won't play when animations are disabled
         if (!ValueAnimator.areAnimatorsEnabled()) return false;
 
@@ -250,7 +272,7 @@
     @Override
     public WindowContainerTransaction handleRequest(@NonNull IBinder transition,
             @NonNull TransitionRequestInfo request) {
-        if (hasUnfold(request)) {
+        if (shouldPlayUnfoldAnimation(request)) {
             mTransition = transition;
             return new WindowContainerTransaction();
         }
diff --git a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/apps/MapsEnterPipTest.kt b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/apps/MapsEnterPipTest.kt
index d06cf77..e272958 100644
--- a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/apps/MapsEnterPipTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/apps/MapsEnterPipTest.kt
@@ -105,7 +105,8 @@
             )
             locationManager.setTestProviderEnabled(LocationManager.GPS_PROVIDER, true)
             mockLocationEnabled = true
-            mainHandler.post(updateLocation)
+            // postpone first location update to make sure GPS is set as test provider
+            mainHandler.postDelayed(updateLocation, 200)
 
             // normal app open through the Launcher All Apps
             // var mapsAddressOption = "Golden Gate Bridge"
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleDataTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleDataTest.java
index 4bca96b..dab762f 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleDataTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleDataTest.java
@@ -221,6 +221,22 @@
     }
 
     @Test
+    public void testAddAppBubble_setsTime() {
+        // Setup
+        mBubbleData.setListener(mListener);
+
+        // Test
+        assertThat(mAppBubble.getLastActivity()).isEqualTo(0);
+        setCurrentTime(1000);
+        mBubbleData.notificationEntryUpdated(mAppBubble, true /* suppressFlyout*/,
+                false /* showInShade */);
+
+        // Verify
+        assertThat(mBubbleData.getBubbleInStackWithKey(mAppBubble.getKey())).isEqualTo(mAppBubble);
+        assertThat(mAppBubble.getLastActivity()).isEqualTo(1000);
+    }
+
+    @Test
     public void testRemoveBubble() {
         // Setup
         sendUpdatedEntryAtTime(mEntryA1, 1000);
@@ -1162,7 +1178,7 @@
     }
 
     @Test
-    public void test_removeAppBubble_skipsOverflow() {
+    public void test_removeAppBubble_overflows() {
         String appBubbleKey = mAppBubble.getKey();
         mBubbleData.notificationEntryUpdated(mAppBubble, true /* suppressFlyout*/,
                 false /* showInShade */);
@@ -1170,7 +1186,7 @@
 
         mBubbleData.dismissBubbleWithKey(appBubbleKey, Bubbles.DISMISS_USER_GESTURE);
 
-        assertThat(mBubbleData.getOverflowBubbleWithKey(appBubbleKey)).isNull();
+        assertThat(mBubbleData.getOverflowBubbleWithKey(appBubbleKey)).isEqualTo(mAppBubble);
         assertThat(mBubbleData.getBubbleInStackWithKey(appBubbleKey)).isNull();
     }
 
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitScreenControllerTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitScreenControllerTests.java
index 99cd4f3..855b7ee 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitScreenControllerTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitScreenControllerTests.java
@@ -235,7 +235,7 @@
         mSplitScreenController.startIntent(pendingIntent, mContext.getUserId(), null,
                 SPLIT_POSITION_TOP_OR_LEFT, null);
 
-        verify(mSplitScreenController).startTask(anyInt(), eq(SPLIT_POSITION_TOP_OR_LEFT),
+        verify(mStageCoordinator).startTask(anyInt(), eq(SPLIT_POSITION_TOP_OR_LEFT),
                 isNull());
         verify(mSplitScreenController, never()).supportMultiInstancesSplit(any());
         verify(mStageCoordinator, never()).switchSplitPosition(any());
@@ -243,7 +243,6 @@
 
     @Test
     public void startIntent_multiInstancesSupported_startTaskInBackgroundAfterSplitActivated() {
-        doReturn(true).when(mSplitScreenController).supportMultiInstancesSplit(any());
         doNothing().when(mSplitScreenController).startTask(anyInt(), anyInt(), any());
         Intent startIntent = createStartIntent("startActivity");
         PendingIntent pendingIntent =
@@ -260,8 +259,8 @@
 
         mSplitScreenController.startIntent(pendingIntent, mContext.getUserId(), null,
                 SPLIT_POSITION_TOP_OR_LEFT, null);
-
-        verify(mSplitScreenController).startTask(anyInt(), eq(SPLIT_POSITION_TOP_OR_LEFT),
+        verify(mSplitScreenController, never()).supportMultiInstancesSplit(any());
+        verify(mStageCoordinator).startTask(anyInt(), eq(SPLIT_POSITION_TOP_OR_LEFT),
                 isNull());
     }
 
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/unfold/UnfoldTransitionHandlerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/unfold/UnfoldTransitionHandlerTest.java
index 6d73c12..fc1fe1c 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/unfold/UnfoldTransitionHandlerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/unfold/UnfoldTransitionHandlerTest.java
@@ -98,10 +98,13 @@
     }
 
     @Test
-    public void handleRequest_physicalDisplayChange_handlesTransition() {
+    public void handleRequest_physicalDisplayChangeUnfold_handlesTransition() {
         ActivityManager.RunningTaskInfo triggerTaskInfo = new ActivityManager.RunningTaskInfo();
         TransitionRequestInfo.DisplayChange displayChange = new TransitionRequestInfo.DisplayChange(
-                Display.DEFAULT_DISPLAY).setPhysicalDisplayChanged(true);
+                Display.DEFAULT_DISPLAY)
+                .setPhysicalDisplayChanged(true)
+                .setStartAbsBounds(new Rect(0, 0, 100, 100))
+                .setEndAbsBounds(new Rect(0, 0, 200, 200));
         TransitionRequestInfo requestInfo = new TransitionRequestInfo(TRANSIT_CHANGE,
                 triggerTaskInfo, /* remoteTransition= */ null, displayChange, 0 /* flags */);
 
@@ -112,6 +115,23 @@
     }
 
     @Test
+    public void handleRequest_physicalDisplayChangeFold_doesNotHandleTransition() {
+        ActivityManager.RunningTaskInfo triggerTaskInfo = new ActivityManager.RunningTaskInfo();
+        TransitionRequestInfo.DisplayChange displayChange = new TransitionRequestInfo.DisplayChange(
+                Display.DEFAULT_DISPLAY)
+                .setPhysicalDisplayChanged(true)
+                .setStartAbsBounds(new Rect(0, 0, 200, 200))
+                .setEndAbsBounds(new Rect(0, 0, 100, 100));
+        TransitionRequestInfo requestInfo = new TransitionRequestInfo(TRANSIT_CHANGE,
+                triggerTaskInfo, /* remoteTransition= */ null, displayChange, 0 /* flags */);
+
+        WindowContainerTransaction result = mUnfoldTransitionHandler.handleRequest(mTransition,
+                requestInfo);
+
+        assertThat(result).isNull();
+    }
+
+    @Test
     public void handleRequest_noPhysicalDisplayChange_doesNotHandleTransition() {
         ActivityManager.RunningTaskInfo triggerTaskInfo = new ActivityManager.RunningTaskInfo();
         TransitionRequestInfo.DisplayChange displayChange = new TransitionRequestInfo.DisplayChange(
@@ -306,7 +326,10 @@
     private TransitionRequestInfo createUnfoldTransitionRequestInfo() {
         ActivityManager.RunningTaskInfo triggerTaskInfo = new ActivityManager.RunningTaskInfo();
         TransitionRequestInfo.DisplayChange displayChange = new TransitionRequestInfo.DisplayChange(
-                Display.DEFAULT_DISPLAY).setPhysicalDisplayChanged(true);
+                Display.DEFAULT_DISPLAY)
+                .setPhysicalDisplayChanged(true)
+                .setStartAbsBounds(new Rect(0, 0, 100, 100))
+                .setEndAbsBounds(new Rect(0, 0, 200, 200));
         return new TransitionRequestInfo(TRANSIT_CHANGE,
                 triggerTaskInfo, /* remoteTransition= */ null, displayChange, 0 /* flags */);
     }
diff --git a/libs/hwui/jni/YuvToJpegEncoder.cpp b/libs/hwui/jni/YuvToJpegEncoder.cpp
index 0275e4f..3533001 100644
--- a/libs/hwui/jni/YuvToJpegEncoder.cpp
+++ b/libs/hwui/jni/YuvToJpegEncoder.cpp
@@ -401,7 +401,7 @@
     if (int success = jpegREncoder.encodeJPEGR(&p010, &yuv420,
             hdrTransferFunction,
             &jpegR, jpegQuality,
-            exif.length > 0 ? &exif : NULL); success != android::OK) {
+            exif.length > 0 ? &exif : NULL); success != JPEGR_NO_ERROR) {
         ALOGW("Encode JPEG/R failed, error code: %d.", success);
         return false;
     }
diff --git a/libs/input/PointerController.cpp b/libs/input/PointerController.cpp
index 65e1605..bba9c97 100644
--- a/libs/input/PointerController.cpp
+++ b/libs/input/PointerController.cpp
@@ -218,6 +218,15 @@
 
     mLocked.presentation = presentation;
 
+    if (input_flags::enable_pointer_choreographer()) {
+        // When pointer choreographer is enabled, the presentation mode is only set once when the
+        // PointerController is constructed, before the display viewport is provided.
+        // TODO(b/293587049): Clean up the PointerController interface after pointer choreographer
+        // is permanently enabled. The presentation can be set in the constructor.
+        mCursorController.setStylusHoverMode(presentation == Presentation::STYLUS_HOVER);
+        return;
+    }
+
     if (!mCursorController.isViewportValid()) {
         return;
     }
diff --git a/location/java/android/location/GnssNavigationMessage.java b/location/java/android/location/GnssNavigationMessage.java
index 32e636f..5e3f803 100644
--- a/location/java/android/location/GnssNavigationMessage.java
+++ b/location/java/android/location/GnssNavigationMessage.java
@@ -78,7 +78,7 @@
     public static final int TYPE_GAL_F = 0x0602;
     /**
      * NavIC L5 C/A message contained in the structure.
-     * @deprecated Use {@link #TYPE_IRN_L5} instead.
+     * @deprecated deprecated.
      */
     @Deprecated
     public static final int TYPE_IRN_L5CA = 0x0701;
diff --git a/media/java/android/media/flags/media_better_together.aconfig b/media/java/android/media/flags/media_better_together.aconfig
index 3c0b002..07f63e5 100644
--- a/media/java/android/media/flags/media_better_together.aconfig
+++ b/media/java/android/media/flags/media_better_together.aconfig
@@ -62,3 +62,10 @@
     description: "Allows clients of privileged MediaRouter2 that hold INTERACT_ACROSS_USERS_FULL to control routing across users."
     bug: "288580225"
 }
+
+flag {
+    name: "enable_use_of_bluetooth_device_get_alias_for_mr2info_get_name"
+    namespace: "media_solutions"
+    description: "Use BluetoothDevice.getAlias to populate the name of Bluetooth MediaRoute2Infos."
+    bug: "314324170"
+}
diff --git a/packages/SettingsLib/Spa/build.gradle.kts b/packages/SettingsLib/Spa/build.gradle.kts
index 60bf48c..8b136da 100644
--- a/packages/SettingsLib/Spa/build.gradle.kts
+++ b/packages/SettingsLib/Spa/build.gradle.kts
@@ -29,7 +29,7 @@
 
 allprojects {
     extra["androidTop"] = androidTop
-    extra["jetpackComposeVersion"] = "1.6.0-beta01"
+    extra["jetpackComposeVersion"] = "1.6.0-beta02"
 }
 
 subprojects {
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/GallerySpaEnvironment.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/GallerySpaEnvironment.kt
index 90c7d46..f4edb36 100644
--- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/GallerySpaEnvironment.kt
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/GallerySpaEnvironment.kt
@@ -48,6 +48,7 @@
 import com.android.settingslib.spa.gallery.preference.SwitchPreferencePageProvider
 import com.android.settingslib.spa.gallery.preference.TwoTargetSwitchPreferencePageProvider
 import com.android.settingslib.spa.gallery.scaffold.SearchScaffoldPageProvider
+import com.android.settingslib.spa.gallery.scaffold.SuwScaffoldPageProvider
 import com.android.settingslib.spa.gallery.ui.CategoryPageProvider
 import com.android.settingslib.spa.gallery.ui.CopyablePageProvider
 import com.android.settingslib.spa.gallery.ui.SpinnerPageProvider
@@ -100,6 +101,7 @@
                 SettingsExposedDropdownMenuCheckBoxProvider,
                 SettingsTextFieldPasswordPageProvider,
                 SearchScaffoldPageProvider,
+                SuwScaffoldPageProvider,
                 CardPageProvider,
                 CopyablePageProvider,
             ),
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/home/HomePageProvider.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/home/HomePageProvider.kt
index 1d897f7..6a2e598 100644
--- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/home/HomePageProvider.kt
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/home/HomePageProvider.kt
@@ -43,6 +43,7 @@
 import com.android.settingslib.spa.gallery.page.SliderPageProvider
 import com.android.settingslib.spa.gallery.preference.PreferenceMainPageProvider
 import com.android.settingslib.spa.gallery.scaffold.SearchScaffoldPageProvider
+import com.android.settingslib.spa.gallery.scaffold.SuwScaffoldPageProvider
 import com.android.settingslib.spa.gallery.ui.CategoryPageProvider
 import com.android.settingslib.spa.gallery.ui.CopyablePageProvider
 import com.android.settingslib.spa.gallery.ui.SpinnerPageProvider
@@ -59,6 +60,7 @@
             OperateListPageProvider.buildInjectEntry().setLink(fromPage = owner).build(),
             ArgumentPageProvider.buildInjectEntry("foo")!!.setLink(fromPage = owner).build(),
             SearchScaffoldPageProvider.buildInjectEntry().setLink(fromPage = owner).build(),
+            SuwScaffoldPageProvider.buildInjectEntry().setLink(fromPage = owner).build(),
             SliderPageProvider.buildInjectEntry().setLink(fromPage = owner).build(),
             SpinnerPageProvider.buildInjectEntry().setLink(fromPage = owner).build(),
             SettingsPagerPageProvider.buildInjectEntry().setLink(fromPage = owner).build(),
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/scaffold/SuwScaffoldPageProvider.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/scaffold/SuwScaffoldPageProvider.kt
new file mode 100644
index 0000000..6fc8de3
--- /dev/null
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/scaffold/SuwScaffoldPageProvider.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.settingslib.spa.gallery.scaffold
+
+import android.os.Bundle
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.padding
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.outlined.SignalCellularAlt
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import com.android.settingslib.spa.framework.common.SettingsEntryBuilder
+import com.android.settingslib.spa.framework.common.SettingsPageProvider
+import com.android.settingslib.spa.framework.common.createSettingsPage
+import com.android.settingslib.spa.framework.compose.navigator
+import com.android.settingslib.spa.framework.theme.SettingsDimension
+import com.android.settingslib.spa.gallery.R
+import com.android.settingslib.spa.widget.illustration.Illustration
+import com.android.settingslib.spa.widget.illustration.IllustrationModel
+import com.android.settingslib.spa.widget.illustration.ResourceType
+import com.android.settingslib.spa.widget.preference.Preference
+import com.android.settingslib.spa.widget.preference.PreferenceModel
+import com.android.settingslib.spa.widget.scaffold.BottomAppBarButton
+import com.android.settingslib.spa.widget.scaffold.SuwScaffold
+import com.android.settingslib.spa.widget.ui.SettingsBody
+
+private const val TITLE = "Sample SuwScaffold"
+
+object SuwScaffoldPageProvider : SettingsPageProvider {
+    override val name = "SuwScaffold"
+
+    private val owner = createSettingsPage()
+
+    fun buildInjectEntry() = SettingsEntryBuilder.createInject(owner = owner)
+        .setUiLayoutFn {
+            Preference(object : PreferenceModel {
+                override val title = TITLE
+                override val onClick = navigator(name)
+            })
+        }
+
+    @Composable
+    override fun Page(arguments: Bundle?) {
+        Page()
+    }
+}
+
+@Composable
+private fun Page() {
+    SuwScaffold(
+        imageVector = Icons.Outlined.SignalCellularAlt,
+        title = "Connect to mobile network",
+        actionButton = BottomAppBarButton("Next") {},
+        dismissButton = BottomAppBarButton("Cancel") {},
+    ) {
+        Column(Modifier.padding(SettingsDimension.itemPadding)) {
+            SettingsBody("To add another SIM, download a new eSIM.")
+        }
+        Illustration(object : IllustrationModel {
+            override val resId = R.drawable.accessibility_captioning_banner
+            override val resourceType = ResourceType.IMAGE
+        })
+        Column(Modifier.padding(SettingsDimension.itemPadding)) {
+            SettingsBody("To add another SIM, download a new eSIM.")
+        }
+        Illustration(object : IllustrationModel {
+            override val resId = R.drawable.accessibility_captioning_banner
+            override val resourceType = ResourceType.IMAGE
+        })
+    }
+}
diff --git a/packages/SettingsLib/Spa/spa/build.gradle.kts b/packages/SettingsLib/Spa/spa/build.gradle.kts
index acd90f3..7eccfe5 100644
--- a/packages/SettingsLib/Spa/spa/build.gradle.kts
+++ b/packages/SettingsLib/Spa/spa/build.gradle.kts
@@ -57,7 +57,7 @@
     api("androidx.slice:slice-builders:1.1.0-alpha02")
     api("androidx.slice:slice-core:1.1.0-alpha02")
     api("androidx.slice:slice-view:1.1.0-alpha02")
-    api("androidx.compose.material3:material3:1.2.0-alpha11")
+    api("androidx.compose.material3:material3:1.2.0-alpha12")
     api("androidx.compose.material:material-icons-extended:$jetpackComposeVersion")
     api("androidx.compose.runtime:runtime-livedata:$jetpackComposeVersion")
     api("androidx.compose.ui:ui-tooling-preview:$jetpackComposeVersion")
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsDimension.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsDimension.kt
index 5a1120e..c143390 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsDimension.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsDimension.kt
@@ -37,11 +37,13 @@
     val itemPaddingAround = 8.dp
     val itemDividerHeight = 32.dp
 
+    val iconLarge = 48.dp
+
     /** The size when app icon is displayed in list. */
     val appIconItemSize = 32.dp
 
     /** The size when app icon is displayed in App info page. */
-    val appIconInfoSize = 48.dp
+    val appIconInfoSize = iconLarge
 
     /** The vertical padding for buttons. */
     val buttonPaddingVertical = 12.dp
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/chart/BarChart.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/chart/BarChart.kt
index e761a33..caceb6f 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/chart/BarChart.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/chart/BarChart.kt
@@ -38,6 +38,8 @@
 import com.android.settingslib.spa.framework.theme.divider
 import com.github.mikephil.charting.charts.BarChart
 import com.github.mikephil.charting.components.XAxis
+import com.github.mikephil.charting.components.YAxis
+import com.github.mikephil.charting.components.YAxis.AxisDependency
 import com.github.mikephil.charting.data.BarData
 import com.github.mikephil.charting.data.BarDataSet
 import com.github.mikephil.charting.data.BarEntry
@@ -90,6 +92,10 @@
     /** If set to true, touch gestures are enabled on the [BarChart]. */
     val enableBarchartTouch: Boolean
         get() = true
+
+    /** The renderer provider for x-axis. */
+    val xAxisRendererProvider: XAxisRendererProvider?
+        get() = null
 }
 
 data class BarChartData(
@@ -143,6 +149,16 @@
                                 yOffset = 10f
                             }
 
+                            barChartModel.xAxisRendererProvider?.let {
+                                setXAxisRenderer(
+                                    it.provideXAxisRenderer(
+                                        getViewPortHandler(),
+                                        getXAxis(),
+                                        getTransformer(YAxis.AxisDependency.LEFT)
+                                    )
+                                )
+                            }
+
                             axisLeft.apply {
                                 axisMaximum = barChartModel.yAxisMaxValue
                                 axisMinimum = barChartModel.yAxisMinValue
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/chart/XAxisRendererProvider.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/chart/XAxisRendererProvider.kt
new file mode 100644
index 0000000..6569d25
--- /dev/null
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/chart/XAxisRendererProvider.kt
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.spa.widget.chart
+
+import com.github.mikephil.charting.components.XAxis
+import com.github.mikephil.charting.renderer.XAxisRenderer
+import com.github.mikephil.charting.utils.Transformer
+import com.github.mikephil.charting.utils.ViewPortHandler
+
+/** A provider for [XAxisRenderer] objects. */
+fun interface XAxisRendererProvider {
+
+  /** Provides an object of [XAxisRenderer] type. */
+  fun provideXAxisRenderer(
+    viewPortHandler: ViewPortHandler,
+    xAxis: XAxis,
+    transformer: Transformer
+  ): XAxisRenderer
+}
\ No newline at end of file
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/editor/SettingsOutlinedTextField.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/editor/SettingsOutlinedTextField.kt
index 3819a10..e0dd4e1 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/editor/SettingsOutlinedTextField.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/editor/SettingsOutlinedTextField.kt
@@ -34,6 +34,7 @@
 fun SettingsOutlinedTextField(
     value: String,
     label: String,
+    errorMessage: String? = null,
     singleLine: Boolean = true,
     enabled: Boolean = true,
     onTextChange: (String) -> Unit,
@@ -49,6 +50,12 @@
         },
         singleLine = singleLine,
         enabled = enabled,
+        isError = errorMessage != null,
+        supportingText = {
+            if (errorMessage != null) {
+                Text(text = errorMessage)
+            }
+        }
     )
 }
 
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/SuwScaffold.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/SuwScaffold.kt
new file mode 100644
index 0000000..354b95d
--- /dev/null
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/SuwScaffold.kt
@@ -0,0 +1,144 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.spa.widget.scaffold
+
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.BoxWithConstraints
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.ColumnScope
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.size
+import androidx.compose.foundation.rememberScrollState
+import androidx.compose.foundation.verticalScroll
+import androidx.compose.material3.Button
+import androidx.compose.material3.Icon
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.Scaffold
+import androidx.compose.material3.Text
+import androidx.compose.material3.TextButton
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.vector.ImageVector
+import com.android.settingslib.spa.framework.theme.SettingsDimension
+import com.android.settingslib.spa.framework.theme.toMediumWeight
+
+data class BottomAppBarButton(
+    val text: String,
+    val onClick: () -> Unit,
+)
+
+@Composable
+fun SuwScaffold(
+    imageVector: ImageVector,
+    title: String,
+    actionButton: BottomAppBarButton? = null,
+    dismissButton: BottomAppBarButton? = null,
+    content: @Composable ColumnScope.() -> Unit,
+) {
+    ActivityTitle(title)
+    Scaffold { innerPadding ->
+        BoxWithConstraints(
+            Modifier
+                .padding(innerPadding)
+                .padding(top = SettingsDimension.itemPaddingAround)
+        ) {
+            // Use single column layout in portrait, two columns in landscape.
+            val useSingleColumn = maxWidth < maxHeight
+            if (useSingleColumn) {
+                Column {
+                    Column(
+                        Modifier
+                            .weight(1f)
+                            .verticalScroll(rememberScrollState())
+                    ) {
+                        Header(imageVector, title)
+                        content()
+                    }
+                    BottomBar(actionButton, dismissButton)
+                }
+            } else {
+                Column(Modifier.padding(horizontal = SettingsDimension.itemPaddingAround)) {
+                    Row((Modifier.weight(1f))) {
+                        Box(Modifier.weight(1f)) {
+                            Header(imageVector, title)
+                        }
+                        Column(
+                            Modifier
+                                .weight(1f)
+                                .verticalScroll(rememberScrollState())) {
+                            content()
+                        }
+                    }
+                    BottomBar(actionButton, dismissButton)
+                }
+            }
+        }
+    }
+}
+
+@Composable
+private fun Header(
+    imageVector: ImageVector,
+    title: String
+) {
+    Column(Modifier.padding(SettingsDimension.itemPadding)) {
+        Icon(
+            imageVector = imageVector,
+            contentDescription = null,
+            modifier = Modifier
+                .padding(vertical = SettingsDimension.itemPaddingAround)
+                .size(SettingsDimension.iconLarge),
+            tint = MaterialTheme.colorScheme.primary,
+        )
+        Text(
+            text = title,
+            modifier = Modifier.padding(vertical = SettingsDimension.itemPaddingVertical),
+            color = MaterialTheme.colorScheme.onSurface,
+            style = MaterialTheme.typography.displaySmall,
+        )
+    }
+}
+
+@Composable
+private fun BottomBar(
+    actionButton: BottomAppBarButton?,
+    dismissButton: BottomAppBarButton?,
+) {
+    Row(modifier = Modifier.padding(SettingsDimension.itemPaddingAround)) {
+        dismissButton?.apply {
+            TextButton(onClick) {
+                ActionText(text)
+            }
+        }
+        Spacer(modifier = Modifier.weight(1f))
+        actionButton?.apply {
+            Button(onClick) {
+                ActionText(text)
+            }
+        }
+    }
+}
+
+@Composable
+private fun ActionText(text: String) {
+    Text(
+        text = text,
+        style = MaterialTheme.typography.bodyMedium.toMediumWeight(),
+    )
+}
diff --git a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/scaffold/SuwScaffoldTest.kt b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/scaffold/SuwScaffoldTest.kt
new file mode 100644
index 0000000..35c9f78
--- /dev/null
+++ b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/scaffold/SuwScaffoldTest.kt
@@ -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.settingslib.spa.widget.scaffold
+
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.outlined.SignalCellularAlt
+import androidx.compose.material3.Text
+import androidx.compose.ui.test.assertIsDisplayed
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.test.onNodeWithText
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+class SuwScaffoldTest {
+    @get:Rule
+    val composeTestRule = createComposeRule()
+
+    @Test
+    fun suwScaffold_titleIsDisplayed() {
+        composeTestRule.setContent {
+            SuwScaffold(imageVector = Icons.Outlined.SignalCellularAlt, title = TITLE) {
+                Text(text = "AAA")
+                Text(text = "BBB")
+            }
+        }
+
+        composeTestRule.onNodeWithText(TITLE).assertIsDisplayed()
+    }
+
+    @Test
+    fun suwScaffold_itemsAreDisplayed() {
+        composeTestRule.setContent {
+            SuwScaffold(imageVector = Icons.Outlined.SignalCellularAlt, title = TITLE) {
+                Text(text = "AAA")
+                Text(text = "BBB")
+            }
+        }
+
+        composeTestRule.onNodeWithText("AAA").assertIsDisplayed()
+        composeTestRule.onNodeWithText("BBB").assertIsDisplayed()
+    }
+
+    @Test
+    fun suwScaffold_actionButtonDisplayed() {
+        composeTestRule.setContent {
+            SuwScaffold(
+                imageVector = Icons.Outlined.SignalCellularAlt,
+                title = TITLE,
+                actionButton = BottomAppBarButton(TEXT) {},
+            ) {}
+        }
+
+        composeTestRule.onNodeWithText(TEXT).assertIsDisplayed()
+    }
+
+    @Test
+    fun suwScaffold_dismissButtonDisplayed() {
+        composeTestRule.setContent {
+            SuwScaffold(
+                imageVector = Icons.Outlined.SignalCellularAlt,
+                title = TITLE,
+                dismissButton = BottomAppBarButton(TEXT) {},
+            ) {}
+        }
+
+        composeTestRule.onNodeWithText(TEXT).assertIsDisplayed()
+    }
+
+    private companion object {
+        const val TITLE = "Title"
+        const val TEXT = "Text"
+    }
+}
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/enterprise/RestrictedMode.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/enterprise/RestrictedMode.kt
index 3acc9ad..b6d9242 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/enterprise/RestrictedMode.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/enterprise/RestrictedMode.kt
@@ -18,6 +18,7 @@
 
 import android.app.admin.DevicePolicyResources.Strings.Settings
 import android.content.Context
+import android.content.Intent
 import com.android.settingslib.RestrictedLockUtils
 import com.android.settingslib.widget.restricted.R
 
@@ -32,6 +33,11 @@
     fun sendShowAdminSupportDetailsIntent()
 }
 
+interface BlockedByEcm : RestrictedMode {
+    fun showRestrictedSettingsDetails()
+}
+
+
 internal data class BlockedByAdminImpl(
     private val context: Context,
     private val enforcedAdmin: RestrictedLockUtils.EnforcedAdmin,
@@ -55,3 +61,13 @@
         RestrictedLockUtils.sendShowAdminSupportDetailsIntent(context, enforcedAdmin)
     }
 }
+
+internal data class BlockedByEcmImpl(
+    private val context: Context,
+    private val intent: Intent,
+) : BlockedByEcm {
+
+    override fun showRestrictedSettingsDetails() {
+        context.startActivity(intent)
+    }
+}
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/enterprise/RestrictionsProvider.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/enterprise/RestrictionsProvider.kt
index 550966b..9432d59 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/enterprise/RestrictionsProvider.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/enterprise/RestrictionsProvider.kt
@@ -29,10 +29,20 @@
 import kotlinx.coroutines.flow.flow
 import kotlinx.coroutines.flow.flowOn
 
+data class EnhancedConfirmation(
+    val key: String,
+    val uid: Int,
+    val packageName: String,
+)
 data class Restrictions(
     val userId: Int = UserHandle.myUserId(),
     val keys: List<String>,
-)
+    val enhancedConfirmation: EnhancedConfirmation? = null,
+) {
+    fun isEmpty(): Boolean {
+        return keys.isEmpty() && enhancedConfirmation == null
+    }
+}
 
 interface RestrictionsProvider {
     @Composable
@@ -77,6 +87,14 @@
                 .checkIfRestrictionEnforced(context, key, restrictions.userId)
                 ?.let { return BlockedByAdminImpl(context = context, enforcedAdmin = it) }
         }
+
+        restrictions.enhancedConfirmation?.let { ec ->
+            RestrictedLockUtilsInternal
+                    .checkIfRequiresEnhancedConfirmation(context, ec.key,
+                        ec.uid, ec.packageName)
+                    ?.let { intent -> return BlockedByEcmImpl(context = context, intent = intent) }
+        }
+
         return NoRestricted
     }
 }
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppOpPermissionAppList.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppOpPermissionAppList.kt
index 06b3eab..25c3bc5 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppOpPermissionAppList.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppOpPermissionAppList.kt
@@ -47,6 +47,9 @@
     abstract val appOp: Int
     abstract val permission: String
 
+    override val enhancedConfirmationKey: String?
+        get() = AppOpsManager.opToPublicName(appOp)
+
     /**
      * When set, specifies the broader permission who trumps the [permission].
      *
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppInfoPage.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppInfoPage.kt
index 5655436..1c830c1 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppInfoPage.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppInfoPage.kt
@@ -41,6 +41,7 @@
 import com.android.settingslib.spaprivileged.model.app.IPackageManagers
 import com.android.settingslib.spaprivileged.model.app.PackageManagers
 import com.android.settingslib.spaprivileged.model.app.toRoute
+import com.android.settingslib.spaprivileged.model.enterprise.EnhancedConfirmation
 import com.android.settingslib.spaprivileged.model.enterprise.Restrictions
 import com.android.settingslib.spaprivileged.model.enterprise.RestrictionsProviderFactory
 import com.android.settingslib.spaprivileged.model.enterprise.RestrictionsProviderImpl
@@ -154,7 +155,12 @@
             override val changeable = { isChangeable }
             override val onCheckedChange: (Boolean) -> Unit = { setAllowed(record, it) }
         }
-        val restrictions = Restrictions(userId, switchRestrictionKeys)
+        val restrictions = Restrictions(userId = userId,
+            keys = switchRestrictionKeys,
+            enhancedConfirmation = enhancedConfirmationKey?.let { EnhancedConfirmation(
+                key = it,
+                uid = checkNotNull(applicationInfo).uid,
+                packageName = packageName) })
         RestrictedSwitchPreference(switchModel, restrictions, restrictionsProviderFactory)
     }
 }
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppList.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppList.kt
index 8704f20..916d83a 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppList.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppList.kt
@@ -36,6 +36,9 @@
     val switchRestrictionKeys: List<String>
         get() = emptyList()
 
+    val enhancedConfirmationKey: String?
+        get() = null
+
     /**
      * Loads the extra info for the App List, and generates the [AppRecord] List.
      *
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppListPage.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppListPage.kt
index 36c91f4..4b47437 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppListPage.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppListPage.kt
@@ -41,6 +41,7 @@
 import com.android.settingslib.spaprivileged.model.app.AppListModel
 import com.android.settingslib.spaprivileged.model.app.AppRecord
 import com.android.settingslib.spaprivileged.model.app.userId
+import com.android.settingslib.spaprivileged.model.enterprise.EnhancedConfirmation
 import com.android.settingslib.spaprivileged.model.enterprise.Restrictions
 import com.android.settingslib.spaprivileged.model.enterprise.RestrictionsProviderFactory
 import com.android.settingslib.spaprivileged.model.enterprise.RestrictionsProviderImpl
@@ -149,11 +150,17 @@
 
     @Composable
     fun getSummary(record: T): () -> String {
-        val restrictions = remember(record.app.userId) {
+        val restrictions = remember(record.app.userId,
+                record.app.uid, record.app.packageName) {
             Restrictions(
                 userId = record.app.userId,
                 keys = listModel.switchRestrictionKeys,
-            )
+                enhancedConfirmation = listModel.enhancedConfirmationKey?.let {
+                    EnhancedConfirmation(
+                        key = it,
+                        uid = record.app.uid,
+                        packageName = record.app.packageName)
+                })
         }
         val restrictedMode by restrictionsProviderFactory.rememberRestrictedMode(restrictions)
         val allowed = listModel.isAllowed(record)
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/preference/RestrictedSwitchPreference.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/preference/RestrictedSwitchPreference.kt
index d5c5574..cd72025 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/preference/RestrictedSwitchPreference.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/preference/RestrictedSwitchPreference.kt
@@ -40,7 +40,7 @@
     restrictions: Restrictions,
     restrictionsProviderFactory: RestrictionsProviderFactory,
 ) {
-    if (restrictions.keys.isEmpty()) {
+    if (restrictions.isEmpty()) {
         SwitchPreference(model)
         return
     }
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/preference/RestrictedSwitchPreferenceModel.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/preference/RestrictedSwitchPreferenceModel.kt
index fa44ecb..aba3460 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/preference/RestrictedSwitchPreferenceModel.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/preference/RestrictedSwitchPreferenceModel.kt
@@ -31,6 +31,7 @@
 import com.android.settingslib.spaprivileged.framework.compose.getPlaceholder
 import com.android.settingslib.spaprivileged.model.enterprise.BaseUserRestricted
 import com.android.settingslib.spaprivileged.model.enterprise.BlockedByAdmin
+import com.android.settingslib.spaprivileged.model.enterprise.BlockedByEcm
 import com.android.settingslib.spaprivileged.model.enterprise.NoRestricted
 import com.android.settingslib.spaprivileged.model.enterprise.RestrictedMode
 import com.android.settingslib.spaprivileged.model.enterprise.Restrictions
@@ -56,6 +57,7 @@
         is NoRestricted -> model.checked
         is BaseUserRestricted -> ({ false })
         is BlockedByAdmin -> model.checked
+        is BlockedByEcm -> model.checked
     }
 
     override val changeable = if (restrictedMode is NoRestricted) model.changeable else ({ false })
@@ -68,24 +70,42 @@
         is BaseUserRestricted -> model.onCheckedChange
         // Pass null since semantics ToggleableState is provided in RestrictionWrapper.
         is BlockedByAdmin -> null
+        is BlockedByEcm -> null
     }
 
     @Composable
     fun RestrictionWrapper(content: @Composable () -> Unit) {
-        if (restrictedMode !is BlockedByAdmin) {
-            content()
-            return
+        when (restrictedMode) {
+            is BlockedByAdmin -> {
+                Box(
+                    Modifier
+                            .clickable(
+                                role = Role.Switch,
+                                onClick = { restrictedMode.sendShowAdminSupportDetailsIntent() },
+                            )
+                            .semantics {
+                                this.toggleableState = ToggleableState(checked())
+                            },
+                ) { content() }
+            }
+
+            is BlockedByEcm -> {
+                Box(
+                    Modifier
+                            .clickable(
+                                role = Role.Switch,
+                                onClick = { restrictedMode.showRestrictedSettingsDetails() },
+                            )
+                            .semantics {
+                                this.toggleableState = ToggleableState(checked())
+                            },
+                ) { content() }
+            }
+
+            else -> {
+                content()
+            }
         }
-        Box(
-            Modifier
-                .clickable(
-                    role = Role.Switch,
-                    onClick = { restrictedMode.sendShowAdminSupportDetailsIntent() },
-                )
-                .semantics {
-                    this.toggleableState = ToggleableState(checked())
-                },
-        ) { content() }
     }
 
     private fun ToggleableState(value: Boolean?) = when (value) {
@@ -123,6 +143,9 @@
                     context.getString(com.android.settingslib.R.string.disabled)
 
                 is BlockedByAdmin -> restrictedMode.getSummary(checked())
+                is BlockedByEcm ->
+                    context.getString(com.android.settingslib.R.string.disabled)
+
                 null -> context.getPlaceholder()
             }
         }
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/scaffold/RestrictedMenuItem.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/scaffold/RestrictedMenuItem.kt
index f9abefc..977615b 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/scaffold/RestrictedMenuItem.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/scaffold/RestrictedMenuItem.kt
@@ -21,6 +21,7 @@
 import com.android.settingslib.spa.widget.scaffold.MoreOptionsScope
 import com.android.settingslib.spaprivileged.model.enterprise.BaseUserRestricted
 import com.android.settingslib.spaprivileged.model.enterprise.BlockedByAdmin
+import com.android.settingslib.spaprivileged.model.enterprise.BlockedByEcm
 import com.android.settingslib.spaprivileged.model.enterprise.Restrictions
 import com.android.settingslib.spaprivileged.model.enterprise.RestrictionsProviderFactory
 import com.android.settingslib.spaprivileged.model.enterprise.RestrictionsProviderImpl
@@ -47,6 +48,7 @@
     MenuItem(text = text, enabled = restrictedMode !== BaseUserRestricted) {
         when (restrictedMode) {
             is BlockedByAdmin -> restrictedMode.sendShowAdminSupportDetailsIntent()
+            is BlockedByEcm -> restrictedMode.showRestrictedSettingsDetails()
             else -> onClick()
         }
     }
diff --git a/packages/SettingsLib/src/com/android/settingslib/RestrictedLockUtilsInternal.java b/packages/SettingsLib/src/com/android/settingslib/RestrictedLockUtilsInternal.java
index e77964c..4454b71 100644
--- a/packages/SettingsLib/src/com/android/settingslib/RestrictedLockUtilsInternal.java
+++ b/packages/SettingsLib/src/com/android/settingslib/RestrictedLockUtilsInternal.java
@@ -22,13 +22,16 @@
 
 import static com.android.settingslib.Utils.getColorAttrDefaultColor;
 
+import android.Manifest;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.UserIdInt;
 import android.app.AppGlobals;
+import android.app.AppOpsManager;
 import android.app.admin.DevicePolicyManager;
 import android.content.ComponentName;
 import android.content.Context;
+import android.content.Intent;
 import android.content.pm.IPackageManager;
 import android.content.pm.PackageManager;
 import android.content.pm.UserInfo;
@@ -39,10 +42,12 @@
 import android.os.UserHandle;
 import android.os.UserManager;
 import android.os.UserManager.EnforcingUser;
+import android.provider.Settings;
 import android.text.SpannableStringBuilder;
 import android.text.Spanned;
 import android.text.style.ForegroundColorSpan;
 import android.text.style.ImageSpan;
+import android.util.ArraySet;
 import android.util.Log;
 import android.view.MenuItem;
 import android.widget.TextView;
@@ -53,6 +58,7 @@
 import com.android.internal.widget.LockPatternUtils;
 
 import java.util.List;
+import java.util.Set;
 
 /**
  * Utility class to host methods usable in adding a restricted padlock icon and showing admin
@@ -62,6 +68,16 @@
 
     private static final String LOG_TAG = "RestrictedLockUtils";
     private static final boolean DEBUG = Log.isLoggable(LOG_TAG, Log.DEBUG);
+    private static final Set<String> ECM_KEYS = new ArraySet<>();
+
+    static {
+        if (android.security.Flags.extendEcmToAllSettings()) {
+            ECM_KEYS.add(AppOpsManager.OPSTR_SYSTEM_ALERT_WINDOW);
+            ECM_KEYS.add(AppOpsManager.OPSTR_GET_USAGE_STATS);
+            ECM_KEYS.add(AppOpsManager.OPSTR_LOADER_USAGE_STATS);
+            ECM_KEYS.add(Manifest.permission.BIND_DEVICE_ADMIN);
+        }
+    }
 
     /**
      * @return drawables for displaying with settings that are locked by a device admin.
@@ -81,6 +97,38 @@
     }
 
     /**
+     * Checks if a given permission requires additional confirmation for the given package
+     *
+     * @return An intent to show the user if additional confirmation is required, null otherwise
+     */
+    @Nullable
+    public static Intent checkIfRequiresEnhancedConfirmation(@NonNull Context context,
+                                                             @NonNull String restriction,
+                                                             int uid,
+                                                             @Nullable String packageName) {
+        // TODO(b/297372999): Replace with call to mainline module once ready
+
+        if (!ECM_KEYS.contains(restriction)) {
+            return null;
+        }
+
+        final AppOpsManager appOps = (AppOpsManager) context
+                .getSystemService(Context.APP_OPS_SERVICE);
+        final int mode = appOps.noteOpNoThrow(AppOpsManager.OP_ACCESS_RESTRICTED_SETTINGS,
+                uid, packageName, null, null);
+        final boolean ecmEnabled = context.getResources().getBoolean(
+                com.android.internal.R.bool.config_enhancedConfirmationModeEnabled);
+        if (ecmEnabled && mode != AppOpsManager.MODE_ALLOWED) {
+            final Intent intent = new Intent(Settings.ACTION_SHOW_RESTRICTED_SETTING_DIALOG);
+            intent.putExtra(Intent.EXTRA_PACKAGE_NAME, packageName);
+            intent.putExtra(Intent.EXTRA_UID, uid);
+            return intent;
+        }
+
+        return null;
+    }
+
+    /**
      * Checks if a restriction is enforced on a user and returns the enforced admin and
      * admin userId.
      *
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/core/instrumentation/SettingsJankMonitorTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/core/instrumentation/SettingsJankMonitorTest.java
index 25833b3..5aee8cd 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/core/instrumentation/SettingsJankMonitorTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/core/instrumentation/SettingsJankMonitorTest.java
@@ -16,7 +16,7 @@
 
 package com.android.settingslib.core.instrumentation;
 
-import static com.android.internal.jank.InteractionJankMonitor.CUJ_SETTINGS_TOGGLE;
+import static com.android.internal.jank.Cuj.CUJ_SETTINGS_TOGGLE;
 import static com.android.settingslib.core.instrumentation.SettingsJankMonitor.MONITORED_ANIMATION_DURATION_MS;
 
 import static com.google.common.truth.Truth.assertThat;
@@ -34,8 +34,8 @@
 import androidx.preference.SwitchPreference;
 import androidx.recyclerview.widget.RecyclerView;
 
+import com.android.internal.jank.Cuj.CujType;
 import com.android.internal.jank.InteractionJankMonitor;
-import com.android.internal.jank.InteractionJankMonitor.CujType;
 import com.android.settingslib.testutils.OverpoweredReflectionHelper;
 import com.android.settingslib.testutils.shadow.ShadowInteractionJankMonitor;
 
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java
index 7cec99d..8f459c6 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java
@@ -95,7 +95,9 @@
 
     static final int SETTINGS_VERSION_NEW_ENCODING = 121;
 
+    // LINT.IfChange
     public static final int MAX_LENGTH_PER_STRING = 32768;
+    // LINT.ThenChange(/services/core/java/com/android/server/audio/AudioDeviceInventory.java:settings_max_length_per_string)
     private static final long WRITE_SETTINGS_DELAY_MILLIS = 200;
     private static final long MAX_WRITE_SETTINGS_DELAY_MILLIS = 2000;
 
diff --git a/packages/SystemUI/Android.bp b/packages/SystemUI/Android.bp
index c2c5e00..7061e2c 100644
--- a/packages/SystemUI/Android.bp
+++ b/packages/SystemUI/Android.bp
@@ -281,6 +281,7 @@
         "com_android_systemui_flags_lib",
         "com_android_systemui_shared_flags_lib",
         "flag-junit-base",
+        "platform-parametric-runner-lib",
         "androidx.viewpager2_viewpager2",
         "androidx.legacy_legacy-support-v4",
         "androidx.recyclerview_recyclerview",
diff --git a/packages/SystemUI/aconfig/Android.bp b/packages/SystemUI/aconfig/Android.bp
index e842967..50ed7ab 100644
--- a/packages/SystemUI/aconfig/Android.bp
+++ b/packages/SystemUI/aconfig/Android.bp
@@ -23,6 +23,7 @@
     default_visibility: [
         "//visibility:override",
         "//frameworks/base/packages/SystemUI:__subpackages__",
+        "//platform_testing:__subpackages__"
     ],
 }
 
diff --git a/packages/SystemUI/aconfig/systemui.aconfig b/packages/SystemUI/aconfig/systemui.aconfig
index 2970aaa..a26b311 100644
--- a/packages/SystemUI/aconfig/systemui.aconfig
+++ b/packages/SystemUI/aconfig/systemui.aconfig
@@ -211,6 +211,13 @@
 }
 
 flag {
+  name: "enable_layout_tracing"
+  namespace: "systemui"
+  description: "Enables detailed traversal slices during measure and layout in perfetto traces"
+  bug: "315274804"
+}
+
+flag {
    name: "quick_settings_visual_haptics_longpress"
    namespace: "systemui"
    description: "Enable special visual and haptic effects for quick settings tiles with long-press actions"
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/DialogLaunchAnimator.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/DialogLaunchAnimator.kt
index 187d073..168039e 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/DialogLaunchAnimator.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/DialogLaunchAnimator.kt
@@ -33,8 +33,8 @@
 import android.view.WindowManager
 import android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS
 import com.android.app.animation.Interpolators
+import com.android.internal.jank.Cuj.CujType
 import com.android.internal.jank.InteractionJankMonitor
-import com.android.internal.jank.InteractionJankMonitor.CujType
 import com.android.systemui.util.maybeForceFullscreen
 import com.android.systemui.util.registerAnimationOnBackInvoked
 import kotlin.math.roundToInt
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/GhostedViewLaunchAnimatorController.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/GhostedViewLaunchAnimatorController.kt
index af35ea4..b738e2b 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/GhostedViewLaunchAnimatorController.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/GhostedViewLaunchAnimatorController.kt
@@ -33,6 +33,7 @@
 import android.view.ViewGroup
 import android.view.ViewGroupOverlay
 import android.widget.FrameLayout
+import com.android.internal.jank.Cuj.CujType
 import com.android.internal.jank.InteractionJankMonitor
 import java.util.LinkedList
 import kotlin.math.min
@@ -58,7 +59,7 @@
     /** The view that will be ghosted and from which the background will be extracted. */
     private val ghostedView: View,
 
-    /** The [InteractionJankMonitor.CujType] associated to this animation. */
+    /** The [CujType] associated to this animation. */
     private val cujType: Int? = null,
     private var interactionJankMonitor: InteractionJankMonitor =
         InteractionJankMonitor.getInstance(),
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerContent.kt b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerContent.kt
index 1a653c3..47f5663 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerContent.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerContent.kt
@@ -14,6 +14,8 @@
  * limitations under the License.
  */
 
+@file:OptIn(ExperimentalFoundationApi::class)
+
 package com.android.systemui.bouncer.ui.composable
 
 import android.app.AlertDialog
@@ -29,18 +31,18 @@
 import androidx.compose.foundation.gestures.detectTapGestures
 import androidx.compose.foundation.layout.Arrangement
 import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.BoxScope
 import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.PaddingValues
 import androidx.compose.foundation.layout.Row
 import androidx.compose.foundation.layout.Spacer
-import androidx.compose.foundation.layout.aspectRatio
 import androidx.compose.foundation.layout.fillMaxHeight
-import androidx.compose.foundation.layout.fillMaxSize
 import androidx.compose.foundation.layout.fillMaxWidth
 import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.imePadding
 import androidx.compose.foundation.layout.padding
 import androidx.compose.foundation.layout.size
 import androidx.compose.foundation.layout.width
-import androidx.compose.foundation.layout.widthIn
 import androidx.compose.foundation.shape.RoundedCornerShape
 import androidx.compose.material.icons.Icons
 import androidx.compose.material.icons.filled.KeyboardArrowDown
@@ -51,6 +53,7 @@
 import androidx.compose.material3.Icon
 import androidx.compose.material3.MaterialTheme
 import androidx.compose.material3.Text
+import androidx.compose.material3.windowsizeclass.WindowHeightSizeClass
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.collectAsState
 import androidx.compose.runtime.getValue
@@ -78,6 +81,7 @@
 import com.android.compose.animation.scene.SceneTransitionLayout
 import com.android.compose.animation.scene.transitions
 import com.android.compose.modifiers.thenIf
+import com.android.compose.windowsizeclass.LocalWindowSizeClass
 import com.android.systemui.bouncer.shared.model.BouncerActionButtonModel
 import com.android.systemui.bouncer.ui.helper.BouncerSceneLayout
 import com.android.systemui.bouncer.ui.viewmodel.AuthMethodBouncerViewModel
@@ -100,37 +104,41 @@
     dialogFactory: BouncerDialogFactory,
     modifier: Modifier = Modifier,
 ) {
-    val isFullScreenUserSwitcherEnabled = viewModel.isUserSwitcherVisible
     val isSideBySideSupported by viewModel.isSideBySideSupported.collectAsState()
     val layout = calculateLayout(isSideBySideSupported = isSideBySideSupported)
 
-    when (layout) {
-        BouncerSceneLayout.STANDARD ->
-            StandardLayout(
-                viewModel = viewModel,
-                dialogFactory = dialogFactory,
-                modifier = modifier,
-            )
-        BouncerSceneLayout.SIDE_BY_SIDE ->
-            SideBySideLayout(
-                viewModel = viewModel,
-                dialogFactory = dialogFactory,
-                isUserSwitcherVisible = isFullScreenUserSwitcherEnabled,
-                modifier = modifier,
-            )
-        BouncerSceneLayout.STACKED ->
-            StackedLayout(
-                viewModel = viewModel,
-                dialogFactory = dialogFactory,
-                isUserSwitcherVisible = isFullScreenUserSwitcherEnabled,
-                modifier = modifier,
-            )
-        BouncerSceneLayout.SPLIT ->
-            SplitLayout(
-                viewModel = viewModel,
-                dialogFactory = dialogFactory,
-                modifier = modifier,
-            )
+    Box(
+        // Allows the content within each of the layouts to react to the appearance and
+        // disappearance of the IME, which is also known as the software keyboard.
+        //
+        // Despite the keyboard only being part of the password bouncer, adding it at this level is
+        // both necessary to properly handle the keyboard in all layouts and harmless in cases when
+        // the keyboard isn't used (like the PIN or pattern auth methods).
+        modifier = modifier.imePadding(),
+    ) {
+        when (layout) {
+            BouncerSceneLayout.STANDARD_BOUNCER ->
+                StandardLayout(
+                    viewModel = viewModel,
+                )
+            BouncerSceneLayout.BESIDE_USER_SWITCHER ->
+                BesideUserSwitcherLayout(
+                    viewModel = viewModel,
+                )
+            BouncerSceneLayout.BELOW_USER_SWITCHER ->
+                BelowUserSwitcherLayout(
+                    viewModel = viewModel,
+                )
+            BouncerSceneLayout.SPLIT_BOUNCER ->
+                SplitLayout(
+                    viewModel = viewModel,
+                )
+        }
+
+        Dialog(
+            viewModel = viewModel,
+            dialogFactory = dialogFactory,
+        )
     }
 }
 
@@ -141,15 +149,333 @@
 @Composable
 private fun StandardLayout(
     viewModel: BouncerViewModel,
-    dialogFactory: BouncerDialogFactory,
     modifier: Modifier = Modifier,
-    layout: BouncerSceneLayout = BouncerSceneLayout.STANDARD,
-    outputOnly: Boolean = false,
+) {
+    val isHeightExpanded =
+        LocalWindowSizeClass.current.heightSizeClass == WindowHeightSizeClass.Expanded
+
+    FoldAware(
+        modifier =
+            modifier.padding(
+                top = 92.dp,
+                bottom = 48.dp,
+            ),
+        viewModel = viewModel,
+        aboveFold = {
+            Column(
+                horizontalAlignment = Alignment.CenterHorizontally,
+                modifier = Modifier.fillMaxWidth(),
+            ) {
+                StatusMessage(
+                    viewModel = viewModel,
+                    modifier = Modifier,
+                )
+
+                OutputArea(
+                    viewModel = viewModel,
+                    modifier = Modifier.padding(top = if (isHeightExpanded) 96.dp else 64.dp),
+                )
+            }
+        },
+        belowFold = {
+            Column(
+                horizontalAlignment = Alignment.CenterHorizontally,
+                modifier = Modifier.fillMaxWidth(),
+            ) {
+                Box(
+                    modifier = Modifier.weight(1f),
+                ) {
+                    InputArea(
+                        viewModel = viewModel,
+                        pinButtonRowVerticalSpacing = 12.dp,
+                        centerPatternDotsVertically = false,
+                        modifier = Modifier.align(Alignment.BottomCenter),
+                    )
+                }
+
+                ActionArea(
+                    viewModel = viewModel,
+                    modifier = Modifier.padding(top = 48.dp),
+                )
+            }
+        },
+    )
+}
+
+/**
+ * Renders the bouncer UI in split mode, with half on one side and half on the other side, swappable
+ * by double-tapping on the side.
+ */
+@Composable
+private fun SplitLayout(
+    viewModel: BouncerViewModel,
+    modifier: Modifier = Modifier,
+) {
+    val authMethod by viewModel.authMethodViewModel.collectAsState()
+
+    Row(
+        modifier =
+            modifier
+                .fillMaxHeight()
+                .padding(
+                    horizontal = 24.dp,
+                    vertical = if (authMethod is PasswordBouncerViewModel) 24.dp else 48.dp,
+                ),
+    ) {
+        // Left side (in left-to-right locales).
+        Box(
+            modifier = Modifier.fillMaxHeight().weight(1f),
+        ) {
+            when (authMethod) {
+                is PinBouncerViewModel -> {
+                    StatusMessage(
+                        viewModel = viewModel,
+                        modifier = Modifier.align(Alignment.TopCenter),
+                    )
+
+                    OutputArea(viewModel = viewModel, modifier = Modifier.align(Alignment.Center))
+
+                    ActionArea(
+                        viewModel = viewModel,
+                        modifier = Modifier.align(Alignment.BottomCenter).padding(top = 48.dp),
+                    )
+                }
+                is PatternBouncerViewModel -> {
+                    StatusMessage(
+                        viewModel = viewModel,
+                        modifier = Modifier.align(Alignment.TopCenter),
+                    )
+
+                    ActionArea(
+                        viewModel = viewModel,
+                        modifier = Modifier.align(Alignment.BottomCenter).padding(vertical = 48.dp),
+                    )
+                }
+                is PasswordBouncerViewModel -> {
+                    ActionArea(
+                        viewModel = viewModel,
+                        modifier = Modifier.align(Alignment.BottomCenter),
+                    )
+                }
+                else -> Unit
+            }
+        }
+
+        // Right side (in right-to-left locales).
+        Box(
+            modifier = Modifier.fillMaxHeight().weight(1f),
+        ) {
+            when (authMethod) {
+                is PinBouncerViewModel,
+                is PatternBouncerViewModel -> {
+                    InputArea(
+                        viewModel = viewModel,
+                        pinButtonRowVerticalSpacing = 8.dp,
+                        centerPatternDotsVertically = true,
+                        modifier = Modifier.align(Alignment.Center),
+                    )
+                }
+                is PasswordBouncerViewModel -> {
+                    Column(
+                        horizontalAlignment = Alignment.CenterHorizontally,
+                        modifier = Modifier.fillMaxWidth().align(Alignment.Center),
+                    ) {
+                        StatusMessage(
+                            viewModel = viewModel,
+                        )
+
+                        OutputArea(viewModel = viewModel, modifier = Modifier.padding(top = 24.dp))
+                    }
+                }
+                else -> Unit
+            }
+        }
+    }
+}
+
+/**
+ * Arranges the bouncer contents and user switcher contents side-by-side, supporting a double tap
+ * anywhere on the background to flip their positions.
+ */
+@Composable
+private fun BesideUserSwitcherLayout(
+    viewModel: BouncerViewModel,
+    modifier: Modifier = Modifier,
+) {
+    val layoutDirection = LocalLayoutDirection.current
+    val isLeftToRight = layoutDirection == LayoutDirection.Ltr
+    val (isSwapped, setSwapped) = rememberSaveable(isLeftToRight) { mutableStateOf(!isLeftToRight) }
+    val isHeightExpanded =
+        LocalWindowSizeClass.current.heightSizeClass == WindowHeightSizeClass.Expanded
+    val authMethod by viewModel.authMethodViewModel.collectAsState()
+
+    Row(
+        modifier =
+            modifier.pointerInput(Unit) {
+                detectTapGestures(
+                    onDoubleTap = { offset ->
+                        // Depending on where the user double tapped, switch the elements such that
+                        // the endContent is closer to the side that was double tapped.
+                        setSwapped(offset.x < size.width / 2)
+                    }
+                )
+            },
+    ) {
+        val animatedOffset by
+            animateFloatAsState(
+                targetValue =
+                    if (!isSwapped) {
+                        // A non-swapped element has its natural placement so it's not offset.
+                        0f
+                    } else if (isLeftToRight) {
+                        // A swapped element has its elements offset horizontally. In the case of
+                        // LTR locales, this means pushing the element to the right, hence the
+                        // positive number.
+                        1f
+                    } else {
+                        // A swapped element has its elements offset horizontally. In the case of
+                        // RTL locales, this means pushing the element to the left, hence the
+                        // negative number.
+                        -1f
+                    },
+                label = "offset",
+            )
+
+        fun Modifier.swappable(inversed: Boolean = false): Modifier {
+            return graphicsLayer {
+                translationX =
+                    size.width *
+                        animatedOffset *
+                        if (inversed) {
+                            // A negative sign is used to make sure this is offset in the direction
+                            // that's opposite to the direction that the user switcher is pushed in.
+                            -1
+                        } else {
+                            1
+                        }
+                alpha = animatedAlpha(animatedOffset)
+            }
+        }
+
+        UserSwitcher(
+            viewModel = viewModel,
+            modifier = Modifier.weight(1f).align(Alignment.CenterVertically).swappable(),
+        )
+
+        FoldAware(
+            modifier =
+                Modifier.weight(1f)
+                    .padding(
+                        if (isHeightExpanded) {
+                            PaddingValues(vertical = 128.dp)
+                        } else {
+                            PaddingValues(top = 94.dp, bottom = 48.dp)
+                        }
+                    )
+                    .swappable(inversed = true),
+            viewModel = viewModel,
+            aboveFold = {
+                Column(
+                    horizontalAlignment = Alignment.CenterHorizontally,
+                    modifier = Modifier.fillMaxWidth()
+                ) {
+                    StatusMessage(
+                        viewModel = viewModel,
+                    )
+
+                    OutputArea(viewModel = viewModel, modifier = Modifier.padding(top = 24.dp))
+                }
+            },
+            belowFold = {
+                Column(
+                    horizontalAlignment = Alignment.CenterHorizontally,
+                    modifier = Modifier.fillMaxWidth(),
+                ) {
+                    val isOutputAreaVisible = authMethod !is PatternBouncerViewModel
+                    // If there is an output area and the window is not tall enough, spacing needs
+                    // to be added between the input and the output areas (otherwise the two get
+                    // very squished together).
+                    val addSpacingBetweenOutputAndInput = isOutputAreaVisible && !isHeightExpanded
+
+                    Box(
+                        modifier =
+                            Modifier.weight(1f)
+                                .padding(top = (if (addSpacingBetweenOutputAndInput) 24 else 0).dp),
+                    ) {
+                        InputArea(
+                            viewModel = viewModel,
+                            pinButtonRowVerticalSpacing = 12.dp,
+                            centerPatternDotsVertically = true,
+                            modifier = Modifier.align(Alignment.BottomCenter),
+                        )
+                    }
+
+                    ActionArea(
+                        viewModel = viewModel,
+                        modifier = Modifier.padding(top = 48.dp),
+                    )
+                }
+            },
+        )
+    }
+}
+
+/** Arranges the bouncer contents and user switcher contents one on top of the other, vertically. */
+@Composable
+private fun BelowUserSwitcherLayout(
+    viewModel: BouncerViewModel,
+    modifier: Modifier = Modifier,
+) {
+    Column(
+        modifier =
+            modifier.padding(
+                vertical = 128.dp,
+            )
+    ) {
+        UserSwitcher(
+            viewModel = viewModel,
+            modifier = Modifier.fillMaxWidth(),
+        )
+
+        Spacer(Modifier.weight(1f))
+
+        Box(modifier = Modifier.fillMaxWidth()) {
+            Column(
+                horizontalAlignment = Alignment.CenterHorizontally,
+                modifier = Modifier.fillMaxWidth(),
+            ) {
+                StatusMessage(
+                    viewModel = viewModel,
+                )
+
+                OutputArea(viewModel = viewModel, modifier = Modifier.padding(top = 24.dp))
+
+                InputArea(
+                    viewModel = viewModel,
+                    pinButtonRowVerticalSpacing = 12.dp,
+                    centerPatternDotsVertically = true,
+                    modifier = Modifier.padding(top = 128.dp),
+                )
+
+                ActionArea(
+                    viewModel = viewModel,
+                    modifier = Modifier.padding(top = 48.dp),
+                )
+            }
+        }
+    }
+}
+
+@Composable
+private fun FoldAware(
+    viewModel: BouncerViewModel,
+    aboveFold: @Composable BoxScope.() -> Unit,
+    belowFold: @Composable BoxScope.() -> Unit,
+    modifier: Modifier = Modifier,
 ) {
     val foldPosture: FoldPosture by foldPosture()
     val isSplitAroundTheFoldRequired by viewModel.isFoldSplitRequired.collectAsState()
-    val isSplitAroundTheFold =
-        foldPosture == FoldPosture.Tabletop && !outputOnly && isSplitAroundTheFoldRequired
+    val isSplitAroundTheFold = foldPosture == FoldPosture.Tabletop && isSplitAroundTheFoldRequired
     val currentSceneKey =
         if (isSplitAroundTheFold) SceneKeys.SplitSceneKey else SceneKeys.ContiguousSceneKey
 
@@ -160,115 +486,57 @@
         modifier = modifier,
     ) {
         scene(SceneKeys.ContiguousSceneKey) {
-            FoldSplittable(
-                viewModel = viewModel,
-                dialogFactory = dialogFactory,
-                layout = layout,
-                outputOnly = outputOnly,
+            FoldableScene(
+                aboveFold = aboveFold,
+                belowFold = belowFold,
                 isSplit = false,
             )
         }
 
         scene(SceneKeys.SplitSceneKey) {
-            FoldSplittable(
-                viewModel = viewModel,
-                dialogFactory = dialogFactory,
-                layout = layout,
-                outputOnly = outputOnly,
+            FoldableScene(
+                aboveFold = aboveFold,
+                belowFold = belowFold,
                 isSplit = true,
             )
         }
     }
 }
 
-/**
- * Renders the "standard" layout of the bouncer, where the bouncer is rendered on its own (no user
- * switcher UI) and laid out vertically, centered horizontally.
- *
- * If [isSplit] is `true`, the top and bottom parts of the bouncer are split such that they don't
- * render across the location of the fold hardware when the device is fully or part-way unfolded
- * with the fold hinge in a horizontal position.
- *
- * If [outputOnly] is `true`, only the "output" part of the UI is shown (where the entered PIN
- * "shapes" appear), if `false`, the entire UI is shown, including the area where the user can enter
- * their PIN or pattern.
- */
 @Composable
-private fun SceneScope.FoldSplittable(
-    viewModel: BouncerViewModel,
-    dialogFactory: BouncerDialogFactory,
-    layout: BouncerSceneLayout,
-    outputOnly: Boolean,
+private fun SceneScope.FoldableScene(
+    aboveFold: @Composable BoxScope.() -> Unit,
+    belowFold: @Composable BoxScope.() -> Unit,
     isSplit: Boolean,
     modifier: Modifier = Modifier,
 ) {
-    val message: BouncerViewModel.MessageViewModel by viewModel.message.collectAsState()
-    val dialogMessage: String? by viewModel.throttlingDialogMessage.collectAsState()
-    var dialog: Dialog? by remember { mutableStateOf(null) }
-    val actionButton: BouncerActionButtonModel? by viewModel.actionButton.collectAsState()
     val splitRatio =
         LocalContext.current.resources.getFloat(
             R.dimen.motion_layout_half_fold_bouncer_height_ratio
         )
 
-    Column(modifier = modifier.padding(horizontal = 32.dp)) {
+    Column(
+        modifier = modifier.fillMaxHeight(),
+    ) {
         // Content above the fold, when split on a foldable device in a "table top" posture:
         Box(
             modifier =
                 Modifier.element(SceneElements.AboveFold)
-                    .fillMaxWidth()
                     .then(
                         if (isSplit) {
                             Modifier.weight(splitRatio)
-                        } else if (outputOnly) {
-                            Modifier.fillMaxHeight()
                         } else {
                             Modifier
                         }
                     ),
         ) {
-            Column(
-                horizontalAlignment = Alignment.CenterHorizontally,
-                modifier = Modifier.fillMaxWidth().padding(top = layout.topPadding),
-            ) {
-                Crossfade(
-                    targetState = message,
-                    label = "Bouncer message",
-                    animationSpec = if (message.isUpdateAnimated) tween() else snap(),
-                ) { message ->
-                    Text(
-                        text = message.text,
-                        color = MaterialTheme.colorScheme.onSurface,
-                        style = MaterialTheme.typography.bodyLarge,
-                    )
-                }
-
-                if (!outputOnly) {
-                    Spacer(Modifier.height(layout.spacingBetweenMessageAndEnteredInput))
-
-                    UserInputArea(
-                        viewModel = viewModel,
-                        visibility = UserInputAreaVisibility.OUTPUT_ONLY,
-                        layout = layout,
-                    )
-                }
-            }
-
-            if (outputOnly) {
-                UserInputArea(
-                    viewModel = viewModel,
-                    visibility = UserInputAreaVisibility.OUTPUT_ONLY,
-                    layout = layout,
-                    modifier = Modifier.align(Alignment.Center),
-                )
-            }
+            aboveFold()
         }
 
         // Content below the fold, when split on a foldable device in a "table top" posture:
         Box(
             modifier =
                 Modifier.element(SceneElements.BelowFold)
-                    .fillMaxWidth()
                     .weight(
                         if (isSplit) {
                             1 - splitRatio
@@ -277,73 +545,40 @@
                         }
                     ),
         ) {
-            Column(
-                horizontalAlignment = Alignment.CenterHorizontally,
-                modifier = Modifier.fillMaxSize()
-            ) {
-                if (!outputOnly) {
-                    Box(Modifier.weight(1f)) {
-                        UserInputArea(
-                            viewModel = viewModel,
-                            visibility = UserInputAreaVisibility.INPUT_ONLY,
-                            layout = layout,
-                            modifier = Modifier.align(Alignment.BottomCenter),
-                        )
-                    }
-                }
-
-                Spacer(Modifier.height(48.dp))
-
-                val actionButtonModifier = Modifier.height(56.dp)
-
-                actionButton.let { actionButtonViewModel ->
-                    if (actionButtonViewModel != null) {
-                        BouncerActionButton(
-                            viewModel = actionButtonViewModel,
-                            modifier = actionButtonModifier,
-                        )
-                    } else {
-                        Spacer(modifier = actionButtonModifier)
-                    }
-                }
-
-                Spacer(Modifier.height(layout.bottomPadding))
-            }
-        }
-
-        if (dialogMessage != null) {
-            if (dialog == null) {
-                dialog =
-                    dialogFactory().apply {
-                        setMessage(dialogMessage)
-                        setButton(
-                            DialogInterface.BUTTON_NEUTRAL,
-                            context.getString(R.string.ok),
-                        ) { _, _ ->
-                            viewModel.onThrottlingDialogDismissed()
-                        }
-                        setCancelable(false)
-                        setCanceledOnTouchOutside(false)
-                        show()
-                    }
-            }
-        } else {
-            dialog?.dismiss()
-            dialog = null
+            belowFold()
         }
     }
 }
 
+@Composable
+private fun StatusMessage(
+    viewModel: BouncerViewModel,
+    modifier: Modifier = Modifier,
+) {
+    val message: BouncerViewModel.MessageViewModel by viewModel.message.collectAsState()
+
+    Crossfade(
+        targetState = message,
+        label = "Bouncer message",
+        animationSpec = if (message.isUpdateAnimated) tween() else snap(),
+        modifier = modifier,
+    ) {
+        Text(
+            text = it.text,
+            color = MaterialTheme.colorScheme.onSurface,
+            style = MaterialTheme.typography.bodyLarge,
+        )
+    }
+}
+
 /**
- * Renders the user input area, where the user interacts with the UI to enter their credentials.
+ * Renders the user output area, where the user sees what they entered.
  *
- * For example, this can be the pattern input area, the password text box, or pin pad.
+ * For example, this can be the PIN shapes or password text field.
  */
 @Composable
-private fun UserInputArea(
+private fun OutputArea(
     viewModel: BouncerViewModel,
-    visibility: UserInputAreaVisibility,
-    layout: BouncerSceneLayout,
     modifier: Modifier = Modifier,
 ) {
     val authMethodViewModel: AuthMethodBouncerViewModel? by
@@ -351,66 +586,115 @@
 
     when (val nonNullViewModel = authMethodViewModel) {
         is PinBouncerViewModel ->
-            when (visibility) {
-                UserInputAreaVisibility.OUTPUT_ONLY ->
-                    PinInputDisplay(
-                        viewModel = nonNullViewModel,
-                        modifier = modifier,
-                    )
-                UserInputAreaVisibility.INPUT_ONLY ->
-                    PinPad(
-                        viewModel = nonNullViewModel,
-                        layout = layout,
-                        modifier = modifier,
-                    )
-            }
+            PinInputDisplay(
+                viewModel = nonNullViewModel,
+                modifier = modifier,
+            )
         is PasswordBouncerViewModel ->
-            if (visibility == UserInputAreaVisibility.INPUT_ONLY) {
-                PasswordBouncer(
-                    viewModel = nonNullViewModel,
-                    modifier = modifier,
-                )
-            }
-        is PatternBouncerViewModel ->
-            if (visibility == UserInputAreaVisibility.INPUT_ONLY) {
-                PatternBouncer(
-                    viewModel = nonNullViewModel,
-                    layout = layout,
-                    modifier = modifier.aspectRatio(1f, matchHeightConstraintsFirst = false),
-                )
-            }
+            PasswordBouncer(
+                viewModel = nonNullViewModel,
+                modifier = modifier,
+            )
         else -> Unit
     }
 }
 
 /**
- * Renders the action button on the bouncer, which triggers either Return to Call or Emergency Call.
+ * Renders the user input area, where the user enters their credentials.
+ *
+ * For example, this can be the pattern input area or the PIN pad.
  */
-@OptIn(ExperimentalFoundationApi::class)
 @Composable
-private fun BouncerActionButton(
-    viewModel: BouncerActionButtonModel,
+private fun InputArea(
+    viewModel: BouncerViewModel,
+    pinButtonRowVerticalSpacing: Dp,
+    centerPatternDotsVertically: Boolean,
     modifier: Modifier = Modifier,
 ) {
-    Button(
-        onClick = viewModel.onClick,
-        modifier =
-            modifier.thenIf(viewModel.onLongClick != null) {
-                Modifier.combinedClickable(
-                    onClick = viewModel.onClick,
-                    onLongClick = viewModel.onLongClick,
+    val authMethodViewModel: AuthMethodBouncerViewModel? by
+        viewModel.authMethodViewModel.collectAsState()
+
+    when (val nonNullViewModel = authMethodViewModel) {
+        is PinBouncerViewModel -> {
+            PinPad(
+                viewModel = nonNullViewModel,
+                verticalSpacing = pinButtonRowVerticalSpacing,
+                modifier = modifier,
+            )
+        }
+        is PatternBouncerViewModel -> {
+            PatternBouncer(
+                viewModel = nonNullViewModel,
+                centerDotsVertically = centerPatternDotsVertically,
+                modifier = modifier,
+            )
+        }
+        else -> Unit
+    }
+}
+
+@Composable
+private fun ActionArea(
+    viewModel: BouncerViewModel,
+    modifier: Modifier = Modifier,
+) {
+    val actionButton: BouncerActionButtonModel? by viewModel.actionButton.collectAsState()
+
+    actionButton?.let { actionButtonViewModel ->
+        Box(
+            modifier = modifier,
+        ) {
+            Button(
+                onClick = actionButtonViewModel.onClick,
+                modifier =
+                    Modifier.height(56.dp).thenIf(actionButtonViewModel.onLongClick != null) {
+                        Modifier.combinedClickable(
+                            onClick = actionButtonViewModel.onClick,
+                            onLongClick = actionButtonViewModel.onLongClick,
+                        )
+                    },
+                colors =
+                    ButtonDefaults.buttonColors(
+                        containerColor = MaterialTheme.colorScheme.tertiaryContainer,
+                        contentColor = MaterialTheme.colorScheme.onTertiaryContainer,
+                    ),
+            ) {
+                Text(
+                    text = actionButtonViewModel.label,
+                    style = MaterialTheme.typography.bodyMedium,
                 )
-            },
-        colors =
-            ButtonDefaults.buttonColors(
-                containerColor = MaterialTheme.colorScheme.tertiaryContainer,
-                contentColor = MaterialTheme.colorScheme.onTertiaryContainer,
-            ),
-    ) {
-        Text(
-            text = viewModel.label,
-            style = MaterialTheme.typography.bodyMedium,
-        )
+            }
+        }
+    }
+}
+
+@Composable
+private fun Dialog(
+    viewModel: BouncerViewModel,
+    dialogFactory: BouncerDialogFactory,
+) {
+    val dialogMessage: String? by viewModel.dialogMessage.collectAsState()
+    var dialog: Dialog? by remember { mutableStateOf(null) }
+
+    if (dialogMessage != null) {
+        if (dialog == null) {
+            dialog =
+                dialogFactory().apply {
+                    setMessage(dialogMessage)
+                    setButton(
+                        DialogInterface.BUTTON_NEUTRAL,
+                        context.getString(R.string.ok),
+                    ) { _, _ ->
+                        viewModel.onDialogDismissed()
+                    }
+                    setCancelable(false)
+                    setCanceledOnTouchOutside(false)
+                    show()
+                }
+        }
+    } else {
+        dialog?.dismiss()
+        dialog = null
     }
 }
 
@@ -420,6 +704,14 @@
     viewModel: BouncerViewModel,
     modifier: Modifier = Modifier,
 ) {
+    if (!viewModel.isUserSwitcherVisible) {
+        // Take up the same space as the user switcher normally would, but with nothing inside it.
+        Box(
+            modifier = modifier,
+        )
+        return
+    }
+
     val selectedUserImage by viewModel.selectedUserImage.collectAsState(null)
     val dropdownItems by viewModel.userSwitcherDropdown.collectAsState(emptyList())
 
@@ -539,195 +831,10 @@
     }
 }
 
-/**
- * Renders the bouncer UI in split mode, with half on one side and half on the other side, swappable
- * by double-tapping on the side.
- */
-@Composable
-private fun SplitLayout(
-    viewModel: BouncerViewModel,
-    dialogFactory: BouncerDialogFactory,
-    modifier: Modifier = Modifier,
-) {
-    SwappableLayout(
-        startContent = { startContentModifier ->
-            StandardLayout(
-                viewModel = viewModel,
-                dialogFactory = dialogFactory,
-                layout = BouncerSceneLayout.SPLIT,
-                outputOnly = true,
-                modifier = startContentModifier,
-            )
-        },
-        endContent = { endContentModifier ->
-            UserInputArea(
-                viewModel = viewModel,
-                visibility = UserInputAreaVisibility.INPUT_ONLY,
-                layout = BouncerSceneLayout.SPLIT,
-                modifier = endContentModifier,
-            )
-        },
-        layout = BouncerSceneLayout.SPLIT,
-        modifier = modifier,
-    )
-}
-
-/**
- * Arranges the given two contents side-by-side, supporting a double tap anywhere on the background
- * to flip their positions.
- */
-@Composable
-private fun SwappableLayout(
-    startContent: @Composable (Modifier) -> Unit,
-    endContent: @Composable (Modifier) -> Unit,
-    layout: BouncerSceneLayout,
-    modifier: Modifier = Modifier,
-) {
-    val layoutDirection = LocalLayoutDirection.current
-    val isLeftToRight = layoutDirection == LayoutDirection.Ltr
-    val (isSwapped, setSwapped) = rememberSaveable(isLeftToRight) { mutableStateOf(!isLeftToRight) }
-
-    Row(
-        modifier =
-            modifier.pointerInput(Unit) {
-                detectTapGestures(
-                    onDoubleTap = { offset ->
-                        // Depending on where the user double tapped, switch the elements such that
-                        // the endContent is closer to the side that was double tapped.
-                        setSwapped(offset.x < size.width / 2)
-                    }
-                )
-            },
-    ) {
-        val animatedOffset by
-            animateFloatAsState(
-                targetValue =
-                    if (!isSwapped) {
-                        // When startContent is first, both elements have their natural placement so
-                        // they are not offset in any way.
-                        0f
-                    } else if (isLeftToRight) {
-                        // Since startContent is not first, the elements have to be swapped
-                        // horizontally. In the case of LTR locales, this means pushing startContent
-                        // to the right, hence the positive number.
-                        1f
-                    } else {
-                        // Since startContent is not first, the elements have to be swapped
-                        // horizontally. In the case of RTL locales, this means pushing startContent
-                        // to the left, hence the negative number.
-                        -1f
-                    },
-                label = "offset",
-            )
-
-        startContent(
-            Modifier.fillMaxHeight().weight(1f).graphicsLayer {
-                translationX = size.width * animatedOffset
-                alpha = animatedAlpha(animatedOffset)
-            }
-        )
-
-        Box(
-            modifier =
-                Modifier.fillMaxHeight().weight(1f).graphicsLayer {
-                    // A negative sign is used to make sure this is offset in the direction that's
-                    // opposite of the direction that the user switcher is pushed in.
-                    translationX = -size.width * animatedOffset
-                    alpha = animatedAlpha(animatedOffset)
-                }
-        ) {
-            endContent(Modifier.align(layout.swappableEndContentAlignment).widthIn(max = 400.dp))
-        }
-    }
-}
-
-/**
- * Arranges the bouncer contents and user switcher contents side-by-side, supporting a double tap
- * anywhere on the background to flip their positions.
- *
- * In situations when [isUserSwitcherVisible] is `false`, one of two things may happen: either the
- * UI for the bouncer will be shown on its own, taking up one side, with the other side just being
- * empty space or, if that kind of "stand-alone side-by-side" isn't supported, the standard
- * rendering of the bouncer will be used instead of the side-by-side layout.
- */
-@Composable
-private fun SideBySideLayout(
-    viewModel: BouncerViewModel,
-    dialogFactory: BouncerDialogFactory,
-    isUserSwitcherVisible: Boolean,
-    modifier: Modifier = Modifier,
-) {
-    SwappableLayout(
-        startContent = { startContentModifier ->
-            if (isUserSwitcherVisible) {
-                UserSwitcher(
-                    viewModel = viewModel,
-                    modifier = startContentModifier,
-                )
-            } else {
-                Box(
-                    modifier = startContentModifier,
-                )
-            }
-        },
-        endContent = { endContentModifier ->
-            StandardLayout(
-                viewModel = viewModel,
-                dialogFactory = dialogFactory,
-                layout = BouncerSceneLayout.SIDE_BY_SIDE,
-                modifier = endContentModifier,
-            )
-        },
-        layout = BouncerSceneLayout.SIDE_BY_SIDE,
-        modifier = modifier,
-    )
-}
-
-/** Arranges the bouncer contents and user switcher contents one on top of the other, vertically. */
-@Composable
-private fun StackedLayout(
-    viewModel: BouncerViewModel,
-    dialogFactory: BouncerDialogFactory,
-    isUserSwitcherVisible: Boolean,
-    modifier: Modifier = Modifier,
-) {
-    Column(
-        modifier = modifier,
-    ) {
-        if (isUserSwitcherVisible) {
-            UserSwitcher(
-                viewModel = viewModel,
-                modifier = Modifier.fillMaxWidth().weight(1f),
-            )
-        }
-
-        StandardLayout(
-            viewModel = viewModel,
-            dialogFactory = dialogFactory,
-            layout = BouncerSceneLayout.STACKED,
-            modifier = Modifier.fillMaxWidth().weight(1f),
-        )
-    }
-}
-
 interface BouncerDialogFactory {
     operator fun invoke(): AlertDialog
 }
 
-/** Enumerates all supported user-input area visibilities. */
-private enum class UserInputAreaVisibility {
-    /**
-     * Only the area where the user enters the input is shown; the area where the input is reflected
-     * back to the user is not shown.
-     */
-    INPUT_ONLY,
-    /**
-     * Only the area where the input is reflected back to the user is shown; the area where the
-     * input is entered by the user is not shown.
-     */
-    OUTPUT_ONLY,
-}
-
 /**
  * Calculates an alpha for the user switcher and bouncer such that it's at `1` when the offset of
  * the two reaches a stopping point but `0` in the middle of the transition.
@@ -774,48 +881,3 @@
 private val SceneTransitions = transitions {
     from(SceneKeys.ContiguousSceneKey, to = SceneKeys.SplitSceneKey) { spec = tween() }
 }
-
-/** Whether a more compact size should be used for various spacing dimensions. */
-internal val BouncerSceneLayout.isUseCompactSize: Boolean
-    get() =
-        when (this) {
-            BouncerSceneLayout.SIDE_BY_SIDE -> true
-            BouncerSceneLayout.SPLIT -> true
-            else -> false
-        }
-
-/** Amount of space to place between the message and the entered input UI elements, in dips. */
-private val BouncerSceneLayout.spacingBetweenMessageAndEnteredInput: Dp
-    get() =
-        when {
-            this == BouncerSceneLayout.STACKED -> 24.dp
-            isUseCompactSize -> 96.dp
-            else -> 128.dp
-        }
-
-/** Amount of space to place above the topmost UI element, in dips. */
-private val BouncerSceneLayout.topPadding: Dp
-    get() =
-        if (this == BouncerSceneLayout.SPLIT) {
-            40.dp
-        } else {
-            92.dp
-        }
-
-/** Amount of space to place below the bottommost UI element, in dips. */
-private val BouncerSceneLayout.bottomPadding: Dp
-    get() =
-        if (this == BouncerSceneLayout.SPLIT) {
-            40.dp
-        } else {
-            48.dp
-        }
-
-/** The in-a-box alignment for the content on the "end" side of a swappable layout. */
-private val BouncerSceneLayout.swappableEndContentAlignment: Alignment
-    get() =
-        if (this == BouncerSceneLayout.SPLIT) {
-            Alignment.Center
-        } else {
-            Alignment.BottomCenter
-        }
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerSceneLayout.kt b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerSceneLayout.kt
index 08b7559..1c3d93c 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerSceneLayout.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerSceneLayout.kt
@@ -26,8 +26,8 @@
 
 /**
  * Returns the [BouncerSceneLayout] that should be used by the bouncer scene. If
- * [isSideBySideSupported] is `false`, then [BouncerSceneLayout.SIDE_BY_SIDE] is replaced by
- * [BouncerSceneLayout.STANDARD].
+ * [isSideBySideSupported] is `false`, then [BouncerSceneLayout.BESIDE_USER_SWITCHER] is replaced by
+ * [BouncerSceneLayout.STANDARD_BOUNCER].
  */
 @Composable
 fun calculateLayout(
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PasswordBouncer.kt b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PasswordBouncer.kt
index 2799959..0960811 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PasswordBouncer.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PasswordBouncer.kt
@@ -17,7 +17,6 @@
 package com.android.systemui.bouncer.ui.composable
 
 import android.view.ViewTreeObserver
-import androidx.compose.foundation.layout.Column
 import androidx.compose.foundation.text.KeyboardActions
 import androidx.compose.foundation.text.KeyboardOptions
 import androidx.compose.material3.LocalTextStyle
@@ -31,7 +30,6 @@
 import androidx.compose.runtime.getValue
 import androidx.compose.runtime.produceState
 import androidx.compose.runtime.remember
-import androidx.compose.ui.Alignment
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.draw.drawBehind
 import androidx.compose.ui.focus.FocusRequester
@@ -81,42 +79,38 @@
         }
     }
 
-    Column(
-        horizontalAlignment = Alignment.CenterHorizontally,
-        modifier = modifier,
-    ) {
-        val color = MaterialTheme.colorScheme.onSurfaceVariant
-        val lineWidthPx = with(LocalDensity.current) { 2.dp.toPx() }
+    val color = MaterialTheme.colorScheme.onSurfaceVariant
+    val lineWidthPx = with(LocalDensity.current) { 2.dp.toPx() }
 
-        TextField(
-            value = password,
-            onValueChange = viewModel::onPasswordInputChanged,
-            enabled = isInputEnabled,
-            visualTransformation = PasswordVisualTransformation(),
-            singleLine = true,
-            textStyle = LocalTextStyle.current.copy(textAlign = TextAlign.Center),
-            keyboardOptions =
-                KeyboardOptions(
-                    keyboardType = KeyboardType.Password,
-                    imeAction = ImeAction.Done,
-                ),
-            keyboardActions =
-                KeyboardActions(
-                    onDone = { viewModel.onAuthenticateKeyPressed() },
-                ),
-            modifier =
-                Modifier.focusRequester(focusRequester)
-                    .onFocusChanged { viewModel.onTextFieldFocusChanged(it.isFocused) }
-                    .drawBehind {
-                        drawLine(
-                            color = color,
-                            start = Offset(x = 0f, y = size.height - lineWidthPx),
-                            end = Offset(size.width, y = size.height - lineWidthPx),
-                            strokeWidth = lineWidthPx,
-                        )
-                    },
-        )
-    }
+    TextField(
+        value = password,
+        onValueChange = viewModel::onPasswordInputChanged,
+        enabled = isInputEnabled,
+        visualTransformation = PasswordVisualTransformation(),
+        singleLine = true,
+        textStyle = LocalTextStyle.current.copy(textAlign = TextAlign.Center),
+        keyboardOptions =
+            KeyboardOptions(
+                keyboardType = KeyboardType.Password,
+                imeAction = ImeAction.Done,
+            ),
+        keyboardActions =
+            KeyboardActions(
+                onDone = { viewModel.onAuthenticateKeyPressed() },
+            ),
+        modifier =
+            modifier
+                .focusRequester(focusRequester)
+                .onFocusChanged { viewModel.onTextFieldFocusChanged(it.isFocused) }
+                .drawBehind {
+                    drawLine(
+                        color = color,
+                        start = Offset(x = 0f, y = size.height - lineWidthPx),
+                        end = Offset(size.width, y = size.height - lineWidthPx),
+                        strokeWidth = lineWidthPx,
+                    )
+                },
+    )
 }
 
 /** Returns a [State] with `true` when the IME/keyboard is visible and `false` when it's not. */
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PatternBouncer.kt b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PatternBouncer.kt
index a4b1955..0a5f5d2 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PatternBouncer.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PatternBouncer.kt
@@ -24,6 +24,8 @@
 import androidx.compose.foundation.gestures.awaitEachGesture
 import androidx.compose.foundation.gestures.awaitFirstDown
 import androidx.compose.foundation.gestures.detectDragGestures
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.width
 import androidx.compose.material3.MaterialTheme
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.DisposableEffect
@@ -48,7 +50,6 @@
 import com.android.compose.animation.Easings
 import com.android.compose.modifiers.thenIf
 import com.android.internal.R
-import com.android.systemui.bouncer.ui.helper.BouncerSceneLayout
 import com.android.systemui.bouncer.ui.viewmodel.PatternBouncerViewModel
 import com.android.systemui.bouncer.ui.viewmodel.PatternDotViewModel
 import kotlin.math.min
@@ -61,11 +62,14 @@
  * UI for the input part of a pattern-requiring version of the bouncer.
  *
  * The user can press, hold, and drag their pointer to select dots along a grid of dots.
+ *
+ * If [centerDotsVertically] is `true`, the dots should be centered along the axis of interest; if
+ * `false`, the dots will be pushed towards the end/bottom of the axis.
  */
 @Composable
 internal fun PatternBouncer(
     viewModel: PatternBouncerViewModel,
-    layout: BouncerSceneLayout,
+    centerDotsVertically: Boolean,
     modifier: Modifier = Modifier,
 ) {
     DisposableEffect(Unit) {
@@ -197,6 +201,14 @@
 
     Canvas(
         modifier
+            // Because the width also includes spacing to the left and right of the leftmost and
+            // rightmost dots in the grid and because UX mocks specify the width without that
+            // spacing, the actual width needs to be defined slightly bigger than the UX mock width.
+            .width((262 * colCount / 2).dp)
+            // Because the height also includes spacing above and below the topmost and bottommost
+            // dots in the grid and because UX mocks specify the height without that spacing, the
+            // actual height needs to be defined slightly bigger than the UX mock height.
+            .height((262 * rowCount / 2).dp)
             // Need to clip to bounds to make sure that the lines don't follow the input pointer
             // when it leaves the bounds of the dot grid.
             .clipToBounds()
@@ -260,7 +272,7 @@
                     availableSize = containerSize.height,
                     spacingPerDot = spacing,
                     dotCount = rowCount,
-                    isCentered = layout.isCenteredVertically,
+                    isCentered = centerDotsVertically,
                 )
             offset = Offset(horizontalOffset, verticalOffset)
             scale = (colCount * spacing) / containerSize.width
@@ -423,10 +435,6 @@
     }
 }
 
-/** Whether the UI should be centered vertically. */
-private val BouncerSceneLayout.isCenteredVertically: Boolean
-    get() = this == BouncerSceneLayout.SPLIT
-
 private const val DOT_DIAMETER_DP = 16
 private const val SELECTED_DOT_DIAMETER_DP = 24
 private const val SELECTED_DOT_REACTION_ANIMATION_DURATION_MS = 83
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PinBouncer.kt b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PinBouncer.kt
index 8f5d9f4..f505b90 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PinBouncer.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PinBouncer.kt
@@ -52,7 +52,6 @@
 import com.android.compose.animation.Easings
 import com.android.compose.grid.VerticalGrid
 import com.android.compose.modifiers.thenIf
-import com.android.systemui.bouncer.ui.helper.BouncerSceneLayout
 import com.android.systemui.bouncer.ui.viewmodel.ActionButtonAppearance
 import com.android.systemui.bouncer.ui.viewmodel.PinBouncerViewModel
 import com.android.systemui.common.shared.model.ContentDescription
@@ -70,7 +69,7 @@
 @Composable
 fun PinPad(
     viewModel: PinBouncerViewModel,
-    layout: BouncerSceneLayout,
+    verticalSpacing: Dp,
     modifier: Modifier = Modifier,
 ) {
     DisposableEffect(Unit) {
@@ -96,8 +95,8 @@
 
     VerticalGrid(
         columns = columns,
-        verticalSpacing = layout.verticalSpacing,
-        horizontalSpacing = calculateHorizontalSpacingBetweenColumns(layout.gridWidth),
+        verticalSpacing = verticalSpacing,
+        horizontalSpacing = calculateHorizontalSpacingBetweenColumns(gridWidth = 300.dp),
         modifier = modifier,
     ) {
         repeat(9) { index ->
@@ -355,14 +354,6 @@
     return (gridWidth - (pinButtonMaxSize * columns)) / (columns - 1)
 }
 
-/** The width of the grid of PIN pad buttons, in dips. */
-private val BouncerSceneLayout.gridWidth: Dp
-    get() = if (isUseCompactSize) 292.dp else 300.dp
-
-/** The spacing between rows of PIN pad buttons, in dips. */
-private val BouncerSceneLayout.verticalSpacing: Dp
-    get() = if (isUseCompactSize) 8.dp else 12.dp
-
 /** Number of columns in the PIN pad grid. */
 private const val columns = 3
 /** Maximum size (width and height) of each PIN pad button. */
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/Notifications.kt b/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/Notifications.kt
index c49c197..12f1b30 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/Notifications.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/Notifications.kt
@@ -27,11 +27,13 @@
 import androidx.compose.material3.MaterialTheme
 import androidx.compose.material3.Text
 import androidx.compose.runtime.Composable
+import androidx.compose.runtime.collectAsState
 import androidx.compose.runtime.getValue
 import androidx.compose.ui.Alignment
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.draw.clip
 import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.graphics.graphicsLayer
 import androidx.compose.ui.layout.LayoutCoordinates
 import androidx.compose.ui.layout.boundsInWindow
 import androidx.compose.ui.layout.onPlaced
@@ -89,13 +91,18 @@
     isScrimVisible: Boolean,
     modifier: Modifier = Modifier,
 ) {
+    val cornerRadius by viewModel.cornerRadiusDp.collectAsState()
+
     Box(modifier = modifier) {
         if (isScrimVisible) {
             Box(
                 modifier =
                     Modifier.element(Notifications.Elements.NotificationScrim)
                         .fillMaxSize()
-                        .clip(RoundedCornerShape(32.dp))
+                        .graphicsLayer {
+                            shape = RoundedCornerShape(cornerRadius.dp)
+                            clip = true
+                        }
                         .background(MaterialTheme.colorScheme.surface)
             )
         }
@@ -167,7 +174,9 @@
                     }
                     val boundsInWindow = coordinates.boundsInWindow()
                     viewModel.onBoundsChanged(
+                        left = boundsInWindow.left,
                         top = boundsInWindow.top,
+                        right = boundsInWindow.right,
                         bottom = boundsInWindow.bottom,
                     )
                 }
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/GestureHandler.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/GestureHandler.kt
index 5d8eaf7..58052cd 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/GestureHandler.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/GestureHandler.kt
@@ -2,10 +2,9 @@
 
 import androidx.compose.ui.geometry.Offset
 import androidx.compose.ui.input.nestedscroll.NestedScrollConnection
-import androidx.compose.ui.unit.IntSize
 
 interface DraggableHandler {
-    fun onDragStarted(layoutSize: IntSize, startedPosition: Offset, pointersDown: Int = 1)
+    fun onDragStarted(startedPosition: Offset, overSlop: Float, pointersDown: Int = 1)
     fun onDelta(pixels: Float)
     fun onDragStopped(velocity: Float)
 }
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/MultiPointerDraggable.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/MultiPointerDraggable.kt
index a0fba80..3873878 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/MultiPointerDraggable.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/MultiPointerDraggable.kt
@@ -66,8 +66,8 @@
     orientation: Orientation,
     enabled: Boolean,
     startDragImmediately: Boolean,
-    onDragStarted: (layoutSize: IntSize, startedPosition: Offset, pointersDown: Int) -> Unit,
-    onDragDelta: (Float) -> Unit,
+    onDragStarted: (startedPosition: Offset, overSlop: Float, pointersDown: Int) -> Unit,
+    onDragDelta: (delta: Float) -> Unit,
     onDragStopped: (velocity: Float) -> Unit,
 ): Modifier =
     this.then(
@@ -86,7 +86,7 @@
     private val enabled: Boolean,
     private val startDragImmediately: Boolean,
     private val onDragStarted:
-        (layoutSize: IntSize, startedPosition: Offset, pointersDown: Int) -> Unit,
+        (startedPosition: Offset, overSlop: Float, pointersDown: Int) -> Unit,
     private val onDragDelta: (Float) -> Unit,
     private val onDragStopped: (velocity: Float) -> Unit,
 ) : ModifierNodeElement<MultiPointerDraggableNode>() {
@@ -114,7 +114,7 @@
     orientation: Orientation,
     enabled: Boolean,
     var startDragImmediately: Boolean,
-    var onDragStarted: (layoutSize: IntSize, startedPosition: Offset, pointersDown: Int) -> Unit,
+    var onDragStarted: (startedPosition: Offset, overSlop: Float, pointersDown: Int) -> Unit,
     var onDragDelta: (Float) -> Unit,
     var onDragStopped: (velocity: Float) -> Unit,
 ) : PointerInputModifierNode, DelegatingNode(), CompositionLocalConsumerModifierNode {
@@ -153,9 +153,9 @@
             return
         }
 
-        val onDragStart: (Offset, Int) -> Unit = { startedPosition, pointersDown ->
+        val onDragStart: (Offset, Float, Int) -> Unit = { startedPosition, overSlop, pointersDown ->
             velocityTracker.resetTracking()
-            onDragStarted(size, startedPosition, pointersDown)
+            onDragStarted(startedPosition, overSlop, pointersDown)
         }
 
         val onDragCancel: () -> Unit = { onDragStopped(/* velocity= */ 0f) }
@@ -203,7 +203,7 @@
 private suspend fun PointerInputScope.detectDragGestures(
     orientation: Orientation,
     startDragImmediately: () -> Boolean,
-    onDragStart: (startedPosition: Offset, pointersDown: Int) -> Unit,
+    onDragStart: (startedPosition: Offset, overSlop: Float, pointersDown: Int) -> Unit,
     onDragEnd: () -> Unit,
     onDragCancel: () -> Unit,
     onDrag: (change: PointerInputChange, dragAmount: Float) -> Unit,
@@ -241,7 +241,7 @@
                 }
             }
 
-            onDragStart(drag.position, pressed.size)
+            onDragStart(drag.position, overSlop, pressed.size)
             onDrag(drag, overSlop)
 
             val successful =
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/NestedScrollToScene.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/NestedScrollToScene.kt
index 2986504..ded6cc1 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/NestedScrollToScene.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/NestedScrollToScene.kt
@@ -44,22 +44,22 @@
      * Overscroll will only be used by the [SceneTransitionLayout] to move to the next scene if the
      * gesture begins at the edge of the scrollable component (so that a scroll in that direction
      * can no longer be consumed). If the gesture is partially consumed by the scrollable component,
-     * there will be NO overscroll effect between scenes.
+     * there will be NO preview of the next scene.
      *
      * In addition, during scene transitions, scroll events are consumed by the
      * [SceneTransitionLayout] instead of the scrollable component.
      */
-    EdgeNoOverscroll(canStartOnPostFling = false),
+    EdgeNoPreview(canStartOnPostFling = false),
 
     /**
      * Overscroll will only be used by the [SceneTransitionLayout] to move to the next scene if the
      * gesture begins at the edge of the scrollable component. If the gesture is partially consumed
-     * by the scrollable component, there will be an overscroll effect between scenes.
+     * by the scrollable component, there will be a preview of the next scene.
      *
      * In addition, during scene transitions, scroll events are consumed by the
      * [SceneTransitionLayout] instead of the scrollable component.
      */
-    EdgeWithOverscroll(canStartOnPostFling = true),
+    EdgeWithPreview(canStartOnPostFling = true),
 
     /**
      * Any overscroll will be used by the [SceneTransitionLayout] to move to the next scene.
@@ -67,7 +67,7 @@
      * In addition, during scene transitions, scroll events are consumed by the
      * [SceneTransitionLayout] instead of the scrollable component.
      */
-    Always(canStartOnPostFling = true),
+    EdgeAlways(canStartOnPostFling = true),
 }
 
 internal fun Modifier.nestedScrollToScene(
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneGestureHandler.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneGestureHandler.kt
index b00c886..212c9eb6 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneGestureHandler.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneGestureHandler.kt
@@ -14,6 +14,8 @@
  * limitations under the License.
  */
 
+@file:Suppress("NOTHING_TO_INLINE")
+
 package com.android.compose.animation.scene
 
 import android.util.Log
@@ -26,7 +28,6 @@
 import androidx.compose.runtime.mutableStateOf
 import androidx.compose.runtime.setValue
 import androidx.compose.ui.geometry.Offset
-import androidx.compose.ui.unit.IntSize
 import androidx.compose.ui.unit.dp
 import androidx.compose.ui.unit.round
 import com.android.compose.nestedscroll.PriorityNestedScrollConnection
@@ -48,14 +49,13 @@
             layoutImpl.state.transitionState = value
         }
 
-    /**
-     * The transition controlled by this gesture handler. It will be set as the [transitionState] in
-     * the [SceneTransitionLayoutImpl] whenever this handler is driving the current transition.
-     *
-     * Note: the initialScene here does not matter, it's only used for initializing the transition
-     * and will be replaced when a drag event starts.
-     */
-    internal val swipeTransition = SwipeTransition(initialScene = currentScene)
+    internal var swipeTransition: SwipeTransition = SwipeTransition(currentScene, currentScene, 1f)
+        private set
+
+    private fun updateTransition(newTransition: SwipeTransition, force: Boolean = false) {
+        if (isDrivingTransition || force) transitionState = newTransition
+        swipeTransition = newTransition
+    }
 
     internal val currentScene: Scene
         get() = layoutImpl.scene(transitionState.currentScene)
@@ -63,15 +63,6 @@
     internal val isDrivingTransition
         get() = transitionState == swipeTransition
 
-    internal var isAnimatingOffset
-        get() = swipeTransition.isAnimatingOffset
-        private set(value) {
-            swipeTransition.isAnimatingOffset = value
-        }
-
-    internal val swipeTransitionToScene
-        get() = swipeTransition._toScene
-
     /**
      * The velocity threshold at which the intent of the user is to swipe up or down. It is the same
      * as SwipeableV2Defaults.VelocityThreshold.
@@ -86,11 +77,17 @@
 
     internal var gestureWithPriority: Any? = null
 
-    internal fun onDragStarted(pointersDown: Int, layoutSize: IntSize, startedPosition: Offset?) {
+    /** The [UserAction]s associated to the current swipe. */
+    private var actionUpOrLeft: UserAction? = null
+    private var actionDownOrRight: UserAction? = null
+    private var actionUpOrLeftNoEdge: UserAction? = null
+    private var actionDownOrRightNoEdge: UserAction? = null
+
+    internal fun onDragStarted(pointersDown: Int, startedPosition: Offset?, overSlop: Float) {
         if (isDrivingTransition) {
             // This [transition] was already driving the animation: simply take over it.
             // Stop animating and start from where the current offset.
-            swipeTransition.stopOffsetAnimation()
+            swipeTransition.cancelOffsetAnimation()
             return
         }
 
@@ -106,37 +103,29 @@
         }
 
         val fromScene = currentScene
+        setCurrentActions(fromScene, startedPosition, pointersDown)
 
-        swipeTransition._currentScene = fromScene
-        swipeTransition._fromScene = fromScene
+        if (fromScene.upOrLeft() == null && fromScene.downOrRight() == null) {
+            return
+        }
 
-        // We don't know where we are transitioning to yet given that the drag just started, so set
-        // it to fromScene, which will effectively be treated the same as Idle(fromScene).
-        swipeTransition._toScene = fromScene
+        val (targetScene, distance) = fromScene.findTargetSceneAndDistance(overSlop)
 
-        swipeTransition.stopOffsetAnimation()
-        swipeTransition.dragOffset = 0f
+        updateTransition(SwipeTransition(fromScene, targetScene, distance), force = true)
+    }
 
-        // Use the layout size in the swipe orientation for swipe distance.
-        // TODO(b/290184746): Also handle custom distances for transitions. With smaller distances,
-        // we will also have to make sure that we correctly handle overscroll.
-        swipeTransition.absoluteDistance =
-            when (orientation) {
-                Orientation.Horizontal -> layoutSize.width
-                Orientation.Vertical -> layoutSize.height
-            }.toFloat()
-
+    private fun setCurrentActions(fromScene: Scene, startedPosition: Offset?, pointersDown: Int) {
         val fromEdge =
             startedPosition?.let { position ->
                 layoutImpl.edgeDetector.edge(
-                    layoutSize,
+                    fromScene.targetSize,
                     position.round(),
                     layoutImpl.density,
                     orientation,
                 )
             }
 
-        swipeTransition.actionUpOrLeft =
+        val upOrLeft =
             Swipe(
                 direction =
                     when (orientation) {
@@ -147,7 +136,7 @@
                 fromEdge = fromEdge,
             )
 
-        swipeTransition.actionDownOrRight =
+        val downOrRight =
             Swipe(
                 direction =
                     when (orientation) {
@@ -159,108 +148,114 @@
             )
 
         if (fromEdge == null) {
-            swipeTransition.actionUpOrLeftNoEdge = null
-            swipeTransition.actionDownOrRightNoEdge = null
+            actionUpOrLeft = null
+            actionDownOrRight = null
+            actionUpOrLeftNoEdge = upOrLeft
+            actionDownOrRightNoEdge = downOrRight
         } else {
-            swipeTransition.actionUpOrLeftNoEdge =
-                (swipeTransition.actionUpOrLeft as Swipe).copy(fromEdge = null)
-            swipeTransition.actionDownOrRightNoEdge =
-                (swipeTransition.actionDownOrRight as Swipe).copy(fromEdge = null)
-        }
-
-        if (swipeTransition.absoluteDistance > 0f) {
-            transitionState = swipeTransition
+            actionUpOrLeft = upOrLeft
+            actionDownOrRight = downOrRight
+            actionUpOrLeftNoEdge = upOrLeft.copy(fromEdge = null)
+            actionDownOrRightNoEdge = downOrRight.copy(fromEdge = null)
         }
     }
 
-    internal fun onDrag(delta: Float) {
-        if (delta == 0f) return
+    /**
+     * Use the layout size in the swipe orientation for swipe distance.
+     *
+     * TODO(b/290184746): Also handle custom distances for transitions. With smaller distances, we
+     *   will also have to make sure that we correctly handle overscroll.
+     */
+    private fun Scene.getAbsoluteDistance(): Float {
+        return when (orientation) {
+            Orientation.Horizontal -> targetSize.width
+            Orientation.Vertical -> targetSize.height
+        }.toFloat()
+    }
 
+    internal fun onDrag(delta: Float) {
+        if (delta == 0f || !isDrivingTransition) return
         swipeTransition.dragOffset += delta
 
-        // First check transition.fromScene should be changed for the case where the user quickly
-        // swiped twice in a row to accelerate the transition and go from A => B then B => C really
-        // fast.
-        maybeHandleAcceleratedSwipe()
-
-        val offset = swipeTransition.dragOffset
-        val fromScene = swipeTransition._fromScene
+        val (fromScene, acceleratedOffset) =
+            computeFromSceneConsideringAcceleratedSwipe(swipeTransition)
+        swipeTransition.dragOffset += acceleratedOffset
 
         // Compute the target scene depending on the current offset.
-        val target = fromScene.findTargetSceneAndDistance(offset)
+        val (targetScene, distance) =
+            fromScene.findTargetSceneAndDistance(swipeTransition.dragOffset)
 
-        if (swipeTransition._toScene.key != target.sceneKey) {
-            swipeTransition._toScene = layoutImpl.scenes.getValue(target.sceneKey)
-        }
-
-        if (swipeTransition._distance != target.distance) {
-            swipeTransition._distance = target.distance
+        // TODO(b/290184746): support long scroll A => B => C? especially for non fullscreen scenes
+        if (
+            fromScene.key != swipeTransition.fromScene || targetScene.key != swipeTransition.toScene
+        ) {
+            updateTransition(
+                SwipeTransition(fromScene, targetScene, distance).apply {
+                    this.dragOffset = swipeTransition.dragOffset
+                }
+            )
         }
     }
 
     /**
      * Change fromScene in the case where the user quickly swiped multiple times in the same
      * direction to accelerate the transition from A => B then B => C.
+     *
+     * @return the new fromScene and a dragOffset to be added in case the scene has changed
+     *
+     * TODO(b/290184746): the second drag needs to pass B to work. Add support for flinging twice
+     *   before B has been reached
      */
-    private fun maybeHandleAcceleratedSwipe() {
+    private inline fun computeFromSceneConsideringAcceleratedSwipe(
+        swipeTransition: SwipeTransition,
+    ): Pair<Scene, Float> {
         val toScene = swipeTransition._toScene
         val fromScene = swipeTransition._fromScene
+        val absoluteDistance = swipeTransition.distance.absoluteValue
 
         // If the swipe was not committed, don't do anything.
         if (fromScene == toScene || swipeTransition._currentScene != toScene) {
-            return
+            return Pair(fromScene, 0f)
         }
 
         // If the offset is past the distance then let's change fromScene so that the user can swipe
         // to the next screen or go back to the previous one.
         val offset = swipeTransition.dragOffset
-        val absoluteDistance = swipeTransition.absoluteDistance
-        if (offset <= -absoluteDistance && swipeTransition.upOrLeft(fromScene) == toScene.key) {
-            swipeTransition.dragOffset += absoluteDistance
-            swipeTransition._fromScene = toScene
-        } else if (
-            offset >= absoluteDistance && swipeTransition.downOrRight(fromScene) == toScene.key
-        ) {
-            swipeTransition.dragOffset -= absoluteDistance
-            swipeTransition._fromScene = toScene
+        return if (offset <= -absoluteDistance && fromScene.upOrLeft() == toScene.key) {
+            Pair(toScene, absoluteDistance)
+        } else if (offset >= absoluteDistance && fromScene.downOrRight() == toScene.key) {
+            Pair(toScene, -absoluteDistance)
+        } else {
+            Pair(fromScene, 0f)
         }
-
-        // Important note: toScene and distance will be updated right after this function is called,
-        // using fromScene and dragOffset.
     }
 
-    private class TargetScene(
-        val sceneKey: SceneKey,
-        val distance: Float,
-    )
-
-    private fun Scene.findTargetSceneAndDistance(directionOffset: Float): TargetScene {
-        val upOrLeft = swipeTransition.upOrLeft(this)
-        val downOrRight = swipeTransition.downOrRight(this)
+    // TODO(b/290184746): there are two bugs here:
+    // 1. if both upOrLeft and downOrRight become `null` during a transition this will crash
+    // 2. if one of them changes during a transition, the transition will jump cut to the new target
+    private inline fun Scene.findTargetSceneAndDistance(
+        directionOffset: Float
+    ): Pair<Scene, Float> {
+        val upOrLeft = upOrLeft()
+        val downOrRight = downOrRight()
+        val absoluteDistance = getAbsoluteDistance()
 
         // Compute the target scene depending on the current offset.
-        return when {
-            directionOffset < 0f && upOrLeft != null -> {
-                TargetScene(
-                    sceneKey = upOrLeft,
-                    distance = -swipeTransition.absoluteDistance,
-                )
-            }
-            directionOffset > 0f && downOrRight != null -> {
-                TargetScene(
-                    sceneKey = downOrRight,
-                    distance = swipeTransition.absoluteDistance,
-                )
-            }
-            else -> {
-                TargetScene(
-                    sceneKey = key,
-                    distance = 0f,
-                )
-            }
+        return if ((directionOffset < 0f && upOrLeft != null) || downOrRight == null) {
+            Pair(layoutImpl.scene(upOrLeft!!), -absoluteDistance)
+        } else {
+            Pair(layoutImpl.scene(downOrRight), absoluteDistance)
         }
     }
 
+    private fun Scene.upOrLeft(): SceneKey? {
+        return userActions[actionUpOrLeft] ?: userActions[actionUpOrLeftNoEdge]
+    }
+
+    private fun Scene.downOrRight(): SceneKey? {
+        return userActions[actionDownOrRight] ?: userActions[actionDownOrRightNoEdge]
+    }
+
     internal fun onDragStopped(velocity: Float, canChangeScene: Boolean) {
         // The state was changed since the drag started; don't do anything.
         if (!isDrivingTransition) {
@@ -291,11 +286,6 @@
             // velocity and offset of the transition, then we launch the animation.
 
             val toScene = swipeTransition._toScene
-            if (fromScene == toScene) {
-                // We were not animating.
-                transitionState = TransitionState.Idle(fromScene.key)
-                return
-            }
 
             // Compute the destination scene (and therefore offset) to settle in.
             val offset = swipeTransition.dragOffset
@@ -322,12 +312,14 @@
 
             if (startFromIdlePosition) {
                 // If there is a next scene, we start the overscroll animation.
-                val target = fromScene.findTargetSceneAndDistance(velocity)
-                val isValidTarget = target.distance != 0f && target.sceneKey != fromScene.key
+                val (targetScene, distance) = fromScene.findTargetSceneAndDistance(velocity)
+                val isValidTarget = distance != 0f && targetScene.key != fromScene.key
                 if (isValidTarget) {
-                    swipeTransition._toScene = layoutImpl.scene(target.sceneKey)
-                    swipeTransition._distance = target.distance
-
+                    updateTransition(
+                        SwipeTransition(fromScene, targetScene, distance).apply {
+                            _currentScene = swipeTransition._currentScene
+                        }
+                    )
                     animateTo(targetScene = fromScene, targetOffset = 0f)
                 } else {
                     // We will not animate
@@ -382,10 +374,10 @@
     ) {
         swipeTransition.startOffsetAnimation {
             coroutineScope.launch {
-                if (!isAnimatingOffset) {
+                if (!swipeTransition.isAnimatingOffset) {
                     swipeTransition.offsetAnimatable.snapTo(swipeTransition.dragOffset)
                 }
-                isAnimatingOffset = true
+                swipeTransition.isAnimatingOffset = true
 
                 swipeTransition.offsetAnimatable.animateTo(
                     targetOffset,
@@ -397,7 +389,7 @@
                     initialVelocity = initialVelocity,
                 )
 
-                isAnimatingOffset = false
+                swipeTransition.finishOffsetAnimation()
 
                 // Now that the animation is done, the state should be idle. Note that if the state
                 // was changed since this animation started, some external code changed it and we
@@ -410,29 +402,26 @@
         }
     }
 
-    internal class SwipeTransition(initialScene: Scene) : TransitionState.Transition {
-        var _currentScene by mutableStateOf(initialScene)
+    internal class SwipeTransition(
+        val _fromScene: Scene,
+        val _toScene: Scene,
+        /**
+         * The signed distance between [fromScene] and [toScene]. It is negative if [fromScene] is
+         * above or to the left of [toScene].
+         */
+        val distance: Float
+    ) : TransitionState.Transition {
+        var _currentScene by mutableStateOf(_fromScene)
         override val currentScene: SceneKey
             get() = _currentScene.key
 
-        var _fromScene by mutableStateOf(initialScene)
-        override val fromScene: SceneKey
-            get() = _fromScene.key
+        override val fromScene: SceneKey = _fromScene.key
 
-        var _toScene by mutableStateOf(initialScene)
-        override val toScene: SceneKey
-            get() = _toScene.key
+        override val toScene: SceneKey = _toScene.key
 
         override val progress: Float
             get() {
                 val offset = if (isAnimatingOffset) offsetAnimatable.value else dragOffset
-                if (distance == 0f) {
-                    // This can happen only if fromScene == toScene.
-                    error(
-                        "Transition.progress should be called only when Transition.fromScene != " +
-                            "Transition.toScene"
-                    )
-                }
                 return offset / distance
             }
 
@@ -459,46 +448,22 @@
 
         /** Ends any previous [offsetAnimationJob] and runs the new [job]. */
         fun startOffsetAnimation(job: () -> Job) {
-            stopOffsetAnimation()
+            cancelOffsetAnimation()
             offsetAnimationJob = job()
         }
 
-        /** Stops any ongoing offset animation. */
-        fun stopOffsetAnimation() {
+        /** Cancel any ongoing offset animation. */
+        fun cancelOffsetAnimation() {
             offsetAnimationJob?.cancel()
+            finishOffsetAnimation()
+        }
 
+        fun finishOffsetAnimation() {
             if (isAnimatingOffset) {
                 isAnimatingOffset = false
                 dragOffset = offsetAnimatable.value
             }
         }
-
-        /** The absolute distance between [fromScene] and [toScene]. */
-        var absoluteDistance = 0f
-
-        /**
-         * The signed distance between [fromScene] and [toScene]. It is negative if [fromScene] is
-         * above or to the left of [toScene].
-         */
-        var _distance by mutableFloatStateOf(0f)
-        val distance: Float
-            get() = _distance
-
-        /** The [UserAction]s associated to this swipe. */
-        var actionUpOrLeft: UserAction = Back
-        var actionDownOrRight: UserAction = Back
-        var actionUpOrLeftNoEdge: UserAction? = null
-        var actionDownOrRightNoEdge: UserAction? = null
-
-        fun upOrLeft(scene: Scene): SceneKey? {
-            return scene.userActions[actionUpOrLeft]
-                ?: actionUpOrLeftNoEdge?.let { scene.userActions[it] }
-        }
-
-        fun downOrRight(scene: Scene): SceneKey? {
-            return scene.userActions[actionDownOrRight]
-                ?: actionDownOrRightNoEdge?.let { scene.userActions[it] }
-        }
     }
 
     companion object {
@@ -509,9 +474,9 @@
 private class SceneDraggableHandler(
     private val gestureHandler: SceneGestureHandler,
 ) : DraggableHandler {
-    override fun onDragStarted(layoutSize: IntSize, startedPosition: Offset, pointersDown: Int) {
+    override fun onDragStarted(startedPosition: Offset, overSlop: Float, pointersDown: Int) {
         gestureHandler.gestureWithPriority = this
-        gestureHandler.onDragStarted(pointersDown, layoutSize, startedPosition)
+        gestureHandler.onDragStarted(pointersDown, startedPosition, overSlop)
     }
 
     override fun onDelta(pixels: Float) {
@@ -589,7 +554,7 @@
                 // The progress value can go beyond this range in the case of overscroll.
                 val shouldSnapToIdle = isProgressCloseTo(0f) || isProgressCloseTo(1f)
                 if (shouldSnapToIdle) {
-                    gestureHandler.swipeTransition.stopOffsetAnimation()
+                    gestureHandler.swipeTransition.cancelOffsetAnimation()
                     gestureHandler.transitionState =
                         TransitionState.Idle(gestureHandler.swipeTransition.currentScene)
                 }
@@ -612,15 +577,15 @@
                         canChangeScene = false // unused: added for consistency
                         false
                     }
-                    NestedScrollBehavior.EdgeNoOverscroll -> {
+                    NestedScrollBehavior.EdgeNoPreview -> {
                         canChangeScene = isZeroOffset
                         isZeroOffset && hasNextScene(offsetAvailable)
                     }
-                    NestedScrollBehavior.EdgeWithOverscroll -> {
+                    NestedScrollBehavior.EdgeWithPreview -> {
                         canChangeScene = isZeroOffset
                         hasNextScene(offsetAvailable)
                     }
-                    NestedScrollBehavior.Always -> {
+                    NestedScrollBehavior.EdgeAlways -> {
                         canChangeScene = true
                         hasNextScene(offsetAvailable)
                     }
@@ -639,12 +604,12 @@
                 behavior.canStartOnPostFling && hasNextScene(velocityAvailable)
             },
             canContinueScroll = { true },
-            onStart = {
+            onStart = { offsetAvailable ->
                 gestureHandler.gestureWithPriority = this
                 gestureHandler.onDragStarted(
                     pointersDown = 1,
-                    layoutSize = gestureHandler.currentScene.targetSize,
                     startedPosition = null,
+                    overSlop = offsetAvailable,
                 )
             },
             onScroll = { offsetAvailable ->
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayout.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayout.kt
index 07add77..afa184b 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayout.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayout.kt
@@ -132,8 +132,8 @@
      */
     fun Modifier.nestedScrollToScene(
         orientation: Orientation,
-        startBehavior: NestedScrollBehavior = NestedScrollBehavior.EdgeNoOverscroll,
-        endBehavior: NestedScrollBehavior = NestedScrollBehavior.EdgeNoOverscroll,
+        startBehavior: NestedScrollBehavior = NestedScrollBehavior.EdgeNoPreview,
+        endBehavior: NestedScrollBehavior = NestedScrollBehavior.EdgeNoPreview,
     ): Modifier
 
     /**
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SwipeToScene.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SwipeToScene.kt
index 2c78dee..116a666 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SwipeToScene.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SwipeToScene.kt
@@ -46,7 +46,7 @@
         // user can't swipe in the other direction.
         startDragImmediately =
             gestureHandler.isDrivingTransition &&
-                gestureHandler.isAnimatingOffset &&
+                gestureHandler.swipeTransition.isAnimatingOffset &&
                 !canOppositeSwipe,
         onDragStarted = gestureHandler.draggable::onDragStarted,
         onDragDelta = gestureHandler.draggable::onDelta,
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/nestedscroll/PriorityNestedScrollConnection.kt b/packages/SystemUI/compose/scene/src/com/android/compose/nestedscroll/PriorityNestedScrollConnection.kt
index a5fd1bf..c49a2b8 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/nestedscroll/PriorityNestedScrollConnection.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/nestedscroll/PriorityNestedScrollConnection.kt
@@ -38,7 +38,7 @@
     private val canStartPostScroll: (offsetAvailable: Offset, offsetBeforeStart: Offset) -> Boolean,
     private val canStartPostFling: (velocityAvailable: Velocity) -> Boolean,
     private val canContinueScroll: () -> Boolean,
-    private val onStart: () -> Unit,
+    private val onStart: (offsetAvailable: Offset) -> Unit,
     private val onScroll: (offsetAvailable: Offset) -> Offset,
     private val onStop: (velocityAvailable: Velocity) -> Velocity,
 ) : NestedScrollConnection {
@@ -131,7 +131,7 @@
 
         // Note: onStop will be called if we cannot continue to scroll (step 3a), or the finger is
         // lifted (step 3b), or this object has been destroyed (step 3c).
-        onStart()
+        onStart(available)
 
         return onScroll(available)
     }
@@ -156,7 +156,7 @@
     canStartPostScroll: (offsetAvailable: Float, offsetBeforeStart: Float) -> Boolean,
     canStartPostFling: (velocityAvailable: Float) -> Boolean,
     canContinueScroll: () -> Boolean,
-    onStart: () -> Unit,
+    onStart: (offsetAvailable: Float) -> Unit,
     onScroll: (offsetAvailable: Float) -> Float,
     onStop: (velocityAvailable: Float) -> Float,
 ) =
@@ -172,7 +172,7 @@
                 canStartPostFling(velocityAvailable.toFloat())
             },
             canContinueScroll = canContinueScroll,
-            onStart = onStart,
+            onStart = { offsetAvailable -> onStart(offsetAvailable.toFloat()) },
             onScroll = { offsetAvailable: Offset ->
                 onScroll(offsetAvailable.toFloat()).toOffset()
             },
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneGestureHandlerTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneGestureHandlerTest.kt
index b84cb36..aa942e0 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneGestureHandlerTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneGestureHandlerTest.kt
@@ -29,10 +29,10 @@
 import androidx.compose.ui.unit.IntSize
 import androidx.compose.ui.unit.Velocity
 import androidx.test.ext.junit.runners.AndroidJUnit4
-import com.android.compose.animation.scene.NestedScrollBehavior.Always
 import com.android.compose.animation.scene.NestedScrollBehavior.DuringTransitionBetweenScenes
-import com.android.compose.animation.scene.NestedScrollBehavior.EdgeNoOverscroll
-import com.android.compose.animation.scene.NestedScrollBehavior.EdgeWithOverscroll
+import com.android.compose.animation.scene.NestedScrollBehavior.EdgeAlways
+import com.android.compose.animation.scene.NestedScrollBehavior.EdgeNoPreview
+import com.android.compose.animation.scene.NestedScrollBehavior.EdgeWithPreview
 import com.android.compose.animation.scene.TestScenes.SceneA
 import com.android.compose.animation.scene.TestScenes.SceneB
 import com.android.compose.animation.scene.TestScenes.SceneC
@@ -191,8 +191,13 @@
         runMonotonicClockTest { TestGestureScope(coroutineScope = this).block() }
     }
 
-    private fun DraggableHandler.onDragStarted() {
-        onDragStarted(layoutSize = LAYOUT_SIZE, startedPosition = Offset.Zero)
+    private fun DraggableHandler.onDragStarted(
+        overSlop: Float = 0f,
+        startedPosition: Offset = Offset.Zero,
+    ) {
+        onDragStarted(startedPosition, overSlop)
+        // MultiPointerDraggable will always call onDelta with the initial overSlop right after
+        onDelta(overSlop)
     }
 
     @Test fun testPreconditions() = runGestureTest { assertIdle(currentScene = SceneA) }
@@ -291,6 +296,86 @@
     }
 
     @Test
+    fun onDragStartedWithoutActionsInBothDirections_stayIdle() = runGestureTest {
+        horizontalSceneGestureHandler.draggable.onDragStarted(up(0.3f))
+        assertIdle(currentScene = SceneA)
+        horizontalSceneGestureHandler.draggable.onDragStarted(down(0.3f))
+        assertIdle(currentScene = SceneA)
+    }
+
+    @Test
+    fun onDragIntoNoAction_startTransitionToOppositeDirection() = runGestureTest {
+        navigateToSceneC()
+
+        // We are on SceneC which has no action in Down direction
+        draggable.onDragStarted(down(0.1f))
+        assertTransition(
+            currentScene = SceneC,
+            fromScene = SceneC,
+            toScene = SceneB,
+            progress = -0.1f
+        )
+
+        // Reverse drag direction, it will consume the previous drag
+        draggable.onDelta(up(0.1f))
+        assertTransition(
+            currentScene = SceneC,
+            fromScene = SceneC,
+            toScene = SceneB,
+            progress = 0.0f
+        )
+
+        // Continue reverse drag direction, it should record progress to Scene B
+        draggable.onDelta(up(0.1f))
+        assertTransition(
+            currentScene = SceneC,
+            fromScene = SceneC,
+            toScene = SceneB,
+            progress = 0.1f
+        )
+    }
+
+    @Test
+    fun onDragFromEdge_startTransitionToEdgeAction() = runGestureTest {
+        navigateToSceneC()
+
+        // Start dragging from the bottom
+        draggable.onDragStarted(up(0.1f), Offset(SCREEN_SIZE * 0.5f, SCREEN_SIZE))
+        assertTransition(
+            currentScene = SceneC,
+            fromScene = SceneC,
+            toScene = SceneA,
+            progress = 0.1f
+        )
+    }
+
+    @Test
+    fun onDragToExactlyZero_toSceneIsSet() = runGestureTest {
+        draggable.onDragStarted(down(0.3f))
+        assertTransition(
+            currentScene = SceneA,
+            fromScene = SceneA,
+            toScene = SceneC,
+            progress = 0.3f
+        )
+        draggable.onDelta(up(0.3f))
+        assertTransition(
+            currentScene = SceneA,
+            fromScene = SceneA,
+            toScene = SceneC,
+            progress = 0.0f
+        )
+    }
+
+    private fun TestGestureScope.navigateToSceneC() {
+        assertIdle(currentScene = SceneA)
+        draggable.onDragStarted(down(1f))
+        draggable.onDragStopped(0f)
+        advanceUntilIdle()
+        assertIdle(currentScene = SceneC)
+    }
+
+    @Test
     fun onAccelaratedScroll_scrollToThirdScene() = runGestureTest {
         // Drag A -> B with progress 0.2
         draggable.onDragStarted()
@@ -339,29 +424,29 @@
         )
 
         // The stop animation is not started yet
-        assertThat(sceneGestureHandler.isAnimatingOffset).isFalse()
+        assertThat(sceneGestureHandler.swipeTransition.isAnimatingOffset).isFalse()
 
         runCurrent()
 
-        assertThat(sceneGestureHandler.isAnimatingOffset).isTrue()
+        assertThat(sceneGestureHandler.swipeTransition.isAnimatingOffset).isTrue()
         assertThat(sceneGestureHandler.isDrivingTransition).isTrue()
         assertTransition(currentScene = SceneC)
 
         // Start a new gesture while the offset is animating
         draggable.onDragStarted()
-        assertThat(sceneGestureHandler.isAnimatingOffset).isFalse()
+        assertThat(sceneGestureHandler.swipeTransition.isAnimatingOffset).isFalse()
     }
 
     @Test
     fun onInitialPreScroll_EdgeWithOverscroll_doNotChangeState() = runGestureTest {
-        val nestedScroll = nestedScrollConnection(nestedScrollBehavior = EdgeWithOverscroll)
+        val nestedScroll = nestedScrollConnection(nestedScrollBehavior = EdgeWithPreview)
         nestedScroll.onPreScroll(available = offsetY10, source = NestedScrollSource.Drag)
         assertIdle(currentScene = SceneA)
     }
 
     @Test
     fun onPostScrollWithNothingAvailable_EdgeWithOverscroll_doNotChangeState() = runGestureTest {
-        val nestedScroll = nestedScrollConnection(nestedScrollBehavior = EdgeWithOverscroll)
+        val nestedScroll = nestedScrollConnection(nestedScrollBehavior = EdgeWithPreview)
         val consumed =
             nestedScroll.onPostScroll(
                 consumed = Offset.Zero,
@@ -375,7 +460,7 @@
 
     @Test
     fun onPostScrollWithSomethingAvailable_startSceneTransition() = runGestureTest {
-        val nestedScroll = nestedScrollConnection(nestedScrollBehavior = EdgeWithOverscroll)
+        val nestedScroll = nestedScrollConnection(nestedScrollBehavior = EdgeWithPreview)
         val consumed =
             nestedScroll.onPostScroll(
                 consumed = Offset.Zero,
@@ -404,7 +489,7 @@
 
     @Test
     fun afterSceneTransitionIsStarted_interceptPreScrollEvents() = runGestureTest {
-        val nestedScroll = nestedScrollConnection(nestedScrollBehavior = EdgeWithOverscroll)
+        val nestedScroll = nestedScrollConnection(nestedScrollBehavior = EdgeWithPreview)
         nestedScroll.scroll(available = offsetY10)
         assertTransition(currentScene = SceneA)
 
@@ -432,7 +517,7 @@
         firstScroll: Float,
         secondScroll: Float
     ) {
-        val nestedScroll = nestedScrollConnection(nestedScrollBehavior = EdgeWithOverscroll)
+        val nestedScroll = nestedScrollConnection(nestedScrollBehavior = EdgeWithPreview)
         // start scene transition
         nestedScroll.scroll(available = Offset(0f, SCREEN_SIZE * firstScroll))
 
@@ -485,7 +570,7 @@
 
     @Test
     fun onPreFling_velocityLowerThanThreshold_remainSameScene() = runGestureTest {
-        val nestedScroll = nestedScrollConnection(nestedScrollBehavior = EdgeWithOverscroll)
+        val nestedScroll = nestedScrollConnection(nestedScrollBehavior = EdgeWithPreview)
         nestedScroll.scroll(available = offsetY10)
         assertTransition(currentScene = SceneA)
 
@@ -517,7 +602,7 @@
 
     @Test
     fun flingAfterScroll_EdgeNoOverscroll_goToNextScene() = runGestureTest {
-        flingAfterScroll(use = EdgeNoOverscroll, idleAfterScroll = false)
+        flingAfterScroll(use = EdgeNoPreview, idleAfterScroll = false)
 
         assertTransition(currentScene = SceneC)
 
@@ -528,7 +613,7 @@
 
     @Test
     fun flingAfterScroll_EdgeWithOverscroll_goToNextScene() = runGestureTest {
-        flingAfterScroll(use = EdgeWithOverscroll, idleAfterScroll = false)
+        flingAfterScroll(use = EdgeWithPreview, idleAfterScroll = false)
 
         assertTransition(currentScene = SceneC)
 
@@ -539,7 +624,7 @@
 
     @Test
     fun flingAfterScroll_Always_goToNextScene() = runGestureTest {
-        flingAfterScroll(use = Always, idleAfterScroll = false)
+        flingAfterScroll(use = EdgeAlways, idleAfterScroll = false)
 
         assertTransition(currentScene = SceneC)
 
@@ -573,14 +658,14 @@
 
     @Test
     fun flingAfterScrollStartedInScene_EdgeNoOverscroll_doNothing() = runGestureTest {
-        flingAfterScrollStartedInScene(use = EdgeNoOverscroll, idleAfterScroll = true)
+        flingAfterScrollStartedInScene(use = EdgeNoPreview, idleAfterScroll = true)
 
         assertIdle(currentScene = SceneA)
     }
 
     @Test
     fun flingAfterScrollStartedInScene_EdgeWithOverscroll_doOverscrollAnimation() = runGestureTest {
-        flingAfterScrollStartedInScene(use = EdgeWithOverscroll, idleAfterScroll = false)
+        flingAfterScrollStartedInScene(use = EdgeWithPreview, idleAfterScroll = false)
 
         assertTransition(currentScene = SceneA)
 
@@ -591,7 +676,7 @@
 
     @Test
     fun flingAfterScrollStartedInScene_Always_goToNextScene() = runGestureTest {
-        flingAfterScrollStartedInScene(use = Always, idleAfterScroll = false)
+        flingAfterScrollStartedInScene(use = EdgeAlways, idleAfterScroll = false)
 
         assertTransition(currentScene = SceneC)
 
@@ -614,14 +699,14 @@
 
     @Test
     fun beforeNestedScrollStart_stop_shouldBeIgnored() = runGestureTest {
-        val nestedScroll = nestedScrollConnection(nestedScrollBehavior = EdgeWithOverscroll)
+        val nestedScroll = nestedScrollConnection(nestedScrollBehavior = EdgeWithPreview)
         nestedScroll.onPreFling(Velocity(0f, velocityThreshold))
         assertIdle(currentScene = SceneA)
     }
 
     @Test
     fun startNestedScrollWhileDragging() = runGestureTest {
-        val nestedScroll = nestedScrollConnection(nestedScrollBehavior = Always)
+        val nestedScroll = nestedScrollConnection(nestedScrollBehavior = EdgeAlways)
         draggable.onDragStarted()
         assertTransition(currentScene = SceneA)
 
diff --git a/packages/SystemUI/docs/executors.md b/packages/SystemUI/docs/executors.md
index 8520ce2..2d9438c 100644
--- a/packages/SystemUI/docs/executors.md
+++ b/packages/SystemUI/docs/executors.md
@@ -14,10 +14,10 @@
 [FakeExecutor][FakeExecutor] is available.
 
 [Executor]: https://developer.android.com/reference/java/util/concurrent/Executor.html
-[Handler]: https://developer.android.com/reference/android/os/Handler
+[Handler]: https://developer.android.com/reference/android/os/Handler.html
 [Runnable]: https://developer.android.com/reference/java/lang/Runnable.html
 [DelayableExecutor]: /packages/SystemUI/src/com/android/systemui/util/concurrency/DelayableExecutor.java
-[FakeExecutor]: /packages/SystemUI/tests/src/com/android/systemui/util/concurrency/FakeExecutor.java
+[FakeExecutor]: /packages/SystemUI/tests/utils/src/com/android/systemui/util/concurrency/FakeExecutor.java
 
 ## Rationale
 
@@ -117,7 +117,7 @@
 postDelayed() | `none`    | executeDelayed()
 postAtTime()  | `none`    | executeAtTime()
 
-There is one notable gap in this implementation: `Handler.postAtFrontOfQueue()`.
+There are some notable gaps in this implementation: `Handler.postAtFrontOfQueue()`.
 If you require this method, or similar, please reach out. The idea of a
 PriorityQueueExecutor has been floated, but will not be implemented until there
 is a clear need.
@@ -173,13 +173,20 @@
 
 If you feel that you have a use case that this does not cover, please reach out.
 
+### ContentObserver
+
+One notable place where Handlers have been a requirement in the past is with
+[ContentObserver], which takes a Handler as an argument. However, we have created
+[ExecutorContentObserver], which is a hidden API that accepts an [Executor] in its
+constructor instead of a [Handler], and is otherwise identical.
+
+[ContentObserver]: https://developer.android.com/reference/android/database/ContentObserver.html
+[ExecutorContentObserver]: /core/java/android/database/ExecutorContentObserver.java
+
 ### Handlers Are Still Necessary
 
-Handlers aren't going away. There are Android APIs that still require them (even
-if future API development discourages them). A simple example is
-[ContentObserver][ContentObserver]. Use them where necessary.
-
-[ContentObserver]: https://developer.android.com/reference/android/database/ContentObserver
+Handlers aren't going away. There are other Android APIs that still require them.
+Avoid Handlers when possible, but use them where necessary.
 
 ## Testing (FakeExecutor)
 
@@ -314,6 +321,15 @@
 The Runnables _will not_ interleave. All of one Executor's callbacks will run,
 then all of the other's.
 
+### Testing Handlers without Loopers
+
+If a [Handler] is required because it is used by Android APIs, but is only
+used in simple ways (i.e. just `Handler.post(Runnable)`), you may still
+want the benefits of [FakeExecutor] when writing your tests, which
+you can get by wrapping the [Executor] in a mock for testing. This can be
+done with `com.android.systemui.util.concurrency.mockExecutorHandler` in
+`MockExecutorHandler.kt`.
+
 ### TestableLooper.RunWithLooper
 
 As long as you're using FakeExecutors in all the code under test (and no
diff --git a/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt
index 543b291..695d888 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt
@@ -465,29 +465,6 @@
     }
 
     @Test
-    fun showNextSecurityScreenOrFinish_setsSecurityScreenToPinAfterSimPinUnlock() {
-        // GIVEN the current security method is SimPin
-        whenever(keyguardUpdateMonitor.getUserHasTrust(anyInt())).thenReturn(false)
-        whenever(keyguardUpdateMonitor.getUserUnlockedWithBiometric(TARGET_USER_ID))
-            .thenReturn(false)
-        underTest.showSecurityScreen(SecurityMode.SimPin)
-
-        // WHEN a request is made from the SimPin screens to show the next security method
-        whenever(keyguardSecurityModel.getSecurityMode(TARGET_USER_ID)).thenReturn(SecurityMode.PIN)
-        underTest.showNextSecurityScreenOrFinish(
-            /* authenticated= */ true,
-            TARGET_USER_ID,
-            /* bypassSecondaryLockScreen= */ true,
-            SecurityMode.SimPin
-        )
-
-        // THEN the next security method of PIN is set, and the keyguard is not marked as done
-        verify(viewMediatorCallback, never()).keyguardDonePending(anyInt())
-        verify(viewMediatorCallback, never()).keyguardDone(anyInt())
-        Truth.assertThat(underTest.currentSecurityMode).isEqualTo(SecurityMode.PIN)
-    }
-
-    @Test
     fun showNextSecurityScreenOrFinish_DeviceNotSecure() {
         // GIVEN the current security method is SimPin
         whenever(keyguardUpdateMonitor.getUserHasTrust(anyInt())).thenReturn(false)
@@ -578,6 +555,57 @@
     }
 
     @Test
+    fun showNextSecurityScreenOrFinish_SimPin_Password() {
+        // GIVEN the current security method is SimPin
+        whenever(keyguardUpdateMonitor.getUserHasTrust(anyInt())).thenReturn(false)
+        whenever(keyguardUpdateMonitor.getUserUnlockedWithBiometric(TARGET_USER_ID))
+            .thenReturn(false)
+        underTest.showSecurityScreen(SecurityMode.SimPin)
+
+        // WHEN a request is made from the SimPin screens to show the next security method
+        whenever(keyguardSecurityModel.getSecurityMode(TARGET_USER_ID))
+            .thenReturn(SecurityMode.Password)
+        // WHEN security method is SWIPE
+        whenever(lockPatternUtils.isLockScreenDisabled(anyInt())).thenReturn(false)
+        whenever(deviceProvisionedController.isUserSetup(anyInt())).thenReturn(false)
+        underTest.showNextSecurityScreenOrFinish(
+            /* authenticated= */ true,
+            TARGET_USER_ID,
+            /* bypassSecondaryLockScreen= */ true,
+            SecurityMode.SimPin
+        )
+
+        // THEN we will not show the password screen.
+        verify(viewFlipperController, never())
+            .getSecurityView(eq(SecurityMode.Password), any(), any())
+    }
+
+    @Test
+    fun showNextSecurityScreenOrFinish_SimPin_SimPin() {
+        // GIVEN the current security method is SimPin
+        whenever(keyguardUpdateMonitor.getUserHasTrust(anyInt())).thenReturn(false)
+        whenever(keyguardUpdateMonitor.getUserUnlockedWithBiometric(TARGET_USER_ID))
+            .thenReturn(false)
+        underTest.showSecurityScreen(SecurityMode.SimPin)
+
+        // WHEN a request is made from the SimPin screens to show the next security method
+        whenever(keyguardSecurityModel.getSecurityMode(TARGET_USER_ID))
+            .thenReturn(SecurityMode.SimPin)
+        // WHEN security method is SWIPE
+        whenever(lockPatternUtils.isLockScreenDisabled(anyInt())).thenReturn(false)
+        whenever(deviceProvisionedController.isUserSetup(anyInt())).thenReturn(false)
+        underTest.showNextSecurityScreenOrFinish(
+            /* authenticated= */ true,
+            TARGET_USER_ID,
+            /* bypassSecondaryLockScreen= */ true,
+            SecurityMode.SimPin
+        )
+
+        // THEN we will not show the password screen.
+        verify(viewFlipperController).getSecurityView(eq(SecurityMode.SimPin), any(), any())
+    }
+
+    @Test
     fun onSwipeUp_forwardsItToFaceAuthInteractor() {
         val registeredSwipeListener = registeredSwipeListener
         setupGetSecurityView(SecurityMode.Password)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSimPinViewControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSimPinViewControllerTest.kt
index 94c3bde..84d73543 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSimPinViewControllerTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSimPinViewControllerTest.kt
@@ -34,11 +34,14 @@
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
+import org.mockito.ArgumentCaptor
 import org.mockito.ArgumentMatchers.anyString
 import org.mockito.Mock
 import org.mockito.Mockito
 import org.mockito.Mockito.anyInt
 import org.mockito.Mockito.mock
+import org.mockito.Mockito.never
+import org.mockito.Mockito.reset
 import org.mockito.Mockito.verify
 import org.mockito.Mockito.`when`
 import org.mockito.MockitoAnnotations
@@ -63,6 +66,8 @@
     @Mock
     private lateinit var keyguardMessageAreaController:
         KeyguardMessageAreaController<BouncerKeyguardMessageArea>
+    private val updateMonitorCallbackArgumentCaptor =
+        ArgumentCaptor.forClass(KeyguardUpdateMonitorCallback::class.java)
 
     @Before
     fun setup() {
@@ -95,6 +100,9 @@
                 mSelectedUserInteractor
             )
         underTest.init()
+        underTest.onResume(0)
+        verify(keyguardUpdateMonitor)
+            .registerCallback(updateMonitorCallbackArgumentCaptor.capture())
     }
 
     @Test
@@ -111,6 +119,7 @@
 
     @Test
     fun onResume() {
+        reset(keyguardUpdateMonitor)
         underTest.onResume(KeyguardSecurityView.VIEW_REVEALED)
         verify(keyguardUpdateMonitor)
             .registerCallback(any(KeyguardUpdateMonitorCallback::class.java))
@@ -137,4 +146,22 @@
         underTest.resetState()
         verify(keyguardMessageAreaController).setMessage("")
     }
+
+    @Test
+    fun onSimStateChangedFromPinToPuk_showsCurrentSecurityScreen() {
+        updateMonitorCallbackArgumentCaptor.value.onSimStateChanged(
+            /* subId= */ 0,
+            /* slotId= */ 0,
+            TelephonyManager.SIM_STATE_PIN_REQUIRED
+        )
+        verify(keyguardSecurityCallback, never()).showCurrentSecurityScreen()
+
+        updateMonitorCallbackArgumentCaptor.value.onSimStateChanged(
+            /* subId= */ 0,
+            /* slotId= */ 0,
+            TelephonyManager.SIM_STATE_PUK_REQUIRED
+        )
+
+        verify(keyguardSecurityCallback).showCurrentSecurityScreen()
+    }
 }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractorTest.kt
index d968c1b..08cd7ed 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractorTest.kt
@@ -21,14 +21,12 @@
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.authentication.data.repository.FakeAuthenticationRepository
+import com.android.systemui.authentication.shared.model.AuthenticationLockoutModel
 import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
 import com.android.systemui.authentication.shared.model.AuthenticationPatternCoordinate
-import com.android.systemui.authentication.shared.model.AuthenticationThrottlingModel
 import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.scene.SceneTestUtils
 import com.google.common.truth.Truth.assertThat
-import kotlin.time.Duration.Companion.milliseconds
-import kotlin.time.Duration.Companion.seconds
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.test.advanceTimeBy
 import kotlinx.coroutines.test.runCurrent
@@ -78,12 +76,13 @@
     @Test
     fun authenticate_withCorrectPin_succeeds() =
         testScope.runTest {
-            val throttling by collectLastValue(underTest.throttling)
+            val lockout by collectLastValue(underTest.lockout)
             utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
 
             assertThat(underTest.authenticate(FakeAuthenticationRepository.DEFAULT_PIN))
                 .isEqualTo(AuthenticationResult.SUCCEEDED)
-            assertThat(throttling).isNull()
+            assertThat(lockout).isNull()
+            assertThat(utils.authenticationRepository.lockoutStartedReportCount).isEqualTo(0)
         }
 
     @Test
@@ -131,14 +130,15 @@
     @Test
     fun authenticate_withCorrectPassword_succeeds() =
         testScope.runTest {
-            val throttling by collectLastValue(underTest.throttling)
+            val lockout by collectLastValue(underTest.lockout)
             utils.authenticationRepository.setAuthenticationMethod(
                 AuthenticationMethodModel.Password
             )
 
             assertThat(underTest.authenticate("password".toList()))
                 .isEqualTo(AuthenticationResult.SUCCEEDED)
-            assertThat(throttling).isNull()
+            assertThat(lockout).isNull()
+            assertThat(utils.authenticationRepository.lockoutStartedReportCount).isEqualTo(0)
         }
 
     @Test
@@ -187,7 +187,7 @@
     fun tryAutoConfirm_withAutoConfirmPinAndShorterPin_returnsNull() =
         testScope.runTest {
             val isAutoConfirmEnabled by collectLastValue(underTest.isAutoConfirmEnabled)
-            val throttling by collectLastValue(underTest.throttling)
+            val lockout by collectLastValue(underTest.lockout)
             utils.authenticationRepository.apply {
                 setAuthenticationMethod(AuthenticationMethodModel.Pin)
                 setAutoConfirmFeatureEnabled(true)
@@ -203,7 +203,8 @@
                     )
                 )
                 .isEqualTo(AuthenticationResult.SKIPPED)
-            assertThat(throttling).isNull()
+            assertThat(lockout).isNull()
+            assertThat(utils.authenticationRepository.lockoutStartedReportCount).isEqualTo(0)
         }
 
     @Test
@@ -264,7 +265,7 @@
         }
 
     @Test
-    fun tryAutoConfirm_withAutoConfirmCorrectPinButDuringThrottling_returnsNull() =
+    fun tryAutoConfirm_withAutoConfirmCorrectPinButDuringLockout_returnsNull() =
         testScope.runTest {
             val isAutoConfirmEnabled by collectLastValue(underTest.isAutoConfirmEnabled)
             val isUnlocked by collectLastValue(utils.deviceEntryRepository.isUnlocked)
@@ -272,7 +273,7 @@
             utils.authenticationRepository.apply {
                 setAuthenticationMethod(AuthenticationMethodModel.Pin)
                 setAutoConfirmFeatureEnabled(true)
-                setThrottleDuration(42)
+                setLockoutDuration(42)
             }
 
             val authResult =
@@ -315,69 +316,121 @@
         }
 
     @Test
-    fun throttling() =
+    fun isAutoConfirmEnabled_featureDisabled_returnsFalse() =
         testScope.runTest {
-            val throttling by collectLastValue(underTest.throttling)
+            val isAutoConfirmEnabled by collectLastValue(underTest.isAutoConfirmEnabled)
+            utils.authenticationRepository.setAutoConfirmFeatureEnabled(false)
+
+            assertThat(isAutoConfirmEnabled).isFalse()
+        }
+
+    @Test
+    fun isAutoConfirmEnabled_featureEnabled_returnsTrue() =
+        testScope.runTest {
+            val isAutoConfirmEnabled by collectLastValue(underTest.isAutoConfirmEnabled)
+            utils.authenticationRepository.setAutoConfirmFeatureEnabled(true)
+
+            assertThat(isAutoConfirmEnabled).isTrue()
+        }
+
+    @Test
+    fun isAutoConfirmEnabled_featureEnabledButDisabledByLockout() =
+        testScope.runTest {
+            val isAutoConfirmEnabled by collectLastValue(underTest.isAutoConfirmEnabled)
+            val lockout by collectLastValue(underTest.lockout)
+            utils.authenticationRepository.setAutoConfirmFeatureEnabled(true)
+
+            // The feature is enabled.
+            assertThat(isAutoConfirmEnabled).isTrue()
+
+            // Make many wrong attempts to trigger lockout.
+            repeat(FakeAuthenticationRepository.MAX_FAILED_AUTH_TRIES_BEFORE_LOCKOUT) {
+                underTest.authenticate(listOf(5, 6, 7)) // Wrong PIN
+            }
+            assertThat(lockout).isNotNull()
+            assertThat(utils.authenticationRepository.lockoutStartedReportCount).isEqualTo(1)
+
+            // Lockout disabled auto-confirm.
+            assertThat(isAutoConfirmEnabled).isFalse()
+
+            // Move the clock forward one more second, to completely finish the lockout period:
+            advanceTimeBy(FakeAuthenticationRepository.LOCKOUT_DURATION_MS + 1000L)
+            assertThat(lockout).isNull()
+
+            // Auto-confirm is still disabled, because lockout occurred at least once in this
+            // session.
+            assertThat(isAutoConfirmEnabled).isFalse()
+
+            // Correct PIN and unlocks successfully, resetting the 'session'.
+            assertThat(underTest.authenticate(FakeAuthenticationRepository.DEFAULT_PIN))
+                .isEqualTo(AuthenticationResult.SUCCEEDED)
+
+            // Auto-confirm is re-enabled.
+            assertThat(isAutoConfirmEnabled).isTrue()
+
+            assertThat(utils.authenticationRepository.lockoutStartedReportCount).isEqualTo(1)
+        }
+
+    @Test
+    fun lockout() =
+        testScope.runTest {
+            val lockout by collectLastValue(underTest.lockout)
             utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
             underTest.authenticate(FakeAuthenticationRepository.DEFAULT_PIN)
-            assertThat(throttling).isNull()
+            assertThat(lockout).isNull()
 
-            // Make many wrong attempts, but just shy of what's needed to get throttled:
-            repeat(FakeAuthenticationRepository.MAX_FAILED_AUTH_TRIES_BEFORE_THROTTLING - 1) {
+            // Make many wrong attempts, but just shy of what's needed to get locked out:
+            repeat(FakeAuthenticationRepository.MAX_FAILED_AUTH_TRIES_BEFORE_LOCKOUT - 1) {
                 underTest.authenticate(listOf(5, 6, 7)) // Wrong PIN
-                assertThat(throttling).isNull()
+                assertThat(lockout).isNull()
             }
 
-            // Make one more wrong attempt, leading to throttling:
+            // Make one more wrong attempt, leading to lockout:
             underTest.authenticate(listOf(5, 6, 7)) // Wrong PIN
-            assertThat(throttling)
+            assertThat(lockout)
                 .isEqualTo(
-                    AuthenticationThrottlingModel(
+                    AuthenticationLockoutModel(
                         failedAttemptCount =
-                            FakeAuthenticationRepository.MAX_FAILED_AUTH_TRIES_BEFORE_THROTTLING,
-                        remainingMs = FakeAuthenticationRepository.THROTTLE_DURATION_MS,
+                            FakeAuthenticationRepository.MAX_FAILED_AUTH_TRIES_BEFORE_LOCKOUT,
+                        remainingSeconds = FakeAuthenticationRepository.LOCKOUT_DURATION_SECONDS,
                     )
                 )
+            assertThat(utils.authenticationRepository.lockoutStartedReportCount).isEqualTo(1)
 
-            // Correct PIN, but throttled, so doesn't attempt it:
+            // Correct PIN, but locked out, so doesn't attempt it:
             assertThat(underTest.authenticate(FakeAuthenticationRepository.DEFAULT_PIN))
                 .isEqualTo(AuthenticationResult.SKIPPED)
-            assertThat(throttling)
+            assertThat(lockout)
                 .isEqualTo(
-                    AuthenticationThrottlingModel(
+                    AuthenticationLockoutModel(
                         failedAttemptCount =
-                            FakeAuthenticationRepository.MAX_FAILED_AUTH_TRIES_BEFORE_THROTTLING,
-                        remainingMs = FakeAuthenticationRepository.THROTTLE_DURATION_MS,
+                            FakeAuthenticationRepository.MAX_FAILED_AUTH_TRIES_BEFORE_LOCKOUT,
+                        remainingSeconds = FakeAuthenticationRepository.LOCKOUT_DURATION_SECONDS,
                     )
                 )
 
-            // Move the clock forward to ALMOST skip the throttling, leaving one second to go:
-            val throttleTimeoutSec =
-                FakeAuthenticationRepository.THROTTLE_DURATION_MS.milliseconds.inWholeSeconds
-                    .toInt()
-            repeat(throttleTimeoutSec - 1) { time ->
+            // Move the clock forward to ALMOST skip the lockout, leaving one second to go:
+            val lockoutTimeoutSec = FakeAuthenticationRepository.LOCKOUT_DURATION_SECONDS
+            repeat(FakeAuthenticationRepository.LOCKOUT_DURATION_SECONDS - 1) { time ->
                 advanceTimeBy(1000)
-                assertThat(throttling)
+                assertThat(lockout)
                     .isEqualTo(
-                        AuthenticationThrottlingModel(
+                        AuthenticationLockoutModel(
                             failedAttemptCount =
-                                FakeAuthenticationRepository
-                                    .MAX_FAILED_AUTH_TRIES_BEFORE_THROTTLING,
-                            remainingMs =
-                                ((throttleTimeoutSec - (time + 1)).seconds.inWholeMilliseconds)
-                                    .toInt(),
+                                FakeAuthenticationRepository.MAX_FAILED_AUTH_TRIES_BEFORE_LOCKOUT,
+                            remainingSeconds = lockoutTimeoutSec - (time + 1),
                         )
                     )
             }
 
-            // Move the clock forward one more second, to completely finish the throttling period:
+            // Move the clock forward one more second, to completely finish the lockout period:
             advanceTimeBy(1000)
-            assertThat(throttling).isNull()
+            assertThat(lockout).isNull()
 
-            // Correct PIN and no longer throttled so unlocks successfully:
+            // Correct PIN and no longer locked out so unlocks successfully:
             assertThat(underTest.authenticate(FakeAuthenticationRepository.DEFAULT_PIN))
                 .isEqualTo(AuthenticationResult.SUCCEEDED)
-            assertThat(throttling).isNull()
+            assertThat(lockout).isNull()
         }
 
     @Test
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsKeyguardViewLegacyControllerWithCoroutinesTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsKeyguardViewLegacyControllerWithCoroutinesTest.kt
index cbb772f..0ab596c 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsKeyguardViewLegacyControllerWithCoroutinesTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsKeyguardViewLegacyControllerWithCoroutinesTest.kt
@@ -21,6 +21,8 @@
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.keyguard.KeyguardSecurityModel
+import com.android.systemui.biometrics.UdfpsKeyguardViewLegacy.ANIMATE_APPEAR_ON_SCREEN_OFF
+import com.android.systemui.biometrics.UdfpsKeyguardViewLegacy.ANIMATION_BETWEEN_AOD_AND_LOCKSCREEN
 import com.android.systemui.biometrics.data.repository.FakeFingerprintPropertyRepository
 import com.android.systemui.bouncer.data.repository.KeyguardBouncerRepository
 import com.android.systemui.bouncer.data.repository.KeyguardBouncerRepositoryImpl
@@ -56,10 +58,12 @@
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.mockito.ArgumentMatchers.any
+import org.mockito.ArgumentMatchers.anyInt
 import org.mockito.ArgumentMatchers.eq
 import org.mockito.Mock
 import org.mockito.Mockito
 import org.mockito.Mockito.mock
+import org.mockito.Mockito.never
 import org.mockito.Mockito.verify
 import org.mockito.MockitoAnnotations
 
@@ -539,4 +543,77 @@
                 .onDozeAmountChanged(eq(0f), eq(0f), eq(UdfpsKeyguardViewLegacy.ANIMATION_NONE))
             job.cancel()
         }
+
+    @Test
+    fun cancelledLockscreenToAod_dozeAmountNotUpdatedToZero() =
+        testScope.runTest {
+            // GIVEN view is attached
+            mController.onViewAttached()
+            Mockito.reset(mView)
+
+            val job = mController.listenForLockscreenAodTransitions(this)
+            // WHEN lockscreen to aod transition is cancelled
+            transitionRepository.sendTransitionStep(
+                TransitionStep(
+                    from = KeyguardState.LOCKSCREEN,
+                    to = KeyguardState.AOD,
+                    value = 1f,
+                    transitionState = TransitionState.CANCELED
+                )
+            )
+            runCurrent()
+
+            // THEN doze amount is NOT updated to zero
+            verify(mView, never()).onDozeAmountChanged(eq(0f), eq(0f), anyInt())
+            job.cancel()
+        }
+
+    @Test
+    fun dreamingToAod_dozeAmountChanged() =
+        testScope.runTest {
+            // GIVEN view is attached
+            mController.onViewAttached()
+            Mockito.reset(mView)
+
+            val job = mController.listenForDreamingToAodTransitions(this)
+            // WHEN dreaming to aod transition in progress
+            transitionRepository.sendTransitionStep(
+                TransitionStep(
+                    from = KeyguardState.DREAMING,
+                    to = KeyguardState.AOD,
+                    value = .3f,
+                    transitionState = TransitionState.RUNNING
+                )
+            )
+            runCurrent()
+
+            // THEN doze amount is updated to
+            verify(mView).onDozeAmountChanged(eq(.3f), eq(.3f), eq(ANIMATE_APPEAR_ON_SCREEN_OFF))
+            job.cancel()
+        }
+
+    @Test
+    fun alternateBouncerToAod_dozeAmountChanged() =
+        testScope.runTest {
+            // GIVEN view is attached
+            mController.onViewAttached()
+            Mockito.reset(mView)
+
+            val job = mController.listenForAlternateBouncerToAodTransitions(this)
+            // WHEN alternate bouncer to aod transition in progress
+            transitionRepository.sendTransitionStep(
+                TransitionStep(
+                    from = KeyguardState.ALTERNATE_BOUNCER,
+                    to = KeyguardState.AOD,
+                    value = .3f,
+                    transitionState = TransitionState.RUNNING
+                )
+            )
+            runCurrent()
+
+            // THEN doze amount is updated to
+            verify(mView)
+                .onDozeAmountChanged(eq(.3f), eq(.3f), eq(ANIMATION_BETWEEN_AOD_AND_LOCKSCREEN))
+            job.cancel()
+        }
 }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractorTest.kt
index 04f6cd3..9b1df7c 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractorTest.kt
@@ -21,15 +21,14 @@
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.authentication.data.repository.FakeAuthenticationRepository
 import com.android.systemui.authentication.domain.interactor.AuthenticationResult
+import com.android.systemui.authentication.shared.model.AuthenticationLockoutModel
 import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
 import com.android.systemui.authentication.shared.model.AuthenticationPatternCoordinate
-import com.android.systemui.authentication.shared.model.AuthenticationThrottlingModel
 import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.keyguard.domain.interactor.KeyguardFaceAuthInteractor
 import com.android.systemui.res.R
 import com.android.systemui.scene.SceneTestUtils
 import com.google.common.truth.Truth.assertThat
-import kotlin.math.ceil
 import kotlin.time.Duration.Companion.milliseconds
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.test.advanceTimeBy
@@ -247,47 +246,42 @@
         }
 
     @Test
-    fun throttling() =
+    fun lockout() =
         testScope.runTest {
-            val throttling by collectLastValue(underTest.throttling)
+            val lockout by collectLastValue(underTest.lockout)
             val message by collectLastValue(underTest.message)
             utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
-            assertThat(throttling).isNull()
-            repeat(FakeAuthenticationRepository.MAX_FAILED_AUTH_TRIES_BEFORE_THROTTLING) { times ->
+            assertThat(lockout).isNull()
+            repeat(FakeAuthenticationRepository.MAX_FAILED_AUTH_TRIES_BEFORE_LOCKOUT) { times ->
                 // Wrong PIN.
                 assertThat(underTest.authenticate(listOf(6, 7, 8, 9)))
                     .isEqualTo(AuthenticationResult.FAILED)
-                if (
-                    times < FakeAuthenticationRepository.MAX_FAILED_AUTH_TRIES_BEFORE_THROTTLING - 1
-                ) {
+                if (times < FakeAuthenticationRepository.MAX_FAILED_AUTH_TRIES_BEFORE_LOCKOUT - 1) {
                     assertThat(message).isEqualTo(MESSAGE_WRONG_PIN)
                 }
             }
-            assertThat(throttling)
+            assertThat(lockout)
                 .isEqualTo(
-                    AuthenticationThrottlingModel(
+                    AuthenticationLockoutModel(
                         failedAttemptCount =
-                            FakeAuthenticationRepository.MAX_FAILED_AUTH_TRIES_BEFORE_THROTTLING,
-                        remainingMs = FakeAuthenticationRepository.THROTTLE_DURATION_MS,
+                            FakeAuthenticationRepository.MAX_FAILED_AUTH_TRIES_BEFORE_LOCKOUT,
+                        remainingSeconds = FakeAuthenticationRepository.LOCKOUT_DURATION_SECONDS,
                     )
                 )
             assertTryAgainMessage(
                 message,
-                FakeAuthenticationRepository.THROTTLE_DURATION_MS.milliseconds.inWholeSeconds
-                    .toInt()
+                FakeAuthenticationRepository.LOCKOUT_DURATION_MS.milliseconds.inWholeSeconds.toInt()
             )
 
-            // Correct PIN, but throttled, so doesn't change away from the bouncer scene:
+            // Correct PIN, but locked out, so doesn't change away from the bouncer scene:
             assertThat(underTest.authenticate(FakeAuthenticationRepository.DEFAULT_PIN))
                 .isEqualTo(AuthenticationResult.SKIPPED)
             assertTryAgainMessage(
                 message,
-                FakeAuthenticationRepository.THROTTLE_DURATION_MS.milliseconds.inWholeSeconds
-                    .toInt()
+                FakeAuthenticationRepository.LOCKOUT_DURATION_MS.milliseconds.inWholeSeconds.toInt()
             )
 
-            throttling?.remainingMs?.let { remainingMs ->
-                val seconds = ceil(remainingMs / 1000f).toInt()
+            lockout?.remainingSeconds?.let { seconds ->
                 repeat(seconds) { time ->
                     advanceTimeBy(1000)
                     val remainingTimeSec = seconds - time - 1
@@ -297,12 +291,12 @@
                 }
             }
             assertThat(message).isEqualTo("")
-            assertThat(throttling).isNull()
+            assertThat(lockout).isNull()
 
-            // Correct PIN and no longer throttled so changes to the Gone scene:
+            // Correct PIN and no longer locked out so changes to the Gone scene:
             assertThat(underTest.authenticate(FakeAuthenticationRepository.DEFAULT_PIN))
                 .isEqualTo(AuthenticationResult.SUCCEEDED)
-            assertThat(throttling).isNull()
+            assertThat(lockout).isNull()
         }
 
     @Test
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/AuthMethodBouncerViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/AuthMethodBouncerViewModelTest.kt
index 45c186d..2f0843b 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/AuthMethodBouncerViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/AuthMethodBouncerViewModelTest.kt
@@ -35,7 +35,6 @@
 
     private val utils = SceneTestUtils(this)
     private val testScope = utils.testScope
-    private val sceneInteractor = utils.sceneInteractor()
     private val bouncerInteractor =
         utils.bouncerInteractor(
             authenticationInteractor = utils.authenticationInteractor(),
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModelTest.kt
index 75d6a00..16a9359 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModelTest.kt
@@ -26,6 +26,7 @@
 import com.android.systemui.scene.SceneTestUtils
 import com.google.common.truth.Truth.assertThat
 import com.google.common.truth.Truth.assertWithMessage
+import kotlin.time.Duration.Companion.seconds
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.flow.emptyFlow
 import kotlinx.coroutines.flow.flatMapLatest
@@ -134,17 +135,19 @@
     fun message() =
         testScope.runTest {
             val message by collectLastValue(underTest.message)
-            val throttling by collectLastValue(bouncerInteractor.throttling)
+            val lockout by collectLastValue(bouncerInteractor.lockout)
             utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
             assertThat(message?.isUpdateAnimated).isTrue()
 
-            repeat(FakeAuthenticationRepository.MAX_FAILED_AUTH_TRIES_BEFORE_THROTTLING) {
+            repeat(FakeAuthenticationRepository.MAX_FAILED_AUTH_TRIES_BEFORE_LOCKOUT) {
                 // Wrong PIN.
                 bouncerInteractor.authenticate(listOf(3, 4, 5, 6))
             }
             assertThat(message?.isUpdateAnimated).isFalse()
 
-            throttling?.remainingMs?.let { remainingMs -> advanceTimeBy(remainingMs.toLong()) }
+            lockout?.remainingSeconds?.let { remainingSeconds ->
+                advanceTimeBy(remainingSeconds.seconds.inWholeMilliseconds)
+            }
             assertThat(message?.isUpdateAnimated).isTrue()
         }
 
@@ -157,35 +160,37 @@
                         authViewModel?.isInputEnabled ?: emptyFlow()
                     }
                 )
-            val throttling by collectLastValue(bouncerInteractor.throttling)
+            val lockout by collectLastValue(bouncerInteractor.lockout)
             utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
             assertThat(isInputEnabled).isTrue()
 
-            repeat(FakeAuthenticationRepository.MAX_FAILED_AUTH_TRIES_BEFORE_THROTTLING) {
+            repeat(FakeAuthenticationRepository.MAX_FAILED_AUTH_TRIES_BEFORE_LOCKOUT) {
                 // Wrong PIN.
                 bouncerInteractor.authenticate(listOf(3, 4, 5, 6))
             }
             assertThat(isInputEnabled).isFalse()
 
-            throttling?.remainingMs?.let { milliseconds -> advanceTimeBy(milliseconds.toLong()) }
+            lockout?.remainingSeconds?.let { remainingSeconds ->
+                advanceTimeBy(remainingSeconds.seconds.inWholeMilliseconds)
+            }
             assertThat(isInputEnabled).isTrue()
         }
 
     @Test
-    fun throttlingDialogMessage() =
+    fun dialogMessage() =
         testScope.runTest {
-            val throttlingDialogMessage by collectLastValue(underTest.throttlingDialogMessage)
+            val dialogMessage by collectLastValue(underTest.dialogMessage)
             utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
 
-            repeat(FakeAuthenticationRepository.MAX_FAILED_AUTH_TRIES_BEFORE_THROTTLING) {
+            repeat(FakeAuthenticationRepository.MAX_FAILED_AUTH_TRIES_BEFORE_LOCKOUT) {
                 // Wrong PIN.
-                assertThat(throttlingDialogMessage).isNull()
+                assertThat(dialogMessage).isNull()
                 bouncerInteractor.authenticate(listOf(3, 4, 5, 6))
             }
-            assertThat(throttlingDialogMessage).isNotEmpty()
+            assertThat(dialogMessage).isNotEmpty()
 
-            underTest.onThrottlingDialogDismissed()
-            assertThat(throttlingDialogMessage).isNull()
+            underTest.onDialogDismissed()
+            assertThat(dialogMessage).isNull()
         }
 
     @Test
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModelTest.kt
index 64f2946..6d6baa5 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModelTest.kt
@@ -19,8 +19,8 @@
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
+import com.android.systemui.authentication.shared.model.AuthenticationLockoutModel
 import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
-import com.android.systemui.authentication.shared.model.AuthenticationThrottlingModel
 import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.coroutines.collectValues
 import com.android.systemui.res.R
@@ -243,12 +243,12 @@
         }
 
     @Test
-    fun onImeVisibilityChanged_falseAfterTrue_whileThrottling_doesNothing() =
+    fun onImeVisibilityChanged_falseAfterTrue_whileLockedOut_doesNothing() =
         testScope.runTest {
             val events by collectValues(bouncerInteractor.onImeHiddenByUser)
             assertThat(events).isEmpty()
             underTest.onImeVisibilityChanged(isVisible = true)
-            setThrottling(true)
+            setLockout(true)
 
             underTest.onImeVisibilityChanged(isVisible = false)
 
@@ -284,11 +284,11 @@
         }
 
     @Test
-    fun isTextFieldFocusRequested_focusLostWhileThrottling_staysFalse() =
+    fun isTextFieldFocusRequested_focusLostWhileLockedOut_staysFalse() =
         testScope.runTest {
             val isTextFieldFocusRequested by collectLastValue(underTest.isTextFieldFocusRequested)
             underTest.onTextFieldFocusChanged(isFocused = true)
-            setThrottling(true)
+            setLockout(true)
 
             underTest.onTextFieldFocusChanged(isFocused = false)
 
@@ -296,14 +296,14 @@
         }
 
     @Test
-    fun isTextFieldFocusRequested_throttlingCountdownEnds_becomesTrue() =
+    fun isTextFieldFocusRequested_lockoutCountdownEnds_becomesTrue() =
         testScope.runTest {
             val isTextFieldFocusRequested by collectLastValue(underTest.isTextFieldFocusRequested)
             underTest.onTextFieldFocusChanged(isFocused = true)
-            setThrottling(true)
+            setLockout(true)
             underTest.onTextFieldFocusChanged(isFocused = false)
 
-            setThrottling(false)
+            setLockout(false)
 
             assertThat(isTextFieldFocusRequested).isTrue()
         }
@@ -327,24 +327,24 @@
         switchToScene(SceneKey.Bouncer)
     }
 
-    private suspend fun TestScope.setThrottling(
-        isThrottling: Boolean,
+    private suspend fun TestScope.setLockout(
+        isLockedOut: Boolean,
         failedAttemptCount: Int = 5,
     ) {
-        if (isThrottling) {
+        if (isLockedOut) {
             repeat(failedAttemptCount) {
                 authenticationRepository.reportAuthenticationAttempt(false)
             }
-            val remainingTimeMs = 30_000
-            authenticationRepository.setThrottleDuration(remainingTimeMs)
-            authenticationRepository.throttling.value =
-                AuthenticationThrottlingModel(
+            val remainingTimeSeconds = 30
+            authenticationRepository.setLockoutDuration(remainingTimeSeconds * 1000)
+            authenticationRepository.lockout.value =
+                AuthenticationLockoutModel(
                     failedAttemptCount = failedAttemptCount,
-                    remainingMs = remainingTimeMs,
+                    remainingSeconds = remainingTimeSeconds,
                 )
         } else {
             authenticationRepository.reportAuthenticationAttempt(true)
-            authenticationRepository.throttling.value = null
+            authenticationRepository.lockout.value = null
         }
 
         runCurrent()
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModelTest.kt
index 862c39c..8971423 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModelTest.kt
@@ -304,13 +304,12 @@
     fun onDragEnd_whenPatternTooShort() =
         testScope.runTest {
             val message by collectLastValue(bouncerViewModel.message)
-            val throttlingDialogMessage by
-                collectLastValue(bouncerViewModel.throttlingDialogMessage)
+            val dialogMessage by collectLastValue(bouncerViewModel.dialogMessage)
             lockDeviceAndOpenPatternBouncer()
 
             // Enter a pattern that's too short more than enough times that would normally trigger
-            // throttling if the pattern were not too short and wrong:
-            val attempts = FakeAuthenticationRepository.MAX_FAILED_AUTH_TRIES_BEFORE_THROTTLING + 1
+            // lockout if the pattern were not too short and wrong:
+            val attempts = FakeAuthenticationRepository.MAX_FAILED_AUTH_TRIES_BEFORE_LOCKOUT + 1
             repeat(attempts) { attempt ->
                 underTest.onDragStart()
                 CORRECT_PATTERN.subList(
@@ -328,7 +327,7 @@
                 underTest.onDragEnd()
 
                 assertWithMessage("Attempt #$attempt").that(message?.text).isEqualTo(WRONG_PATTERN)
-                assertWithMessage("Attempt #$attempt").that(throttlingDialogMessage).isNull()
+                assertWithMessage("Attempt #$attempt").that(dialogMessage).isNull()
             }
         }
 
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/data/repository/DeviceEntryRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/data/repository/DeviceEntryRepositoryTest.kt
index 97ac8c6..d3049d9 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/data/repository/DeviceEntryRepositoryTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/data/repository/DeviceEntryRepositoryTest.kt
@@ -6,6 +6,7 @@
 import com.android.internal.widget.LockPatternUtils
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
 import com.android.systemui.scene.SceneTestUtils
 import com.android.systemui.statusbar.phone.KeyguardBypassController
 import com.android.systemui.statusbar.policy.KeyguardStateController
@@ -38,6 +39,7 @@
     private val testUtils = SceneTestUtils(this)
     private val testScope = testUtils.testScope
     private val userRepository = FakeUserRepository()
+    private val keyguardRepository = FakeKeyguardRepository()
 
     private lateinit var underTest: DeviceEntryRepository
 
@@ -55,6 +57,7 @@
                 lockPatternUtils = lockPatternUtils,
                 keyguardBypassController = keyguardBypassController,
                 keyguardStateController = keyguardStateController,
+                keyguardRepository = keyguardRepository,
             )
         testScope.runCurrent()
     }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorTest.kt
index bc4bae0..34f703b 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorTest.kt
@@ -49,8 +49,10 @@
 import com.android.systemui.res.R
 import com.android.systemui.settings.UserFileManager
 import com.android.systemui.settings.UserTracker
+import com.android.systemui.shade.domain.interactor.shadeInteractor
 import com.android.systemui.shared.keyguard.shared.model.KeyguardQuickAffordanceSlots
 import com.android.systemui.statusbar.policy.KeyguardStateController
+import com.android.systemui.testKosmos
 import com.android.systemui.util.FakeSharedPreferences
 import com.android.systemui.util.mockito.mock
 import com.android.systemui.util.mockito.whenever
@@ -93,6 +95,8 @@
     private lateinit var dockManager: DockManagerFake
     private lateinit var biometricSettingsRepository: FakeBiometricSettingsRepository
 
+    private val kosmos = testKosmos()
+
     @Before
     fun setUp() {
         MockitoAnnotations.initMocks(this)
@@ -179,6 +183,7 @@
         underTest =
             KeyguardQuickAffordanceInteractor(
                 keyguardInteractor = withDeps.keyguardInteractor,
+                shadeInteractor = kosmos.shadeInteractor,
                 lockPatternUtils = lockPatternUtils,
                 keyguardStateController = keyguardStateController,
                 userTracker = userTracker,
@@ -339,6 +344,31 @@
         }
 
     @Test
+    fun quickAffordanceAlwaysVisible_notVisible_restrictedByPolicyManager() =
+        testScope.runTest {
+            whenever(devicePolicyManager.getKeyguardDisabledFeatures(null, userTracker.userId))
+                .thenReturn(DevicePolicyManager.KEYGUARD_DISABLE_SHORTCUTS_ALL)
+
+            repository.setKeyguardShowing(false)
+            repository.setIsDozing(true)
+            homeControls.setState(
+                KeyguardQuickAffordanceConfig.LockScreenState.Visible(
+                    icon = ICON,
+                    activationState = ActivationState.Active,
+                )
+            )
+
+            val collectedValue by
+                collectLastValue(
+                    underTest.quickAffordanceAlwaysVisible(
+                        KeyguardQuickAffordancePosition.BOTTOM_START
+                    )
+                )
+
+            assertThat(collectedValue).isInstanceOf(KeyguardQuickAffordanceModel.Hidden::class.java)
+        }
+
+    @Test
     fun quickAffordanceAlwaysVisible_evenWhenLockScreenNotShowingAndDozing() =
         testScope.runTest {
             repository.setKeyguardShowing(false)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToDreamingTransitionViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToDreamingTransitionViewModelTest.kt
index 9226c0d..a346e8b 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToDreamingTransitionViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToDreamingTransitionViewModelTest.kt
@@ -24,7 +24,7 @@
 import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.coroutines.collectValues
 import com.android.systemui.flags.Flags
-import com.android.systemui.flags.featureFlagsClassic
+import com.android.systemui.flags.fakeFeatureFlagsClassic
 import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
 import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
 import com.android.systemui.keyguard.shared.model.KeyguardState
@@ -48,7 +48,7 @@
 
     private val kosmos =
         testKosmos().apply {
-            featureFlagsClassic.apply { set(Flags.FULL_SCREEN_USER_SWITCHER, false) }
+            fakeFeatureFlagsClassic.apply { set(Flags.FULL_SCREEN_USER_SWITCHER, false) }
         }
     private val testScope = kosmos.testScope
     private val repository = kosmos.fakeKeyguardTransitionRepository
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToOccludedTransitionViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToOccludedTransitionViewModelTest.kt
index bcad72b..274bde1 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToOccludedTransitionViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToOccludedTransitionViewModelTest.kt
@@ -25,7 +25,7 @@
 import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.coroutines.collectValues
 import com.android.systemui.flags.Flags
-import com.android.systemui.flags.featureFlagsClassic
+import com.android.systemui.flags.fakeFeatureFlagsClassic
 import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
 import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
 import com.android.systemui.keyguard.shared.model.KeyguardState
@@ -49,7 +49,7 @@
 class LockscreenToOccludedTransitionViewModelTest : SysuiTestCase() {
     private val kosmos =
         testKosmos().apply {
-            featureFlagsClassic.apply { set(Flags.FULL_SCREEN_USER_SWITCHER, false) }
+            fakeFeatureFlagsClassic.apply { set(Flags.FULL_SCREEN_USER_SWITCHER, false) }
         }
     private val testScope = kosmos.testScope
     private val repository = kosmos.fakeKeyguardTransitionRepository
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGoneTransitionViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGoneTransitionViewModelTest.kt
index 78d87a6..f027bc8 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGoneTransitionViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGoneTransitionViewModelTest.kt
@@ -22,7 +22,7 @@
 import com.android.systemui.bouncer.domain.interactor.primaryBouncerInteractor
 import com.android.systemui.coroutines.collectValues
 import com.android.systemui.flags.Flags
-import com.android.systemui.flags.featureFlagsClassic
+import com.android.systemui.flags.fakeFeatureFlagsClassic
 import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
 import com.android.systemui.keyguard.shared.model.KeyguardState
 import com.android.systemui.keyguard.shared.model.TransitionState
@@ -43,7 +43,7 @@
 class PrimaryBouncerToGoneTransitionViewModelTest : SysuiTestCase() {
     val kosmos =
         testKosmos().apply {
-            featureFlagsClassic.apply {
+            fakeFeatureFlagsClassic.apply {
                 set(Flags.REFACTOR_KEYGUARD_DISMISS_INTENT, false)
                 set(Flags.FULL_SCREEN_USER_SWITCHER, false)
             }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/flashlight/domain/interactor/FlashlightTileDataInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/flashlight/domain/interactor/FlashlightTileDataInteractorTest.kt
index 00572d3..7f7490d 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/flashlight/domain/interactor/FlashlightTileDataInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/flashlight/domain/interactor/FlashlightTileDataInteractorTest.kt
@@ -14,6 +14,8 @@
  * limitations under the License.
  */
 
+package com.android.systemui.qs.tiles.impl.flashlight.domain.interactor
+
 import android.os.UserHandle
 import android.testing.LeakCheck
 import androidx.test.ext.junit.runners.AndroidJUnit4
@@ -22,7 +24,6 @@
 import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.coroutines.collectValues
 import com.android.systemui.qs.tiles.base.interactor.DataUpdateTrigger
-import com.android.systemui.qs.tiles.impl.flashlight.domain.interactor.FlashlightTileDataInteractor
 import com.android.systemui.qs.tiles.impl.flashlight.domain.model.FlashlightTileModel
 import com.android.systemui.utils.leaks.FakeFlashlightController
 import com.google.common.truth.Truth.assertThat
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/flashlight/domain/interactor/FlashlightTileUserActionInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/flashlight/domain/interactor/FlashlightTileUserActionInteractorTest.kt
index f819f53..28d43b3 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/flashlight/domain/interactor/FlashlightTileUserActionInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/flashlight/domain/interactor/FlashlightTileUserActionInteractorTest.kt
@@ -14,12 +14,13 @@
  * limitations under the License.
  */
 
+package com.android.systemui.qs.tiles.impl.flashlight.domain.interactor
+
 import android.app.ActivityManager
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.qs.tiles.base.interactor.QSTileInputTestKtx.click
-import com.android.systemui.qs.tiles.impl.flashlight.domain.interactor.FlashlightTileUserActionInteractor
 import com.android.systemui.qs.tiles.impl.flashlight.domain.model.FlashlightTileModel
 import com.android.systemui.statusbar.policy.FlashlightController
 import com.android.systemui.util.mockito.mock
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt
index 3cb97e3..61d55f0 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt
@@ -19,6 +19,7 @@
 package com.android.systemui.scene.domain.startable
 
 import android.os.PowerManager
+import android.platform.test.annotations.EnableFlags
 import android.view.Display
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
@@ -45,7 +46,6 @@
 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.Mockito.clearInvocations
@@ -55,6 +55,7 @@
 
 @SmallTest
 @RunWith(AndroidJUnit4::class)
+@EnableFlags(AconfigFlags.FLAG_SCENE_CONTAINER)
 class SceneContainerStartableTest : SysuiTestCase() {
 
     private val utils = SceneTestUtils(this)
@@ -93,11 +94,6 @@
             authenticationInteractor = authenticationInteractor,
         )
 
-    @Before
-    fun setUp() {
-        mSetFlagsRule.enableFlags(AconfigFlags.FLAG_SCENE_CONTAINER)
-    }
-
     @Test
     fun hydrateVisibility() =
         testScope.runTest {
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/NotificationStackAppearanceIntegrationTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/NotificationStackAppearanceIntegrationTest.kt
index f04dfd1..4cdb08a 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/NotificationStackAppearanceIntegrationTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/NotificationStackAppearanceIntegrationTest.kt
@@ -24,7 +24,7 @@
 import com.android.systemui.common.shared.model.NotificationContainerBounds
 import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.flags.Flags
-import com.android.systemui.flags.featureFlagsClassic
+import com.android.systemui.flags.fakeFeatureFlagsClassic
 import com.android.systemui.kosmos.testScope
 import com.android.systemui.scene.domain.interactor.sceneInteractor
 import com.android.systemui.scene.shared.flag.FakeSceneContainerFlags
@@ -51,7 +51,7 @@
     private val kosmos =
         testKosmos().apply {
             sceneContainerFlags = FakeSceneContainerFlags(enabled = true)
-            featureFlagsClassic.apply {
+            fakeFeatureFlagsClassic.apply {
                 set(Flags.FULL_SCREEN_USER_SWITCHER, false)
                 set(Flags.NSSL_DEBUG_LINES, false)
             }
@@ -67,9 +67,24 @@
             val bounds by collectLastValue(appearanceViewModel.stackBounds)
 
             val top = 200f
+            val left = 0f
             val bottom = 550f
-            placeholderViewModel.onBoundsChanged(top, bottom)
-            assertThat(bounds).isEqualTo(NotificationContainerBounds(top = top, bottom = bottom))
+            val right = 100f
+            placeholderViewModel.onBoundsChanged(
+                left = left,
+                top = top,
+                right = right,
+                bottom = bottom
+            )
+            assertThat(bounds)
+                .isEqualTo(
+                    NotificationContainerBounds(
+                        left = left,
+                        top = top,
+                        right = right,
+                        bottom = bottom
+                    )
+                )
         }
 
     @Test
diff --git a/packages/SystemUI/res/drawable/qs_record_issue_icon_off.xml b/packages/SystemUI/res/drawable/qs_record_issue_icon_off.xml
new file mode 100644
index 0000000..bd60431
--- /dev/null
+++ b/packages/SystemUI/res/drawable/qs_record_issue_icon_off.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="24.0dp"
+    android:height="24.0dp"
+    android:viewportWidth="24.0"
+    android:viewportHeight="24.0">
+    <path
+        android:pathData="M5.76 12.89c0 3.28 2.54 5.97 5.76 6.22v-8.26h-5.4c-.23.64-.36 1.33-.36 2.04zm9.27-5.45l1.01-1.59c.19-.29.1-.67-.19-.86-.29-.19-.68-.1-.86.19l-1.12 1.76c-.59-.19-1.22-.29-1.87-.29s-1.28.1-1.87.29L9.01 5.18c-.18-.29-.57-.38-.86-.19-.29.18-.38.57-.19.86l1.01 1.59c-1.02.57-1.86 1.43-2.43 2.45h10.92c-.57-1.02-1.41-1.88-2.43-2.45zm2.85 3.41h-5.4v8.26c3.22-.25 5.76-2.93 5.76-6.22 0-.71-.13-1.4-.36-2.04z"
+        android:fillColor="#000000"/>
+</vector>
diff --git a/packages/SystemUI/res/drawable/qs_record_issue_icon_on.xml b/packages/SystemUI/res/drawable/qs_record_issue_icon_on.xml
new file mode 100644
index 0000000..fadaf78
--- /dev/null
+++ b/packages/SystemUI/res/drawable/qs_record_issue_icon_on.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="24.0dp"
+    android:height="24.0dp"
+    android:viewportWidth="24.0"
+    android:viewportHeight="24.0">
+    <path
+        android:pathData="M5.76 12.89c0 3.28 2.54 5.97 5.76 6.22v-8.26h-5.4c-.23.64-.36 1.33-.36 2.04zm9.27-5.45l1.01-1.59c.19-.29.1-.67-.19-.86-.29-.19-.68-.1-.86.19l-1.12 1.76c-.59-.19-1.22-.29-1.87-.29s-1.28.1-1.87.29L9.01 5.18c-.18-.29-.57-.38-.86-.19-.29.18-.38.57-.19.86l1.01 1.59c-1.02.57-1.86 1.43-2.43 2.45h10.92c-.57-1.02-1.41-1.88-2.43-2.45zm2.85 3.41h-5.4v8.26c3.22-.25 5.76-2.93 5.76-6.22 0-.71-.13-1.4-.36-2.04z"
+        android:fillColor="#FFFFFF"/>
+</vector>
diff --git a/packages/SystemUI/res/layout/keyguard_bottom_area.xml b/packages/SystemUI/res/layout/keyguard_bottom_area.xml
index 66c57fc..6d7ce06 100644
--- a/packages/SystemUI/res/layout/keyguard_bottom_area.xml
+++ b/packages/SystemUI/res/layout/keyguard_bottom_area.xml
@@ -106,5 +106,5 @@
     </FrameLayout>
 
     <include layout="@layout/ambient_indication"
-             android:id="@+id/ambient_indication_container" />
+             android:id="@id/ambient_indication_container" />
 </com.android.systemui.statusbar.phone.KeyguardBottomAreaView>
diff --git a/packages/SystemUI/res/values/ids.xml b/packages/SystemUI/res/values/ids.xml
index 1838795..cf63cc7 100644
--- a/packages/SystemUI/res/values/ids.xml
+++ b/packages/SystemUI/res/values/ids.xml
@@ -223,6 +223,8 @@
     <item type="id" name="lock_icon_bg" />
     <item type="id" name="burn_in_layer" />
     <item type="id" name="communal_tutorial_indicator" />
+    <item type="id" name="nssl_placeholder_barrier_bottom" />
+    <item type="id" name="ambient_indication_container" />
 
     <!-- Privacy dialog -->
     <item type="id" name="privacy_dialog_close_app_button" />
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 7ca0b6e..78b701c 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -824,6 +824,13 @@
     <!-- QuickSettings: Text to prompt the user to stop an ongoing recording [CHAR LIMIT=20] -->
     <string name="quick_settings_screen_record_stop">Stop</string>
 
+    <!-- QuickSettings: Record Issue tile [CHAR LIMIT=NONE] -->
+    <string name="qs_record_issue_label">Record Issue</string>
+    <!-- QuickSettings: Text to prompt the user to begin a new recording [CHAR LIMIT=20] -->
+    <string name="qs_record_issue_start">Start</string>
+    <!-- QuickSettings: Text to prompt the user to stop an ongoing recording [CHAR LIMIT=20] -->
+    <string name="qs_record_issue_stop">Stop</string>
+
     <!-- QuickSettings: Label for the toggle that controls whether One-handed mode is enabled. [CHAR LIMIT=NONE] -->
     <string name="quick_settings_onehanded_label">One-handed mode</string>
 
diff --git a/packages/SystemUI/shared/src/com/android/systemui/flags/Flag.kt b/packages/SystemUI/shared/src/com/android/systemui/flags/Flag.kt
index f1a4007..e27a328 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/flags/Flag.kt
+++ b/packages/SystemUI/shared/src/com/android/systemui/flags/Flag.kt
@@ -119,9 +119,8 @@
 data class ReleasedFlag constructor(
     override val name: String,
     override val namespace: String,
-    override val teamfood: Boolean = false,
     override val overridden: Boolean = false
-) : BooleanFlag(name, namespace, true, teamfood, overridden)
+) : BooleanFlag(name, namespace, true, teamfood = false, overridden)
 
 /**
  * A Flag that reads its default values from a resource overlay instead of code.
@@ -132,8 +131,9 @@
     override val name: String,
     override val namespace: String,
     @BoolRes override val resourceId: Int,
+) : ResourceFlag<Boolean> {
     override val teamfood: Boolean = false
-) : ResourceFlag<Boolean>
+}
 
 /**
  * A Flag that can reads its overrides from System Properties.
@@ -147,7 +147,6 @@
     override val namespace: String,
     override val default: Boolean = false,
 ) : SysPropFlag<Boolean> {
-    // TODO(b/268520433): Teamfood not supported for sysprop flags yet.
     override val teamfood: Boolean = false
 }
 
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/InteractionJankMonitorWrapper.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/InteractionJankMonitorWrapper.java
index c505bd5..df7182b 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/InteractionJankMonitorWrapper.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/InteractionJankMonitorWrapper.java
@@ -21,6 +21,7 @@
 import android.text.TextUtils;
 import android.view.View;
 
+import com.android.internal.jank.Cuj;
 import com.android.internal.jank.InteractionJankMonitor;
 import com.android.internal.jank.InteractionJankMonitor.Configuration;
 
@@ -29,40 +30,25 @@
 
 public final class InteractionJankMonitorWrapper {
     // Launcher journeys.
-    public static final int CUJ_APP_LAUNCH_FROM_RECENTS =
-            InteractionJankMonitor.CUJ_LAUNCHER_APP_LAUNCH_FROM_RECENTS;
-    public static final int CUJ_APP_LAUNCH_FROM_ICON =
-            InteractionJankMonitor.CUJ_LAUNCHER_APP_LAUNCH_FROM_ICON;
-    public static final int CUJ_APP_CLOSE_TO_HOME =
-            InteractionJankMonitor.CUJ_LAUNCHER_APP_CLOSE_TO_HOME;
+    public static final int CUJ_APP_LAUNCH_FROM_RECENTS = Cuj.CUJ_LAUNCHER_APP_LAUNCH_FROM_RECENTS;
+    public static final int CUJ_APP_LAUNCH_FROM_ICON = Cuj.CUJ_LAUNCHER_APP_LAUNCH_FROM_ICON;
+    public static final int CUJ_APP_CLOSE_TO_HOME = Cuj.CUJ_LAUNCHER_APP_CLOSE_TO_HOME;
     public static final int CUJ_APP_CLOSE_TO_HOME_FALLBACK =
-            InteractionJankMonitor.CUJ_LAUNCHER_APP_CLOSE_TO_HOME_FALLBACK;
-    public static final int CUJ_APP_CLOSE_TO_PIP =
-            InteractionJankMonitor.CUJ_LAUNCHER_APP_CLOSE_TO_PIP;
-    public static final int CUJ_QUICK_SWITCH =
-            InteractionJankMonitor.CUJ_LAUNCHER_QUICK_SWITCH;
-    public static final int CUJ_OPEN_ALL_APPS =
-            InteractionJankMonitor.CUJ_LAUNCHER_OPEN_ALL_APPS;
-    public static final int CUJ_CLOSE_ALL_APPS_SWIPE =
-            InteractionJankMonitor.CUJ_LAUNCHER_CLOSE_ALL_APPS_SWIPE;
-    public static final int CUJ_CLOSE_ALL_APPS_TO_HOME =
-            InteractionJankMonitor.CUJ_LAUNCHER_CLOSE_ALL_APPS_TO_HOME;
-    public static final int CUJ_ALL_APPS_SCROLL =
-            InteractionJankMonitor.CUJ_LAUNCHER_ALL_APPS_SCROLL;
-    public static final int CUJ_APP_LAUNCH_FROM_WIDGET =
-            InteractionJankMonitor.CUJ_LAUNCHER_APP_LAUNCH_FROM_WIDGET;
-    public static final int CUJ_SPLIT_SCREEN_ENTER =
-            InteractionJankMonitor.CUJ_SPLIT_SCREEN_ENTER;
+            Cuj.CUJ_LAUNCHER_APP_CLOSE_TO_HOME_FALLBACK;
+    public static final int CUJ_APP_CLOSE_TO_PIP = Cuj.CUJ_LAUNCHER_APP_CLOSE_TO_PIP;
+    public static final int CUJ_QUICK_SWITCH = Cuj.CUJ_LAUNCHER_QUICK_SWITCH;
+    public static final int CUJ_OPEN_ALL_APPS = Cuj.CUJ_LAUNCHER_OPEN_ALL_APPS;
+    public static final int CUJ_CLOSE_ALL_APPS_SWIPE = Cuj.CUJ_LAUNCHER_CLOSE_ALL_APPS_SWIPE;
+    public static final int CUJ_CLOSE_ALL_APPS_TO_HOME = Cuj.CUJ_LAUNCHER_CLOSE_ALL_APPS_TO_HOME;
+    public static final int CUJ_ALL_APPS_SCROLL = Cuj.CUJ_LAUNCHER_ALL_APPS_SCROLL;
+    public static final int CUJ_APP_LAUNCH_FROM_WIDGET = Cuj.CUJ_LAUNCHER_APP_LAUNCH_FROM_WIDGET;
+    public static final int CUJ_SPLIT_SCREEN_ENTER = Cuj.CUJ_SPLIT_SCREEN_ENTER;
     public static final int CUJ_LAUNCHER_UNLOCK_ENTRANCE_ANIMATION =
-            InteractionJankMonitor.CUJ_LAUNCHER_UNLOCK_ENTRANCE_ANIMATION;
-    public static final int CUJ_RECENTS_SCROLLING =
-            InteractionJankMonitor.CUJ_RECENTS_SCROLLING;
-    public static final int CUJ_APP_SWIPE_TO_RECENTS =
-            InteractionJankMonitor.CUJ_LAUNCHER_APP_SWIPE_TO_RECENTS;
-    public static final int CUJ_OPEN_SEARCH_RESULT =
-            InteractionJankMonitor.CUJ_LAUNCHER_OPEN_SEARCH_RESULT;
-    public static final int CUJ_LAUNCHER_UNFOLD_ANIM =
-            InteractionJankMonitor.CUJ_LAUNCHER_UNFOLD_ANIM;
+            Cuj.CUJ_LAUNCHER_UNLOCK_ENTRANCE_ANIMATION;
+    public static final int CUJ_RECENTS_SCROLLING = Cuj.CUJ_RECENTS_SCROLLING;
+    public static final int CUJ_APP_SWIPE_TO_RECENTS = Cuj.CUJ_LAUNCHER_APP_SWIPE_TO_RECENTS;
+    public static final int CUJ_OPEN_SEARCH_RESULT = Cuj.CUJ_LAUNCHER_OPEN_SEARCH_RESULT;
+    public static final int CUJ_LAUNCHER_UNFOLD_ANIM = Cuj.CUJ_LAUNCHER_UNFOLD_ANIM;
 
     @IntDef({
             CUJ_APP_LAUNCH_FROM_RECENTS,
@@ -89,7 +75,7 @@
      * Begin a trace session.
      *
      * @param v       an attached view.
-     * @param cujType the specific {@link InteractionJankMonitor.CujType}.
+     * @param cujType the specific {@link Cuj.CujType}.
      */
     public static void begin(View v, @CujType int cujType) {
         if (Build.VERSION.SDK_INT < Build.VERSION_CODES.S) return;
@@ -100,7 +86,7 @@
      * Begin a trace session.
      *
      * @param v       an attached view.
-     * @param cujType the specific {@link InteractionJankMonitor.CujType}.
+     * @param cujType the specific {@link Cuj.CujType}.
      * @param timeout duration to cancel the instrumentation in ms
      */
     public static void begin(View v, @CujType int cujType, long timeout) {
@@ -115,7 +101,7 @@
      * Begin a trace session.
      *
      * @param v       an attached view.
-     * @param cujType the specific {@link InteractionJankMonitor.CujType}.
+     * @param cujType the specific {@link Cuj.CujType}.
      * @param tag the tag to distinguish different flow of same type CUJ.
      */
     public static void begin(View v, @CujType int cujType, String tag) {
@@ -131,7 +117,7 @@
     /**
      * End a trace session.
      *
-     * @param cujType the specific {@link InteractionJankMonitor.CujType}.
+     * @param cujType the specific {@link Cuj.CujType}.
      */
     public static void end(@CujType int cujType) {
         if (Build.VERSION.SDK_INT < Build.VERSION_CODES.S) return;
diff --git a/packages/SystemUI/src-debug/com/android/systemui/flags/FlagsFactory.kt b/packages/SystemUI/src-debug/com/android/systemui/flags/FlagsFactory.kt
index aef8371..f9fe67a 100644
--- a/packages/SystemUI/src-debug/com/android/systemui/flags/FlagsFactory.kt
+++ b/packages/SystemUI/src-debug/com/android/systemui/flags/FlagsFactory.kt
@@ -42,7 +42,7 @@
         name: String,
         namespace: String = "systemui",
     ): ReleasedFlag {
-        val flag = ReleasedFlag(name = name, namespace = namespace, teamfood = false)
+        val flag = ReleasedFlag(name = name, namespace = namespace)
         checkForDupesAndAdd(flag)
         return flag
     }
@@ -57,7 +57,6 @@
                 name = name,
                 namespace = namespace,
                 resourceId = resourceId,
-                teamfood = false,
             )
         checkForDupesAndAdd(flag)
         return flag
diff --git a/packages/SystemUI/src-release/com/android/systemui/flags/FlagsFactory.kt b/packages/SystemUI/src-release/com/android/systemui/flags/FlagsFactory.kt
index f4b4296..aedf0ce 100644
--- a/packages/SystemUI/src-release/com/android/systemui/flags/FlagsFactory.kt
+++ b/packages/SystemUI/src-release/com/android/systemui/flags/FlagsFactory.kt
@@ -42,7 +42,7 @@
         name: String,
         namespace: String = "systemui",
     ): ReleasedFlag {
-        val flag = ReleasedFlag(name = name, namespace = namespace, teamfood = false)
+        val flag = ReleasedFlag(name = name, namespace = namespace)
         flagMap[name] = flag
         return flag
     }
@@ -57,7 +57,6 @@
                 name = name,
                 namespace = namespace,
                 resourceId = resourceId,
-                teamfood = false,
             )
         flagMap[name] = flag
         return flag
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java
index be2c65f..cdd7b80 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java
@@ -72,7 +72,7 @@
 import com.android.systemui.statusbar.phone.NotificationIconAreaController;
 import com.android.systemui.statusbar.phone.NotificationIconContainer;
 import com.android.systemui.statusbar.phone.ScreenOffAnimationController;
-import com.android.systemui.statusbar.policy.ConfigurationController;
+import com.android.systemui.statusbar.ui.SystemBarUtilsState;
 import com.android.systemui.util.ViewController;
 import com.android.systemui.util.concurrency.DelayableExecutor;
 import com.android.systemui.util.settings.SecureSettings;
@@ -105,7 +105,7 @@
     private final NotificationIconContainerAlwaysOnDisplayViewModel mAodIconsViewModel;
     private final KeyguardRootViewModel mKeyguardRootViewModel;
     private final ConfigurationState mConfigurationState;
-    private final ConfigurationController mConfigurationController;
+    private final SystemBarUtilsState mSystemBarUtilsState;
     private final DozeParameters mDozeParameters;
     private final ScreenOffAnimationController mScreenOffAnimationController;
     private final AlwaysOnDisplayNotificationIconViewStore mAodIconViewStore;
@@ -183,7 +183,7 @@
             KeyguardSliceViewController keyguardSliceViewController,
             NotificationIconAreaController notificationIconAreaController,
             LockscreenSmartspaceController smartspaceController,
-            ConfigurationController configurationController,
+            SystemBarUtilsState systemBarUtilsState,
             ScreenOffAnimationController screenOffAnimationController,
             StatusBarIconViewBindingFailureTracker iconViewBindingFailureTracker,
             KeyguardUnlockAnimationController keyguardUnlockAnimationController,
@@ -208,7 +208,7 @@
         mKeyguardSliceViewController = keyguardSliceViewController;
         mNotificationIconAreaController = notificationIconAreaController;
         mSmartspaceController = smartspaceController;
-        mConfigurationController = configurationController;
+        mSystemBarUtilsState = systemBarUtilsState;
         mScreenOffAnimationController = screenOffAnimationController;
         mIconViewBindingFailureTracker = iconViewBindingFailureTracker;
         mSecureSettings = secureSettings;
@@ -619,13 +619,14 @@
                     mAodIconsBindHandle.dispose();
                 }
                 if (nic != null) {
-                    final DisposableHandle viewHandle = NotificationIconContainerViewBinder.bind(
-                            nic,
-                            mAodIconsViewModel,
-                            mConfigurationState,
-                            mConfigurationController,
-                            mIconViewBindingFailureTracker,
-                            mAodIconViewStore);
+                    final DisposableHandle viewHandle =
+                            NotificationIconContainerViewBinder.bindWhileAttached(
+                                    nic,
+                                    mAodIconsViewModel,
+                                    mConfigurationState,
+                                    mSystemBarUtilsState,
+                                    mIconViewBindingFailureTracker,
+                                    mAodIconViewStore);
                     final DisposableHandle visHandle = KeyguardRootViewBinder.bindAodIconVisibility(
                             nic,
                             mKeyguardRootViewModel.isNotifIconContainerVisible(),
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityCallback.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityCallback.java
index 38a8cd3..c4aa7a2 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityCallback.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityCallback.java
@@ -104,4 +104,14 @@
      */
     default void onSecurityModeChanged(SecurityMode securityMode, boolean needsInput) {
     }
+
+    /**
+     * Shows the security screen that should be shown.
+     *
+     * This can be considered as a "refresh" of the bouncer view. Based on certain parameters,
+     * we might switch to a different bouncer screen. e.g. SimPin to SimPuk.
+     */
+    default void showCurrentSecurityScreen() {
+
+    }
 }
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
index f706301..0a4378e 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
@@ -27,6 +27,8 @@
 import static com.android.keyguard.KeyguardSecurityContainer.USER_TYPE_PRIMARY;
 import static com.android.keyguard.KeyguardSecurityContainer.USER_TYPE_SECONDARY_USER;
 import static com.android.keyguard.KeyguardSecurityContainer.USER_TYPE_WORK_PROFILE;
+import static com.android.keyguard.KeyguardSecurityModel.SecurityMode.SimPin;
+import static com.android.keyguard.KeyguardSecurityModel.SecurityMode.SimPuk;
 import static com.android.systemui.DejankUtils.whitelistIpcs;
 import static com.android.systemui.flags.Flags.LOCKSCREEN_ENABLE_LANDSCAPE;
 import static com.android.systemui.flags.Flags.REVAMPED_BOUNCER_MESSAGES;
@@ -99,6 +101,7 @@
 import dagger.Lazy;
 
 import java.io.File;
+import java.util.Arrays;
 import java.util.Optional;
 
 import javax.inject.Inject;
@@ -164,8 +167,8 @@
                     }
                     mCurrentUser = mSelectedUserInteractor.getSelectedUserId();
                     showPrimarySecurityScreen(false);
-                    if (mCurrentSecurityMode != SecurityMode.SimPin
-                            && mCurrentSecurityMode != SecurityMode.SimPuk) {
+                    if (mCurrentSecurityMode != SimPin
+                            && mCurrentSecurityMode != SimPuk) {
                         reinflateViewFlipper((l) -> {
                         });
                     }
@@ -334,6 +337,11 @@
         public void onSecurityModeChanged(SecurityMode securityMode, boolean needsInput) {
             mViewMediatorCallback.setNeedsInput(needsInput);
         }
+
+        @Override
+        public void showCurrentSecurityScreen() {
+            showPrimarySecurityScreen(false);
+        }
     };
 
     private final SwipeListener mSwipeListener = new SwipeListener() {
@@ -888,7 +896,8 @@
                         finish = true;
                         eventSubtype = BOUNCER_DISMISS_SIM;
                         uiEvent = BouncerUiEvent.BOUNCER_DISMISS_SIM;
-                    } else {
+                    } else if (Arrays.asList(SimPin, SimPuk).contains(securityMode)) {
+                        // There are additional screens to the sim pin/puk flow.
                         showSecurityScreen(securityMode);
                     }
                     break;
@@ -1095,8 +1104,8 @@
     }
 
     private void configureMode() {
-        boolean useSimSecurity = mCurrentSecurityMode == SecurityMode.SimPin
-                || mCurrentSecurityMode == SecurityMode.SimPuk;
+        boolean useSimSecurity = mCurrentSecurityMode == SimPin
+                || mCurrentSecurityMode == SimPuk;
         int mode = KeyguardSecurityContainer.MODE_DEFAULT;
         if (canDisplayUserSwitcher() && !useSimSecurity) {
             mode = KeyguardSecurityContainer.MODE_USER_SWITCHER;
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSimPinViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSimPinViewController.java
index 6e24208..c5e7070 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSimPinViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSimPinViewController.java
@@ -16,6 +16,8 @@
 
 package com.android.keyguard;
 
+import static android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID;
+
 import static com.android.systemui.util.PluralMessageFormaterKt.icuMessageFormat;
 
 import android.annotation.NonNull;
@@ -60,7 +62,7 @@
     // When this is true and when SIM card is PIN locked state, on PIN lock screen, message would
     // be displayed to inform user about the number of remaining PIN attempts left.
     private boolean mShowDefaultMessage;
-    private int mSubId = SubscriptionManager.INVALID_SUBSCRIPTION_ID;
+    private int mSubId = INVALID_SUBSCRIPTION_ID;
     private AlertDialog mRemainingAttemptsDialog;
     private ImageView mSimImageView;
 
@@ -68,6 +70,12 @@
         @Override
         public void onSimStateChanged(int subId, int slotId, int simState) {
             if (DEBUG) Log.v(TAG, "onSimStateChanged(subId=" + subId + ",state=" + simState + ")");
+            // If subId has gone to PUK required then we need to go to the PUK screen.
+            if (subId == mSubId && simState == TelephonyManager.SIM_STATE_PUK_REQUIRED) {
+                getKeyguardSecurityCallback().showCurrentSecurityScreen();
+                return;
+            }
+
             if (simState == TelephonyManager.SIM_STATE_READY) {
                 mRemainingAttempts = -1;
                 resetState();
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
index 37bd9b2..9c61a8a 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
@@ -1277,6 +1277,17 @@
 
     private final FaceAuthenticationListener mFaceAuthenticationListener =
             new FaceAuthenticationListener() {
+                public void onAuthenticatedChanged(boolean isAuthenticated) {
+                    if (!isAuthenticated) {
+                        for (int i = 0; i < mCallbacks.size(); i++) {
+                            KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get();
+                            if (cb != null) {
+                                cb.onFacesCleared();
+                            }
+                        }
+                    }
+                }
+
                 @Override
                 public void onAuthEnrollmentStateChanged(boolean enrolled) {
                     notifyAboutEnrollmentChange(TYPE_FACE);
@@ -1961,7 +1972,7 @@
 
     protected void handleStartedGoingToSleep(int arg1) {
         Assert.isMainThread();
-        clearBiometricRecognized();
+        clearFingerprintRecognized();
         for (int i = 0; i < mCallbacks.size(); i++) {
             KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get();
             if (cb != null) {
@@ -3010,7 +3021,7 @@
     void handleUserSwitching(int userId, Runnable resultCallback) {
         mLogger.logUserSwitching(userId, "from UserTracker");
         Assert.isMainThread();
-        clearBiometricRecognized();
+        clearFingerprintRecognized();
         boolean trustUsuallyManaged = mTrustManager.isTrustUsuallyManaged(userId);
         mLogger.logTrustUsuallyManagedUpdated(userId, mUserTrustIsUsuallyManaged.get(userId),
                 trustUsuallyManaged, "userSwitching");
@@ -3560,25 +3571,30 @@
         return mServiceStates.get(subId);
     }
 
-    public void clearBiometricRecognized() {
-        clearBiometricRecognized(UserHandle.USER_NULL);
+    /**
+     * Resets the fingerprint authenticated state to false.
+     */
+    public void clearFingerprintRecognized() {
+        clearFingerprintRecognized(UserHandle.USER_NULL);
     }
 
-    public void clearBiometricRecognizedWhenKeyguardDone(int unlockedUser) {
-        clearBiometricRecognized(unlockedUser);
+    /**
+     * Resets the fingerprint authenticated state to false.
+     */
+    public void clearFingerprintRecognizedWhenKeyguardDone(int unlockedUser) {
+        clearFingerprintRecognized(unlockedUser);
     }
 
-    private void clearBiometricRecognized(int unlockedUser) {
+    private void clearFingerprintRecognized(int unlockedUser) {
         Assert.isMainThread();
         mUserFingerprintAuthenticated.clear();
         mTrustManager.clearAllBiometricRecognized(FINGERPRINT, unlockedUser);
-        mTrustManager.clearAllBiometricRecognized(FACE, unlockedUser);
-        mLogger.d("clearBiometricRecognized");
+        mLogger.d("clearFingerprintRecognized");
 
         for (int i = 0; i < mCallbacks.size(); i++) {
             KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get();
             if (cb != null) {
-                cb.onBiometricsCleared();
+                cb.onFingerprintsCleared();
             }
         }
     }
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java
index 02dd331..9d216dce 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java
@@ -291,9 +291,14 @@
     public void onLogoutEnabledChanged() { }
 
     /**
-     * Called when authenticated biometrics are cleared.
+     * Called when authenticated fingerprint biometrics are cleared.
      */
-    public void onBiometricsCleared() { }
+    public void onFingerprintsCleared() { }
+
+    /**
+     * Called when authenticated face biometrics have cleared.
+     */
+    public void onFacesCleared() { }
 
     /**
      * Called when the secondary lock screen requirement changes.
diff --git a/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java b/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java
index bf44517..c3f6480 100644
--- a/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java
+++ b/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java
@@ -110,6 +110,10 @@
         View.setTracedRequestLayoutClassClass(
                 SystemProperties.get("persist.debug.trace_request_layout_class", null));
 
+        if (Flags.enableLayoutTracing()) {
+            View.setTraceLayoutSteps(true);
+        }
+
         if (Process.myUserHandle().equals(UserHandle.SYSTEM)) {
             IntentFilter bootCompletedFilter = new
                     IntentFilter(Intent.ACTION_LOCKED_BOOT_COMPLETED);
diff --git a/packages/SystemUI/src/com/android/systemui/authentication/data/repository/AuthenticationRepository.kt b/packages/SystemUI/src/com/android/systemui/authentication/data/repository/AuthenticationRepository.kt
index dd4ca92..fda23b7f 100644
--- a/packages/SystemUI/src/com/android/systemui/authentication/data/repository/AuthenticationRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/authentication/data/repository/AuthenticationRepository.kt
@@ -24,9 +24,9 @@
 import com.android.internal.widget.LockPatternUtils
 import com.android.internal.widget.LockscreenCredential
 import com.android.keyguard.KeyguardSecurityModel
+import com.android.systemui.authentication.shared.model.AuthenticationLockoutModel
 import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
 import com.android.systemui.authentication.shared.model.AuthenticationResultModel
-import com.android.systemui.authentication.shared.model.AuthenticationThrottlingModel
 import com.android.systemui.broadcast.BroadcastDispatcher
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
@@ -60,14 +60,6 @@
 /** Defines interface for classes that can access authentication-related application state. */
 interface AuthenticationRepository {
     /**
-     * Whether the auto confirm feature is enabled for the currently-selected user.
-     *
-     * Note that the length of the PIN is also important to take into consideration, please see
-     * [hintedPinLength].
-     */
-    val isAutoConfirmFeatureEnabled: StateFlow<Boolean>
-
-    /**
      * Emits the result whenever a PIN/Pattern/Password security challenge is attempted by the user
      * in order to unlock the device.
      */
@@ -88,10 +80,22 @@
     val isPatternVisible: StateFlow<Boolean>
 
     /**
-     * The current authentication throttling state, set when the user has to wait before being able
-     * to try another authentication attempt. `null` indicates throttling isn't active.
+     * The current authentication lockout (aka "throttling") state, set when the user has to wait
+     * before being able to try another authentication attempt. `null` indicates throttling isn't
+     * active.
      */
-    val throttling: MutableStateFlow<AuthenticationThrottlingModel?>
+    val lockout: MutableStateFlow<AuthenticationLockoutModel?>
+
+    /** Whether throttling has occurred at least once since the last successful authentication. */
+    val hasLockoutOccurred: MutableStateFlow<Boolean>
+
+    /**
+     * Whether the auto confirm feature is enabled for the currently-selected user.
+     *
+     * Note that the length of the PIN is also important to take into consideration, please see
+     * [hintedPinLength].
+     */
+    val isAutoConfirmFeatureEnabled: StateFlow<Boolean>
 
     /**
      * The currently-configured authentication method. This determines how the authentication
@@ -135,22 +139,25 @@
     /** Reports an authentication attempt. */
     suspend fun reportAuthenticationAttempt(isSuccessful: Boolean)
 
+    /** Reports that the user has entered a temporary device lockout (throttling). */
+    suspend fun reportLockoutStarted(durationMs: Int)
+
     /** Returns the current number of failed authentication attempts. */
     suspend fun getFailedAuthenticationAttemptCount(): Int
 
     /**
-     * Returns the timestamp for when the current throttling will end, allowing the user to attempt
+     * Returns the timestamp for when the current lockout will end, allowing the user to attempt
      * authentication again.
      *
      * Note that this is in milliseconds and it matches [SystemClock.elapsedRealtime].
      */
-    suspend fun getThrottlingEndTimestamp(): Long
+    suspend fun getLockoutEndTimestamp(): Long
 
     /**
-     * Sets the throttling timeout duration (time during which the user should not be allowed to
+     * Sets the lockout timeout duration (time during which the user should not be allowed to
      * attempt authentication).
      */
-    suspend fun setThrottleDuration(durationMs: Int)
+    suspend fun setLockoutDuration(durationMs: Int)
 
     /**
      * Checks the given [LockscreenCredential] to see if it's correct, returning an
@@ -172,11 +179,6 @@
     mobileConnectionsRepository: MobileConnectionsRepository,
 ) : AuthenticationRepository {
 
-    override val isAutoConfirmFeatureEnabled: StateFlow<Boolean> =
-        refreshingFlow(
-            initialValue = false,
-            getFreshValue = lockPatternUtils::isAutoPinConfirmEnabled,
-        )
     override val authenticationChallengeResult = MutableSharedFlow<Boolean>()
 
     override val hintedPinLength: Int = 6
@@ -187,11 +189,15 @@
             getFreshValue = lockPatternUtils::isVisiblePatternEnabled,
         )
 
-    override val throttling: MutableStateFlow<AuthenticationThrottlingModel?> =
-        MutableStateFlow(null)
+    override val lockout: MutableStateFlow<AuthenticationLockoutModel?> = MutableStateFlow(null)
 
-    private val selectedUserId: Int
-        get() = userRepository.getSelectedUserInfo().id
+    override val hasLockoutOccurred: MutableStateFlow<Boolean> = MutableStateFlow(false)
+
+    override val isAutoConfirmFeatureEnabled: StateFlow<Boolean> =
+        refreshingFlow(
+            initialValue = false,
+            getFreshValue = lockPatternUtils::isAutoPinConfirmEnabled,
+        )
 
     override val authenticationMethod: Flow<AuthenticationMethodModel> =
         combine(userRepository.selectedUserInfo, mobileConnectionsRepository.isAnySimSecure) {
@@ -249,19 +255,25 @@
         }
     }
 
+    override suspend fun reportLockoutStarted(durationMs: Int) {
+        return withContext(backgroundDispatcher) {
+            lockPatternUtils.reportPasswordLockout(durationMs, selectedUserId)
+        }
+    }
+
     override suspend fun getFailedAuthenticationAttemptCount(): Int {
         return withContext(backgroundDispatcher) {
             lockPatternUtils.getCurrentFailedPasswordAttempts(selectedUserId)
         }
     }
 
-    override suspend fun getThrottlingEndTimestamp(): Long {
+    override suspend fun getLockoutEndTimestamp(): Long {
         return withContext(backgroundDispatcher) {
             lockPatternUtils.getLockoutAttemptDeadline(selectedUserId)
         }
     }
 
-    override suspend fun setThrottleDuration(durationMs: Int) {
+    override suspend fun setLockoutDuration(durationMs: Int) {
         withContext(backgroundDispatcher) {
             lockPatternUtils.setLockoutAttemptDeadline(selectedUserId, durationMs)
         }
@@ -273,13 +285,16 @@
         return withContext(backgroundDispatcher) {
             try {
                 val matched = lockPatternUtils.checkCredential(credential, selectedUserId) {}
-                AuthenticationResultModel(isSuccessful = matched, throttleDurationMs = 0)
+                AuthenticationResultModel(isSuccessful = matched, lockoutDurationMs = 0)
             } catch (ex: LockPatternUtils.RequestThrottledException) {
-                AuthenticationResultModel(isSuccessful = false, throttleDurationMs = ex.timeoutMs)
+                AuthenticationResultModel(isSuccessful = false, lockoutDurationMs = ex.timeoutMs)
             }
         }
     }
 
+    private val selectedUserId: Int
+        get() = userRepository.getSelectedUserInfo().id
+
     /**
      * Returns a [StateFlow] that's automatically kept fresh. The passed-in [getFreshValue] is
      * invoked on a background thread every time the selected user is changed and every time a new
diff --git a/packages/SystemUI/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractor.kt b/packages/SystemUI/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractor.kt
index 1ba0220..797154e 100644
--- a/packages/SystemUI/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractor.kt
@@ -20,15 +20,16 @@
 import com.android.internal.widget.LockPatternView
 import com.android.internal.widget.LockscreenCredential
 import com.android.systemui.authentication.data.repository.AuthenticationRepository
+import com.android.systemui.authentication.shared.model.AuthenticationLockoutModel
 import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
 import com.android.systemui.authentication.shared.model.AuthenticationPatternCoordinate
-import com.android.systemui.authentication.shared.model.AuthenticationThrottlingModel
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.dagger.qualifiers.Background
 import com.android.systemui.user.data.repository.UserRepository
 import com.android.systemui.util.time.SystemClock
 import javax.inject.Inject
+import kotlin.math.ceil
 import kotlin.math.max
 import kotlin.time.Duration.Companion.seconds
 import kotlinx.coroutines.CoroutineDispatcher
@@ -84,25 +85,25 @@
     val authenticationMethod: Flow<AuthenticationMethodModel> = repository.authenticationMethod
 
     /**
-     * The current authentication throttling state, set when the user has to wait before being able
-     * to try another authentication attempt. `null` indicates throttling isn't active.
+     * The current authentication lockout (aka "throttling") state, set when the user has to wait
+     * before being able to try another authentication attempt. `null` indicates lockout isn't
+     * active.
      */
-    val throttling: StateFlow<AuthenticationThrottlingModel?> = repository.throttling
+    val lockout: StateFlow<AuthenticationLockoutModel?> = repository.lockout
 
     /**
      * Whether the auto confirm feature is enabled for the currently-selected user.
      *
      * Note that the length of the PIN is also important to take into consideration, please see
      * [hintedPinLength].
-     *
-     * During throttling, this is always disabled (`false`).
      */
     val isAutoConfirmEnabled: StateFlow<Boolean> =
-        combine(repository.isAutoConfirmFeatureEnabled, repository.throttling) {
+        combine(repository.isAutoConfirmFeatureEnabled, repository.hasLockoutOccurred) {
                 featureEnabled,
-                throttling ->
-                // Disable auto-confirm during throttling.
-                featureEnabled && throttling == null
+                hasLockoutOccurred ->
+                // Disable auto-confirm if lockout occurred since the last successful
+                // authentication attempt.
+                featureEnabled && !hasLockoutOccurred
             }
             .stateIn(
                 scope = applicationScope,
@@ -139,7 +140,7 @@
     /** Whether the "enhanced PIN privacy" setting is enabled for the current user. */
     val isPinEnhancedPrivacyEnabled: StateFlow<Boolean> = repository.isPinEnhancedPrivacyEnabled
 
-    private var throttlingCountdownJob: Job? = null
+    private var lockoutCountdownJob: Job? = null
 
     init {
         applicationScope.launch {
@@ -188,8 +189,8 @@
         val authMethod = getAuthenticationMethod()
         val skipCheck =
             when {
-                // Throttling is active, the UI layer should not have called this; skip the attempt.
-                throttling.value != null -> true
+                // Lockout is active, the UI layer should not have called this; skip the attempt.
+                lockout.value != null -> true
                 // The input is too short; skip the attempt.
                 input.isTooShort(authMethod) -> true
                 // Auto-confirm attempt when the feature is not enabled; skip the attempt.
@@ -215,18 +216,22 @@
             )
         }
 
-        // Check if we need to throttle and, if so, kick off the throttle countdown:
-        if (!authenticationResult.isSuccessful && authenticationResult.throttleDurationMs > 0) {
-            repository.setThrottleDuration(
-                durationMs = authenticationResult.throttleDurationMs,
-            )
-            startThrottlingCountdown()
+        // Check if lockout should start and, if so, kick off the countdown:
+        if (!authenticationResult.isSuccessful && authenticationResult.lockoutDurationMs > 0) {
+            repository.apply {
+                setLockoutDuration(durationMs = authenticationResult.lockoutDurationMs)
+                reportLockoutStarted(durationMs = authenticationResult.lockoutDurationMs)
+                hasLockoutOccurred.value = true
+            }
+            startLockoutCountdown()
         }
 
         if (authenticationResult.isSuccessful) {
-            // Since authentication succeeded, we should refresh throttling to make sure that our
-            // state is completely reflecting the upstream source of truth.
-            refreshThrottling()
+            // Since authentication succeeded, refresh lockout to make sure the state is completely
+            // reflecting the upstream source of truth.
+            refreshLockout()
+
+            repository.hasLockoutOccurred.value = false
         }
 
         return if (authenticationResult.isSuccessful) {
@@ -244,52 +249,52 @@
         }
     }
 
-    /** Starts refreshing the throttling state every second. */
-    private suspend fun startThrottlingCountdown() {
-        cancelThrottlingCountdown()
-        throttlingCountdownJob =
+    /** Starts refreshing the lockout state every second. */
+    private suspend fun startLockoutCountdown() {
+        cancelLockoutCountdown()
+        lockoutCountdownJob =
             applicationScope.launch {
-                while (refreshThrottling()) {
+                while (refreshLockout()) {
                     delay(1.seconds.inWholeMilliseconds)
                 }
             }
     }
 
-    /** Cancels any throttling state countdown started in [startThrottlingCountdown]. */
-    private fun cancelThrottlingCountdown() {
-        throttlingCountdownJob?.cancel()
-        throttlingCountdownJob = null
+    /** Cancels any lockout state countdown started in [startLockoutCountdown]. */
+    private fun cancelLockoutCountdown() {
+        lockoutCountdownJob?.cancel()
+        lockoutCountdownJob = null
     }
 
     /** Notifies that the currently-selected user has changed. */
     private suspend fun onSelectedUserChanged() {
-        cancelThrottlingCountdown()
-        if (refreshThrottling()) {
-            startThrottlingCountdown()
+        cancelLockoutCountdown()
+        if (refreshLockout()) {
+            startLockoutCountdown()
         }
     }
 
     /**
-     * Refreshes the throttling state, hydrating the repository with the latest state.
+     * Refreshes the lockout state, hydrating the repository with the latest state.
      *
-     * @return Whether throttling is active or not.
+     * @return Whether lockout is active or not.
      */
-    private suspend fun refreshThrottling(): Boolean {
-        withContext("$TAG#refreshThrottling", backgroundDispatcher) {
+    private suspend fun refreshLockout(): Boolean {
+        withContext("$TAG#refreshLockout", backgroundDispatcher) {
             val failedAttemptCount = async { repository.getFailedAuthenticationAttemptCount() }
-            val deadline = async { repository.getThrottlingEndTimestamp() }
+            val deadline = async { repository.getLockoutEndTimestamp() }
             val remainingMs = max(0, deadline.await() - clock.elapsedRealtime())
-            repository.throttling.value =
+            repository.lockout.value =
                 if (remainingMs > 0) {
-                    AuthenticationThrottlingModel(
+                    AuthenticationLockoutModel(
                         failedAttemptCount = failedAttemptCount.await(),
-                        remainingMs = remainingMs.toInt(),
+                        remainingSeconds = ceil(remainingMs / 1000f).toInt(),
                     )
                 } else {
-                    null // Throttling ended.
+                    null // Lockout ended.
                 }
         }
-        return repository.throttling.value != null
+        return repository.lockout.value != null
     }
 
     private fun AuthenticationMethodModel.createCredential(
diff --git a/packages/SystemUI/src/com/android/systemui/authentication/shared/model/AuthenticationThrottlingModel.kt b/packages/SystemUI/src/com/android/systemui/authentication/shared/model/AuthenticationLockoutModel.kt
similarity index 60%
rename from packages/SystemUI/src/com/android/systemui/authentication/shared/model/AuthenticationThrottlingModel.kt
rename to packages/SystemUI/src/com/android/systemui/authentication/shared/model/AuthenticationLockoutModel.kt
index d0d398e..8ee2d5e 100644
--- a/packages/SystemUI/src/com/android/systemui/authentication/shared/model/AuthenticationThrottlingModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/authentication/shared/model/AuthenticationLockoutModel.kt
@@ -16,17 +16,20 @@
 
 package com.android.systemui.authentication.shared.model
 
-/** Models a state for throttling the next authentication attempt. */
-data class AuthenticationThrottlingModel(
+/** Models a state for temporarily locking out the next authentication attempt. */
+data class AuthenticationLockoutModel(
 
-    /** Number of failed authentication attempts so far. If not throttling this will be `0`. */
+    /** Number of failed authentication attempts so far. If not locked out this will be `0`. */
     val failedAttemptCount: Int = 0,
 
     /**
-     * Remaining amount of time, in milliseconds, before another authentication attempt can be done.
-     * If not throttling this will be `0`.
+     * Remaining amount of time, in seconds, before another authentication attempt can be done. If
+     * not locked out this will be `0`.
      *
-     * This number is changed throughout the timeout.
+     * This number is changed throughout the lockout.
+     *
+     * Note: this isn't precise (in milliseconds), but rounded up to ensure "at most" this amount of
+     * seconds remains.
      */
-    val remainingMs: Int = 0,
+    val remainingSeconds: Int = 0,
 )
diff --git a/packages/SystemUI/src/com/android/systemui/authentication/shared/model/AuthenticationResultModel.kt b/packages/SystemUI/src/com/android/systemui/authentication/shared/model/AuthenticationResultModel.kt
index f2a3e74..addc75e 100644
--- a/packages/SystemUI/src/com/android/systemui/authentication/shared/model/AuthenticationResultModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/authentication/shared/model/AuthenticationResultModel.kt
@@ -21,5 +21,5 @@
     /** Whether authentication was successful. */
     val isSuccessful: Boolean = false,
     /** If [isSuccessful] is `false`, how long the user must wait before trying again. */
-    val throttleDurationMs: Int = 0,
+    val lockoutDurationMs: Int = 0,
 )
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerLegacy.kt b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerLegacy.kt
index a2ac66f..63fe26a 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerLegacy.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerLegacy.kt
@@ -197,11 +197,42 @@
                 listenForGoneToAodTransition(this)
                 listenForLockscreenAodTransitions(this)
                 listenForAodToOccludedTransitions(this)
+                listenForAlternateBouncerToAodTransitions(this)
+                listenForDreamingToAodTransitions(this)
             }
         }
     }
 
     @VisibleForTesting
+    suspend fun listenForDreamingToAodTransitions(scope: CoroutineScope): Job {
+        return scope.launch {
+            transitionInteractor.transition(KeyguardState.DREAMING, KeyguardState.AOD).collect {
+                transitionStep ->
+                view.onDozeAmountChanged(
+                    transitionStep.value,
+                    transitionStep.value,
+                    ANIMATE_APPEAR_ON_SCREEN_OFF,
+                )
+            }
+        }
+    }
+
+    @VisibleForTesting
+    suspend fun listenForAlternateBouncerToAodTransitions(scope: CoroutineScope): Job {
+        return scope.launch {
+            transitionInteractor
+                .transition(KeyguardState.ALTERNATE_BOUNCER, KeyguardState.AOD)
+                .collect { transitionStep ->
+                    view.onDozeAmountChanged(
+                        transitionStep.value,
+                        transitionStep.value,
+                        UdfpsKeyguardViewLegacy.ANIMATION_BETWEEN_AOD_AND_LOCKSCREEN,
+                    )
+                }
+        }
+    }
+
+    @VisibleForTesting
     suspend fun listenForAodToOccludedTransitions(scope: CoroutineScope): Job {
         return scope.launch {
             transitionInteractor.transition(KeyguardState.AOD, KeyguardState.OCCLUDED).collect {
@@ -246,7 +277,10 @@
     suspend fun listenForLockscreenAodTransitions(scope: CoroutineScope): Job {
         return scope.launch {
             transitionInteractor.dozeAmountTransition.collect { transitionStep ->
-                if (transitionStep.transitionState == TransitionState.CANCELED) {
+                if (
+                    transitionStep.from == KeyguardState.AOD &&
+                        transitionStep.transitionState == TransitionState.CANCELED
+                ) {
                     if (
                         transitionInteractor.startedKeyguardTransitionStep.first().to !=
                             KeyguardState.AOD
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractor.kt b/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractor.kt
index 1122877..724c0fe 100644
--- a/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractor.kt
@@ -19,8 +19,8 @@
 import android.content.Context
 import com.android.systemui.authentication.domain.interactor.AuthenticationInteractor
 import com.android.systemui.authentication.domain.interactor.AuthenticationResult
+import com.android.systemui.authentication.shared.model.AuthenticationLockoutModel
 import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
-import com.android.systemui.authentication.shared.model.AuthenticationThrottlingModel
 import com.android.systemui.bouncer.data.repository.BouncerRepository
 import com.android.systemui.classifier.FalsingClassifier
 import com.android.systemui.classifier.domain.interactor.FalsingInteractor
@@ -32,7 +32,6 @@
 import com.android.systemui.scene.shared.flag.SceneContainerFlags
 import com.android.systemui.util.kotlin.pairwise
 import javax.inject.Inject
-import kotlin.time.Duration.Companion.milliseconds
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.async
 import kotlinx.coroutines.flow.MutableSharedFlow
@@ -61,24 +60,25 @@
 
     /** The user-facing message to show in the bouncer. */
     val message: StateFlow<String?> =
-        combine(repository.message, authenticationInteractor.throttling) { message, throttling ->
-                messageOrThrottlingMessage(message, throttling)
+        combine(repository.message, authenticationInteractor.lockout) { message, lockout ->
+                messageOrLockoutMessage(message, lockout)
             }
             .stateIn(
                 scope = applicationScope,
                 started = SharingStarted.WhileSubscribed(),
                 initialValue =
-                    messageOrThrottlingMessage(
+                    messageOrLockoutMessage(
                         repository.message.value,
-                        authenticationInteractor.throttling.value,
+                        authenticationInteractor.lockout.value,
                     )
             )
 
     /**
-     * The current authentication throttling state, set when the user has to wait before being able
-     * to try another authentication attempt. `null` indicates throttling isn't active.
+     * The current authentication lockout (aka "throttling") state, set when the user has to wait
+     * before being able to try another authentication attempt. `null` indicates lockout isn't
+     * active.
      */
-    val throttling: StateFlow<AuthenticationThrottlingModel?> = authenticationInteractor.throttling
+    val lockout: StateFlow<AuthenticationLockoutModel?> = authenticationInteractor.lockout
 
     /** Whether the auto confirm feature is enabled for the currently-selected user. */
     val isAutoConfirmEnabled: StateFlow<Boolean> = authenticationInteractor.isAutoConfirmEnabled
@@ -103,9 +103,9 @@
 
     init {
         if (flags.isEnabled()) {
-            // Clear the message if moved from throttling to no-longer throttling.
+            // Clear the message if moved from locked-out to no-longer locked-out.
             applicationScope.launch {
-                throttling.pairwise().collect { (previous, current) ->
+                lockout.pairwise().collect { (previous, current) ->
                     if (previous != null && current == null) {
                         clearMessage()
                     }
@@ -214,9 +214,9 @@
      * Shows the error message.
      *
      * Callers should use this instead of [authenticate] when they know ahead of time that an auth
-     * attempt will fail but aren't interested in the other side effects like triggering throttling.
+     * attempt will fail but aren't interested in the other side effects like triggering lockout.
      * For example, if the user entered a pattern that's too short, the system can show the error
-     * message without having the attempt trigger throttling.
+     * message without having the attempt trigger lockout.
      */
     private suspend fun showErrorMessage() {
         repository.setMessage(errorMessage(authenticationInteractor.getAuthenticationMethod()))
@@ -251,15 +251,15 @@
         }
     }
 
-    private fun messageOrThrottlingMessage(
+    private fun messageOrLockoutMessage(
         message: String?,
-        throttlingModel: AuthenticationThrottlingModel?,
+        lockoutModel: AuthenticationLockoutModel?,
     ): String {
         return when {
-            throttlingModel != null ->
+            lockoutModel != null ->
                 applicationContext.getString(
                     com.android.internal.R.string.lockscreen_too_many_failed_attempts_countdown,
-                    throttlingModel.remainingMs.milliseconds.inWholeSeconds,
+                    lockoutModel.remainingSeconds,
                 )
             message != null -> message
             else -> ""
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/ui/helper/BouncerSceneLayout.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/helper/BouncerSceneLayout.kt
index 5385442..7f97718 100644
--- a/packages/SystemUI/src/com/android/systemui/bouncer/ui/helper/BouncerSceneLayout.kt
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/ui/helper/BouncerSceneLayout.kt
@@ -21,13 +21,13 @@
 /** Enumerates all known adaptive layout configurations. */
 enum class BouncerSceneLayout {
     /** The default UI with the bouncer laid out normally. */
-    STANDARD,
+    STANDARD_BOUNCER,
     /** The bouncer is displayed vertically stacked with the user switcher. */
-    STACKED,
+    BELOW_USER_SWITCHER,
     /** The bouncer is displayed side-by-side with the user switcher or an empty space. */
-    SIDE_BY_SIDE,
+    BESIDE_USER_SWITCHER,
     /** The bouncer is split in two with both sides shown side-by-side. */
-    SPLIT,
+    SPLIT_BOUNCER,
 }
 
 /** Enumerates the supported window size classes. */
@@ -48,19 +48,19 @@
     isSideBySideSupported: Boolean,
 ): BouncerSceneLayout {
     return when (height) {
-        SizeClass.COMPACT -> BouncerSceneLayout.SPLIT
+        SizeClass.COMPACT -> BouncerSceneLayout.SPLIT_BOUNCER
         SizeClass.MEDIUM ->
             when (width) {
-                SizeClass.COMPACT -> BouncerSceneLayout.STANDARD
-                SizeClass.MEDIUM -> BouncerSceneLayout.STANDARD
-                SizeClass.EXPANDED -> BouncerSceneLayout.SIDE_BY_SIDE
+                SizeClass.COMPACT -> BouncerSceneLayout.STANDARD_BOUNCER
+                SizeClass.MEDIUM -> BouncerSceneLayout.STANDARD_BOUNCER
+                SizeClass.EXPANDED -> BouncerSceneLayout.BESIDE_USER_SWITCHER
             }
         SizeClass.EXPANDED ->
             when (width) {
-                SizeClass.COMPACT -> BouncerSceneLayout.STANDARD
-                SizeClass.MEDIUM -> BouncerSceneLayout.STACKED
-                SizeClass.EXPANDED -> BouncerSceneLayout.SIDE_BY_SIDE
+                SizeClass.COMPACT -> BouncerSceneLayout.STANDARD_BOUNCER
+                SizeClass.MEDIUM -> BouncerSceneLayout.BELOW_USER_SWITCHER
+                SizeClass.EXPANDED -> BouncerSceneLayout.BESIDE_USER_SWITCHER
             }
-    }.takeIf { it != BouncerSceneLayout.SIDE_BY_SIDE || isSideBySideSupported }
-        ?: BouncerSceneLayout.STANDARD
+    }.takeIf { it != BouncerSceneLayout.BESIDE_USER_SWITCHER || isSideBySideSupported }
+        ?: BouncerSceneLayout.STANDARD_BOUNCER
 }
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/AuthMethodBouncerViewModel.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/AuthMethodBouncerViewModel.kt
index e379dab..0d7f6dc 100644
--- a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/AuthMethodBouncerViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/AuthMethodBouncerViewModel.kt
@@ -50,12 +50,12 @@
     abstract val authenticationMethod: AuthenticationMethodModel
 
     /**
-     * String resource ID of the failure message to be shown during throttling.
+     * String resource ID of the failure message to be shown during lockout.
      *
      * The message must include 2 number parameters: the first one indicating how many unsuccessful
-     * attempts were made, and the second one indicating in how many seconds throttling will expire.
+     * attempts were made, and the second one indicating in how many seconds lockout will expire.
      */
-    @get:StringRes abstract val throttlingMessageId: Int
+    @get:StringRes abstract val lockoutMessageId: Int
 
     /** Notifies that the UI has been shown to the user. */
     fun onShown() {
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModel.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModel.kt
index 58fa857..4b14343 100644
--- a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModel.kt
@@ -36,7 +36,6 @@
 import com.android.systemui.user.ui.viewmodel.UserViewModel
 import dagger.Module
 import dagger.Provides
-import kotlin.math.ceil
 import kotlinx.coroutines.CoroutineDispatcher
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.SupervisorJob
@@ -106,17 +105,17 @@
         get() = bouncerInteractor.isUserSwitcherVisible
 
     private val isInputEnabled: StateFlow<Boolean> =
-        bouncerInteractor.throttling
+        bouncerInteractor.lockout
             .map { it == null }
             .stateIn(
                 scope = applicationScope,
                 started = SharingStarted.WhileSubscribed(),
-                initialValue = bouncerInteractor.throttling.value == null,
+                initialValue = bouncerInteractor.lockout.value == null,
             )
 
     // Handle to the scope of the child ViewModel (stored in [authMethod]).
     private var childViewModelScope: CoroutineScope? = null
-    private val _throttlingDialogMessage = MutableStateFlow<String?>(null)
+    private val _dialogMessage = MutableStateFlow<String?>(null)
 
     /** View-model for the current UI, based on the current authentication method. */
     val authMethodViewModel: StateFlow<AuthMethodBouncerViewModel?> =
@@ -129,20 +128,20 @@
             )
 
     /**
-     * A message for a throttling dialog to show when the user has attempted the wrong credential
-     * too many times and now must wait a while before attempting again.
+     * A message for a dialog to show when the user has attempted the wrong credential too many
+     * times and now must wait a while before attempting again.
      *
      * If `null`, no dialog should be shown.
      *
-     * Once the dialog is shown, the UI should call [onThrottlingDialogDismissed] when the user
-     * dismisses this dialog.
+     * Once the dialog is shown, the UI should call [onDialogDismissed] when the user dismisses this
+     * dialog.
      */
-    val throttlingDialogMessage: StateFlow<String?> = _throttlingDialogMessage.asStateFlow()
+    val dialogMessage: StateFlow<String?> = _dialogMessage.asStateFlow()
 
     /** The user-facing message to show in the bouncer. */
     val message: StateFlow<MessageViewModel> =
-        combine(bouncerInteractor.message, bouncerInteractor.throttling) { message, throttling ->
-                toMessageViewModel(message, isThrottled = throttling != null)
+        combine(bouncerInteractor.message, bouncerInteractor.lockout) { message, lockout ->
+                toMessageViewModel(message, isLockedOut = lockout != null)
             }
             .stateIn(
                 scope = applicationScope,
@@ -150,7 +149,7 @@
                 initialValue =
                     toMessageViewModel(
                         message = bouncerInteractor.message.value,
-                        isThrottled = bouncerInteractor.throttling.value != null,
+                        isLockedOut = bouncerInteractor.lockout.value != null,
                     ),
             )
 
@@ -198,28 +197,28 @@
     init {
         if (flags.isEnabled()) {
             applicationScope.launch {
-                combine(bouncerInteractor.throttling, authMethodViewModel) {
-                        throttling,
+                combine(bouncerInteractor.lockout, authMethodViewModel) {
+                        lockout,
                         authMethodViewModel ->
-                        if (throttling != null && authMethodViewModel != null) {
+                        if (lockout != null && authMethodViewModel != null) {
                             applicationContext.getString(
-                                authMethodViewModel.throttlingMessageId,
-                                throttling.failedAttemptCount,
-                                ceil(throttling.remainingMs / 1000f).toInt(),
+                                authMethodViewModel.lockoutMessageId,
+                                lockout.failedAttemptCount,
+                                lockout.remainingSeconds,
                             )
                         } else {
                             null
                         }
                     }
                     .distinctUntilChanged()
-                    .collect { dialogMessage -> _throttlingDialogMessage.value = dialogMessage }
+                    .collect { dialogMessage -> _dialogMessage.value = dialogMessage }
             }
         }
     }
 
-    /** Notifies that a throttling dialog has been dismissed by the user. */
-    fun onThrottlingDialogDismissed() {
-        _throttlingDialogMessage.value = null
+    /** Notifies that the dialog has been dismissed by the user. */
+    fun onDialogDismissed() {
+        _dialogMessage.value = null
     }
 
     private fun isSideBySideSupported(authMethod: AuthMethodBouncerViewModel?): Boolean {
@@ -232,11 +231,11 @@
 
     private fun toMessageViewModel(
         message: String?,
-        isThrottled: Boolean,
+        isLockedOut: Boolean,
     ): MessageViewModel {
         return MessageViewModel(
             text = message ?: "",
-            isUpdateAnimated = !isThrottled,
+            isUpdateAnimated = !isLockedOut,
         )
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModel.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModel.kt
index 3b7e321..b682717 100644
--- a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModel.kt
@@ -46,7 +46,7 @@
 
     override val authenticationMethod = AuthenticationMethodModel.Password
 
-    override val throttlingMessageId = R.string.kg_too_many_failed_password_attempts_dialog_message
+    override val lockoutMessageId = R.string.kg_too_many_failed_password_attempts_dialog_message
 
     /** Whether the input method editor (for example, the software keyboard) is visible. */
     private var isImeVisible: Boolean = false
@@ -56,13 +56,13 @@
 
     /** Whether the UI should request focus on the text field element. */
     val isTextFieldFocusRequested =
-        combine(interactor.throttling, isTextFieldFocused) { throttling, hasFocus ->
+        combine(interactor.lockout, isTextFieldFocused) { throttling, hasFocus ->
                 throttling == null && !hasFocus
             }
             .stateIn(
                 scope = viewModelScope,
                 started = SharingStarted.WhileSubscribed(),
-                initialValue = interactor.throttling.value == null && !isTextFieldFocused.value,
+                initialValue = interactor.lockout.value == null && !isTextFieldFocused.value,
             )
 
     override fun onHidden() {
@@ -104,7 +104,7 @@
      * hidden.
      */
     suspend fun onImeVisibilityChanged(isVisible: Boolean) {
-        if (isImeVisible && !isVisible && interactor.throttling.value == null) {
+        if (isImeVisible && !isVisible && interactor.lockout.value == null) {
             interactor.onImeHiddenByUser()
         }
 
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModel.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModel.kt
index b1c5ab6..69f8032 100644
--- a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModel.kt
@@ -80,7 +80,7 @@
 
     override val authenticationMethod = AuthenticationMethodModel.Pattern
 
-    override val throttlingMessageId = R.string.kg_too_many_failed_pattern_attempts_dialog_message
+    override val lockoutMessageId = R.string.kg_too_many_failed_pattern_attempts_dialog_message
 
     /** Notifies that the user has started a drag gesture across the dot grid. */
     fun onDragStart() {
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModel.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModel.kt
index e25e82f..7f4a029 100644
--- a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModel.kt
@@ -104,7 +104,7 @@
 
     override val authenticationMethod: AuthenticationMethodModel = authenticationMethod
 
-    override val throttlingMessageId = R.string.kg_too_many_failed_pin_attempts_dialog_message
+    override val lockoutMessageId = R.string.kg_too_many_failed_pin_attempts_dialog_message
 
     init {
         viewModelScope.launch { simBouncerInteractor.subId.collect { onResetSimFlow() } }
diff --git a/packages/SystemUI/src/com/android/systemui/common/shared/model/NotificationContainerBounds.kt b/packages/SystemUI/src/com/android/systemui/common/shared/model/NotificationContainerBounds.kt
index fdd98bec..3063ebd 100644
--- a/packages/SystemUI/src/com/android/systemui/common/shared/model/NotificationContainerBounds.kt
+++ b/packages/SystemUI/src/com/android/systemui/common/shared/model/NotificationContainerBounds.kt
@@ -18,8 +18,12 @@
 
 /** Models the bounds of the notification container. */
 data class NotificationContainerBounds(
+    /** The position of the left of the container in its window coordinate system, in pixels. */
+    val left: Float = 0f,
     /** The position of the top of the container in its window coordinate system, in pixels. */
     val top: Float = 0f,
+    /** The position of the right of the container in its window coordinate system, in pixels. */
+    val right: Float = 0f,
     /** The position of the bottom of the container in its window coordinate system, in pixels. */
     val bottom: Float = 0f,
     /** Whether any modifications to top/bottom should be smoothly animated. */
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
index 0405ca4..ca8268d 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
@@ -82,6 +82,7 @@
 import com.android.systemui.qs.QSFragmentStartableModule;
 import com.android.systemui.qs.footer.dagger.FooterActionsModule;
 import com.android.systemui.recents.Recents;
+import com.android.systemui.recordissue.RecordIssueModule;
 import com.android.systemui.retail.dagger.RetailModeModule;
 import com.android.systemui.scene.ui.view.WindowRootViewComponent;
 import com.android.systemui.screenrecord.ScreenRecordModule;
@@ -209,6 +210,7 @@
         PrivacyModule.class,
         QRCodeScannerModule.class,
         QSFragmentStartableModule.class,
+        RecordIssueModule.class,
         ReferenceModule.class,
         RetailModeModule.class,
         ScreenshotModule.class,
diff --git a/packages/SystemUI/src/com/android/systemui/deviceentry/DeviceEntryModule.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/DeviceEntryModule.kt
index cd764c0..b915418 100644
--- a/packages/SystemUI/src/com/android/systemui/deviceentry/DeviceEntryModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/deviceentry/DeviceEntryModule.kt
@@ -1,6 +1,5 @@
 package com.android.systemui.deviceentry
 
-import com.android.systemui.deviceentry.data.repository.DeviceEntryHapticsRepositoryModule
 import com.android.systemui.deviceentry.data.repository.DeviceEntryRepositoryModule
 import com.android.systemui.keyguard.ui.transitions.DeviceEntryIconTransition
 import dagger.Module
@@ -10,7 +9,6 @@
     includes =
         [
             DeviceEntryRepositoryModule::class,
-            DeviceEntryHapticsRepositoryModule::class,
         ],
 )
 abstract class DeviceEntryModule {
diff --git a/packages/SystemUI/src/com/android/systemui/deviceentry/data/repository/DeviceEntryHapticsRepository.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/data/repository/DeviceEntryHapticsRepository.kt
deleted file mode 100644
index 1458404..0000000
--- a/packages/SystemUI/src/com/android/systemui/deviceentry/data/repository/DeviceEntryHapticsRepository.kt
+++ /dev/null
@@ -1,72 +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.deviceentry.data.repository
-
-import com.android.systemui.dagger.SysUISingleton
-import dagger.Binds
-import dagger.Module
-import javax.inject.Inject
-import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.MutableStateFlow
-import kotlinx.coroutines.flow.asStateFlow
-
-/** Interface for classes that can access device-entry haptics application state. */
-interface DeviceEntryHapticsRepository {
-    /**
-     * Whether a successful biometric haptic has been requested. Has not yet been handled if true.
-     */
-    val successHapticRequest: Flow<Boolean>
-
-    /** Whether an error biometric haptic has been requested. Has not yet been handled if true. */
-    val errorHapticRequest: Flow<Boolean>
-
-    fun requestSuccessHaptic()
-    fun handleSuccessHaptic()
-    fun requestErrorHaptic()
-    fun handleErrorHaptic()
-}
-
-/** Encapsulates application state for device entry haptics. */
-@SysUISingleton
-class DeviceEntryHapticsRepositoryImpl @Inject constructor() : DeviceEntryHapticsRepository {
-    private val _successHapticRequest = MutableStateFlow(false)
-    override val successHapticRequest: Flow<Boolean> = _successHapticRequest.asStateFlow()
-
-    private val _errorHapticRequest = MutableStateFlow(false)
-    override val errorHapticRequest: Flow<Boolean> = _errorHapticRequest.asStateFlow()
-
-    override fun requestSuccessHaptic() {
-        _successHapticRequest.value = true
-    }
-
-    override fun handleSuccessHaptic() {
-        _successHapticRequest.value = false
-    }
-
-    override fun requestErrorHaptic() {
-        _errorHapticRequest.value = true
-    }
-
-    override fun handleErrorHaptic() {
-        _errorHapticRequest.value = false
-    }
-}
-
-@Module
-interface DeviceEntryHapticsRepositoryModule {
-    @Binds fun repository(impl: DeviceEntryHapticsRepositoryImpl): DeviceEntryHapticsRepository
-}
diff --git a/packages/SystemUI/src/com/android/systemui/deviceentry/data/repository/DeviceEntryRepository.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/data/repository/DeviceEntryRepository.kt
index f27bbe6..08e8c2d 100644
--- a/packages/SystemUI/src/com/android/systemui/deviceentry/data/repository/DeviceEntryRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/deviceentry/data/repository/DeviceEntryRepository.kt
@@ -7,26 +7,36 @@
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.keyguard.data.repository.KeyguardRepository
+import com.android.systemui.keyguard.shared.model.BiometricUnlockModel
+import com.android.systemui.keyguard.shared.model.BiometricUnlockSource
 import com.android.systemui.statusbar.phone.KeyguardBypassController
 import com.android.systemui.statusbar.policy.KeyguardStateController
 import com.android.systemui.user.data.repository.UserRepository
+import com.android.systemui.util.kotlin.sample
 import dagger.Binds
 import dagger.Module
 import javax.inject.Inject
 import kotlinx.coroutines.CoroutineDispatcher
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.SharingStarted
 import kotlinx.coroutines.flow.StateFlow
 import kotlinx.coroutines.flow.asStateFlow
 import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.filter
+import kotlinx.coroutines.flow.filterNotNull
 import kotlinx.coroutines.flow.onEach
 import kotlinx.coroutines.flow.stateIn
 import kotlinx.coroutines.withContext
 
 /** Interface for classes that can access device-entry-related application state. */
 interface DeviceEntryRepository {
+    /** Whether the device is immediately entering the device after a biometric unlock. */
+    val enteringDeviceFromBiometricUnlock: Flow<BiometricUnlockSource>
+
     /**
      * Whether the device is unlocked.
      *
@@ -73,7 +83,14 @@
     private val lockPatternUtils: LockPatternUtils,
     private val keyguardBypassController: KeyguardBypassController,
     keyguardStateController: KeyguardStateController,
+    keyguardRepository: KeyguardRepository,
 ) : DeviceEntryRepository {
+    override val enteringDeviceFromBiometricUnlock =
+        keyguardRepository.biometricUnlockState
+            .filter { BiometricUnlockModel.dismissesKeyguard(it) }
+            .sample(
+                keyguardRepository.biometricUnlockSource.filterNotNull(),
+            )
 
     private val _isUnlocked = MutableStateFlow(false)
 
diff --git a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryBiometricAuthInteractor.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryBiometricAuthInteractor.kt
new file mode 100644
index 0000000..1a6bd04
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryBiometricAuthInteractor.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.systemui.deviceentry.domain.interactor
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.deviceentry.shared.DeviceEntryBiometricMode
+import com.android.systemui.keyguard.data.repository.BiometricSettingsRepository
+import com.android.systemui.keyguard.shared.model.FailedFaceAuthenticationStatus
+import javax.inject.Inject
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.emptyFlow
+import kotlinx.coroutines.flow.flatMapLatest
+import kotlinx.coroutines.flow.map
+
+/** Business logic for device entry biometric states that may differ based on the biometric mode. */
+@ExperimentalCoroutinesApi
+@SysUISingleton
+class DeviceEntryBiometricAuthInteractor
+@Inject
+constructor(
+    biometricSettingsRepository: BiometricSettingsRepository,
+    deviceEntryFaceAuthInteractor: DeviceEntryFaceAuthInteractor,
+) {
+    private val biometricMode: Flow<DeviceEntryBiometricMode> =
+        combine(
+            biometricSettingsRepository.isFingerprintEnrolledAndEnabled,
+            biometricSettingsRepository.isFaceAuthEnrolledAndEnabled,
+        ) { fingerprintEnrolled, faceEnrolled ->
+            if (fingerprintEnrolled && faceEnrolled) {
+                DeviceEntryBiometricMode.CO_EXPERIENCE
+            } else if (fingerprintEnrolled) {
+                DeviceEntryBiometricMode.FINGERPRINT_ONLY
+            } else if (faceEnrolled) {
+                DeviceEntryBiometricMode.FACE_ONLY
+            } else {
+                DeviceEntryBiometricMode.NONE
+            }
+        }
+    private val faceOnly: Flow<Boolean> =
+        biometricMode.map { it == DeviceEntryBiometricMode.FACE_ONLY }
+
+    /**
+     * Triggered if face is the only biometric that can be used for device entry and a face failure
+     * occurs.
+     */
+    val faceOnlyFaceFailure: Flow<FailedFaceAuthenticationStatus> =
+        faceOnly.flatMapLatest { faceOnly ->
+            if (faceOnly) {
+                deviceEntryFaceAuthInteractor.faceFailure
+            } else {
+                emptyFlow()
+            }
+        }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFaceAuthInteractor.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFaceAuthInteractor.kt
new file mode 100644
index 0000000..70716c6
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFaceAuthInteractor.kt
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.deviceentry.domain.interactor
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.keyguard.data.repository.DeviceEntryFaceAuthRepository
+import com.android.systemui.keyguard.shared.model.FailedFaceAuthenticationStatus
+import javax.inject.Inject
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.filterIsInstance
+
+@SysUISingleton
+class DeviceEntryFaceAuthInteractor
+@Inject
+constructor(
+    repository: DeviceEntryFaceAuthRepository,
+) {
+    val faceFailure: Flow<FailedFaceAuthenticationStatus> =
+        repository.authenticationStatus.filterIsInstance<FailedFaceAuthenticationStatus>()
+}
diff --git a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFingerprintAuthInteractor.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFingerprintAuthInteractor.kt
new file mode 100644
index 0000000..efa1c0a
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFingerprintAuthInteractor.kt
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.deviceentry.domain.interactor
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.keyguard.data.repository.DeviceEntryFingerprintAuthRepository
+import com.android.systemui.keyguard.shared.model.FailFingerprintAuthenticationStatus
+import javax.inject.Inject
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.filterIsInstance
+
+@SysUISingleton
+class DeviceEntryFingerprintAuthInteractor
+@Inject
+constructor(
+    repository: DeviceEntryFingerprintAuthRepository,
+) {
+    val fingerprintFailure: Flow<FailFingerprintAuthenticationStatus> =
+        repository.authenticationStatus.filterIsInstance<FailFingerprintAuthenticationStatus>()
+}
diff --git a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryHapticsInteractor.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryHapticsInteractor.kt
index 53d6f73..649a971 100644
--- a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryHapticsInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryHapticsInteractor.kt
@@ -19,7 +19,6 @@
 import com.android.systemui.biometrics.data.repository.FingerprintPropertyRepository
 import com.android.systemui.biometrics.shared.model.FingerprintSensorType
 import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.deviceentry.data.repository.DeviceEntryHapticsRepository
 import com.android.systemui.keyevent.domain.interactor.KeyEventInteractor
 import com.android.systemui.keyguard.data.repository.BiometricSettingsRepository
 import com.android.systemui.power.domain.interactor.PowerInteractor
@@ -34,11 +33,12 @@
 import kotlinx.coroutines.flow.distinctUntilChanged
 import kotlinx.coroutines.flow.filter
 import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.merge
 import kotlinx.coroutines.flow.onStart
 
 /**
  * Business logic for device entry haptic events. Determines whether the haptic should play. In
- * particular, there are extra guards for whether device entry error and successes hatpics should
+ * particular, there are extra guards for whether device entry error and successes haptics should
  * play when the physical fingerprint sensor is located on the power button.
  */
 @ExperimentalCoroutinesApi
@@ -46,7 +46,9 @@
 class DeviceEntryHapticsInteractor
 @Inject
 constructor(
-    private val repository: DeviceEntryHapticsRepository,
+    deviceEntryInteractor: DeviceEntryInteractor,
+    deviceEntryFingerprintAuthInteractor: DeviceEntryFingerprintAuthInteractor,
+    deviceEntryBiometricAuthInteractor: DeviceEntryBiometricAuthInteractor,
     fingerprintPropertyRepository: FingerprintPropertyRepository,
     biometricSettingsRepository: BiometricSettingsRepository,
     keyEventInteractor: KeyEventInteractor,
@@ -77,9 +79,8 @@
                 emit(recentPowerButtonPressThresholdMs * -1L - 1L)
             }
 
-    val playSuccessHaptic: Flow<Boolean> =
-        repository.successHapticRequest
-            .filter { it }
+    val playSuccessHaptic: Flow<Unit> =
+        deviceEntryInteractor.enteringDeviceFromBiometricUnlock
             .sample(
                 combine(
                     powerButtonSideFpsEnrolled,
@@ -88,7 +89,7 @@
                     ::Triple
                 )
             )
-            .map { (sideFpsEnrolled, powerButtonDown, lastPowerButtonWakeup) ->
+            .filter { (sideFpsEnrolled, powerButtonDown, lastPowerButtonWakeup) ->
                 val sideFpsAllowsHaptic =
                     !powerButtonDown &&
                         systemClock.uptimeMillis() - lastPowerButtonWakeup >
@@ -96,38 +97,28 @@
                 val allowHaptic = !sideFpsEnrolled || sideFpsAllowsHaptic
                 if (!allowHaptic) {
                     logger.d("Skip success haptic. Recent power button press or button is down.")
-                    handleSuccessHaptic() // immediately handle, don't vibrate
                 }
                 allowHaptic
             }
-    val playErrorHaptic: Flow<Boolean> =
-        repository.errorHapticRequest
-            .filter { it }
+            .map {} // map to Unit
+
+    private val playErrorHapticForBiometricFailure: Flow<Unit> =
+        merge(
+                deviceEntryFingerprintAuthInteractor.fingerprintFailure,
+                deviceEntryBiometricAuthInteractor.faceOnlyFaceFailure,
+            )
+            .map {} // map to Unit
+    val playErrorHaptic: Flow<Unit> =
+        playErrorHapticForBiometricFailure
             .sample(combine(powerButtonSideFpsEnrolled, powerButtonDown, ::Pair))
-            .map { (sideFpsEnrolled, powerButtonDown) ->
+            .filter { (sideFpsEnrolled, powerButtonDown) ->
                 val allowHaptic = !sideFpsEnrolled || !powerButtonDown
                 if (!allowHaptic) {
                     logger.d("Skip error haptic. Power button is down.")
-                    handleErrorHaptic() // immediately handle, don't vibrate
                 }
                 allowHaptic
             }
-
-    fun vibrateSuccess() {
-        repository.requestSuccessHaptic()
-    }
-
-    fun vibrateError() {
-        repository.requestErrorHaptic()
-    }
-
-    fun handleSuccessHaptic() {
-        repository.handleSuccessHaptic()
-    }
-
-    fun handleErrorHaptic() {
-        repository.handleErrorHaptic()
-    }
+            .map {} // map to Unit
 
     private val recentPowerButtonPressThresholdMs = 400L
 }
diff --git a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractor.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractor.kt
index 4cddb9c..47be8ab 100644
--- a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractor.kt
@@ -23,6 +23,7 @@
 import com.android.systemui.deviceentry.data.repository.DeviceEntryRepository
 import com.android.systemui.keyguard.data.repository.DeviceEntryFaceAuthRepository
 import com.android.systemui.keyguard.data.repository.TrustRepository
+import com.android.systemui.keyguard.shared.model.BiometricUnlockSource
 import com.android.systemui.scene.domain.interactor.SceneInteractor
 import com.android.systemui.scene.shared.flag.SceneContainerFlags
 import com.android.systemui.scene.shared.model.SceneKey
@@ -30,6 +31,7 @@
 import javax.inject.Inject
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.SharingStarted
 import kotlinx.coroutines.flow.StateFlow
 import kotlinx.coroutines.flow.collectLatest
@@ -60,6 +62,9 @@
     trustRepository: TrustRepository,
     flags: SceneContainerFlags,
 ) {
+    val enteringDeviceFromBiometricUnlock: Flow<BiometricUnlockSource> =
+        repository.enteringDeviceFromBiometricUnlock
+
     /**
      * Whether the device is unlocked.
      *
diff --git a/packages/SystemUI/src/com/android/systemui/deviceentry/shared/DeviceEntryBiometricMode.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/shared/DeviceEntryBiometricMode.kt
new file mode 100644
index 0000000..6d885b3
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/deviceentry/shared/DeviceEntryBiometricMode.kt
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.deviceentry.shared
+
+/** Models the biometrics that can be used to enter the device. */
+enum class DeviceEntryBiometricMode {
+    /** No biometrics can be used to enter the device from the lockscreen. */
+    NONE,
+    /** Only face can be used to enter the device from the lockscreen. */
+    FACE_ONLY,
+    /** Only fingerprint can be used to enter the device from the lockscreen. */
+    FINGERPRINT_ONLY,
+    /** Both face and fingerprint can be used to enter the device from the lockscreen. */
+    CO_EXPERIENCE,
+}
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeAuthRemover.java b/packages/SystemUI/src/com/android/systemui/doze/DozeAuthRemover.java
index 9c13a8c..3fac865 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeAuthRemover.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeAuthRemover.java
@@ -43,7 +43,7 @@
         if (newState == DozeMachine.State.DOZE || newState == DozeMachine.State.DOZE_AOD) {
             int currentUser = mSelectedUserInteractor.getSelectedUserId();
             if (mKeyguardUpdateMonitor.getUserUnlockedWithBiometric(currentUser)) {
-                mKeyguardUpdateMonitor.clearBiometricRecognized();
+                mKeyguardUpdateMonitor.clearFingerprintRecognized();
             }
         }
     }
diff --git a/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsClassicDebug.java b/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsClassicDebug.java
index 87c12b4..72b0891 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsClassicDebug.java
+++ b/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsClassicDebug.java
@@ -25,7 +25,6 @@
 import static com.android.systemui.flags.FlagManager.EXTRA_VALUE;
 import static com.android.systemui.flags.FlagsCommonModule.ALL_FLAGS;
 import static com.android.systemui.shared.Flags.exampleSharedFlag;
-
 import static java.util.Objects.requireNonNull;
 
 import android.content.BroadcastReceiver;
@@ -508,9 +507,7 @@
                 enabled = isEnabled((ResourceBooleanFlag) f);
                 overridden = readBooleanFlagOverride(f.getName()) != null;
             } else if (f instanceof SysPropBooleanFlag) {
-                // TODO(b/223379190): Teamfood not supported for sysprop flags yet.
                 enabled = isEnabled((SysPropBooleanFlag) f);
-                teamfood = false;
                 overridden = !mSystemProperties.get(f.getName()).isEmpty();
             } else {
                 // TODO: add support for other flag types.
@@ -519,7 +516,7 @@
             }
 
             if (enabled) {
-                return new ReleasedFlag(f.getName(), f.getNamespace(), teamfood, overridden);
+                return new ReleasedFlag(f.getName(), f.getNamespace(), overridden);
             } else {
                 return new UnreleasedFlag(f.getName(), f.getNamespace(), teamfood, overridden);
             }
diff --git a/packages/SystemUI/src/com/android/systemui/flags/FlagDependencies.kt b/packages/SystemUI/src/com/android/systemui/flags/FlagDependencies.kt
index 6a0e882..d5b95d67 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/FlagDependencies.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/FlagDependencies.kt
@@ -16,7 +16,10 @@
 
 package com.android.systemui.flags
 
+import com.android.systemui.Flags.FLAG_KEYGUARD_BOTTOM_AREA_REFACTOR
+import com.android.systemui.Flags.keyguardBottomAreaRefactor
 import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.keyguard.shared.KeyguardShadeMigrationNssl
 import com.android.systemui.statusbar.notification.footer.shared.FooterViewRefactor
 import com.android.systemui.statusbar.notification.shared.NotificationIconContainerRefactor
 import com.android.systemui.statusbar.notification.shared.NotificationsLiveDataStoreRefactor
@@ -29,5 +32,9 @@
     override fun defineDependencies() {
         NotificationsLiveDataStoreRefactor.token dependsOn NotificationIconContainerRefactor.token
         FooterViewRefactor.token dependsOn NotificationIconContainerRefactor.token
+
+        val keyguardBottomAreaRefactor = FlagToken(
+                FLAG_KEYGUARD_BOTTOM_AREA_REFACTOR, keyguardBottomAreaRefactor())
+        KeyguardShadeMigrationNssl.token dependsOn keyguardBottomAreaRefactor
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
index 3009087..b7260f2 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
@@ -2611,14 +2611,14 @@
         }
 
         if (mGoingToSleep) {
-            mUpdateMonitor.clearBiometricRecognizedWhenKeyguardDone(currentUser);
+            mUpdateMonitor.clearFingerprintRecognizedWhenKeyguardDone(currentUser);
             Log.i(TAG, "Device is going to sleep, aborting keyguardDone");
             return;
         }
         setPendingLock(false); // user may have authenticated during the screen off animation
 
         handleHide();
-        mUpdateMonitor.clearBiometricRecognizedWhenKeyguardDone(currentUser);
+        mUpdateMonitor.clearFingerprintRecognizedWhenKeyguardDone(currentUser);
         Trace.endSection();
     }
 
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 e47c448..eceaf6c 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
@@ -326,7 +326,7 @@
                     it.selectionStatus == SelectionStatus.SELECTION_IN_PROGRESS
                 },
             )
-            .flowOn(backgroundDispatcher)
+            .flowOn(mainDispatcher) // should revoke auth ASAP in the main thread
             .onEach { anyOfThemIsTrue ->
                 if (anyOfThemIsTrue) {
                     clearPendingAuthRequest("Resetting auth status")
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractor.kt
index 0b6b971..7fdcf2f 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractor.kt
@@ -48,7 +48,7 @@
     override fun start() {
         listenForDreamingToOccluded()
         listenForDreamingToGone()
-        listenForDreamingToDozing()
+        listenForDreamingToAodOrDozing()
         listenForTransitionToCamera(scope, keyguardInteractor)
     }
 
@@ -94,7 +94,7 @@
         }
     }
 
-    private fun listenForDreamingToDozing() {
+    private fun listenForDreamingToAodOrDozing() {
         scope.launch {
             combine(
                     keyguardInteractor.dozeTransitionModel,
@@ -102,11 +102,12 @@
                     ::Pair
                 )
                 .collect { (dozeTransitionModel, keyguardState) ->
-                    if (
-                        dozeTransitionModel.to == DozeStateModel.DOZE &&
-                            keyguardState == KeyguardState.DREAMING
-                    ) {
-                        startTransitionTo(KeyguardState.DOZING)
+                    if (keyguardState == KeyguardState.DREAMING) {
+                        if (dozeTransitionModel.to == DozeStateModel.DOZE) {
+                            startTransitionTo(KeyguardState.DOZING)
+                        } else if (dozeTransitionModel.to == DozeStateModel.DOZE_AOD) {
+                            startTransitionTo(KeyguardState.AOD)
+                        }
                     }
                 }
         }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt
index cbfd17ff..9fe5c3f 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt
@@ -214,7 +214,7 @@
     private fun listenForLockscreenToPrimaryBouncerDragging() {
         var transitionId: UUID? = null
         scope.launch("$TAG#listenForLockscreenToPrimaryBouncerDragging") {
-            shadeRepository.shadeModel
+            shadeRepository.legacyShadeExpansion
                 .sample(
                     combine(
                         transitionInteractor.startedKeyguardTransitionStep,
@@ -224,23 +224,23 @@
                     ),
                     ::toQuad
                 )
-                .collect { (shadeModel, keyguardState, statusBarState, isKeyguardUnlocked) ->
+                .collect { (shadeExpansion, keyguardState, statusBarState, isKeyguardUnlocked) ->
                     val id = transitionId
                     if (id != null) {
                         if (keyguardState.to == KeyguardState.PRIMARY_BOUNCER) {
                             // An existing `id` means a transition is started, and calls to
                             // `updateTransition` will control it until FINISHED or CANCELED
                             var nextState =
-                                if (shadeModel.expansionAmount == 0f) {
+                                if (shadeExpansion == 0f) {
                                     TransitionState.FINISHED
-                                } else if (shadeModel.expansionAmount == 1f) {
+                                } else if (shadeExpansion == 1f) {
                                     TransitionState.CANCELED
                                 } else {
                                     TransitionState.RUNNING
                                 }
                             transitionRepository.updateTransition(
                                 id,
-                                1f - shadeModel.expansionAmount,
+                                1f - shadeExpansion,
                                 nextState,
                             )
 
@@ -274,7 +274,7 @@
                         // integrated into KeyguardTransitionRepository
                         if (
                             keyguardState.to == KeyguardState.LOCKSCREEN &&
-                                shadeModel.isUserDragging &&
+                                shadeRepository.legacyShadeTracking.value &&
                                 !isKeyguardUnlocked &&
                                 statusBarState == KEYGUARD
                         ) {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardFaceAuthInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardFaceAuthInteractor.kt
index 5ed70b5..046916a 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardFaceAuthInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardFaceAuthInteractor.kt
@@ -78,6 +78,9 @@
  * flows.
  */
 interface FaceAuthenticationListener {
+    /** Receive face isAuthenticated updates */
+    fun onAuthenticatedChanged(isAuthenticated: Boolean)
+
     /** Receive face authentication status updates */
     fun onAuthenticationStatusChanged(status: FaceAuthenticationStatus)
 
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt
index 702386d..c12efe8 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt
@@ -224,8 +224,8 @@
         configurationInteractor
             .dimensionPixelSize(R.dimen.keyguard_translate_distance_on_swipe_up)
             .flatMapLatest { translationDistance ->
-                shadeRepository.shadeModel.map {
-                    if (it.expansionAmount == 0f) {
+                shadeRepository.legacyShadeExpansion.map {
+                    if (it == 0f) {
                         // Reset the translation value
                         0f
                     } else {
@@ -233,7 +233,7 @@
                         MathUtils.lerp(
                             translationDistance,
                             0,
-                            Interpolators.FAST_OUT_LINEAR_IN.getInterpolation(it.expansionAmount)
+                            Interpolators.FAST_OUT_LINEAR_IN.getInterpolation(it)
                         )
                     }
                 }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractor.kt
index 448411e..7882a97 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractor.kt
@@ -46,6 +46,7 @@
 import com.android.systemui.plugins.ActivityStarter
 import com.android.systemui.res.R
 import com.android.systemui.settings.UserTracker
+import com.android.systemui.shade.domain.interactor.ShadeInteractor
 import com.android.systemui.shared.customization.data.content.CustomizationProviderContract as Contract
 import com.android.systemui.statusbar.phone.SystemUIDialog
 import com.android.systemui.statusbar.policy.KeyguardStateController
@@ -66,6 +67,7 @@
 @Inject
 constructor(
     private val keyguardInteractor: KeyguardInteractor,
+    private val shadeInteractor: ShadeInteractor,
     private val lockPatternUtils: LockPatternUtils,
     private val keyguardStateController: KeyguardStateController,
     private val userTracker: UserTracker,
@@ -100,9 +102,10 @@
             quickAffordanceAlwaysVisible(position),
             keyguardInteractor.isDozing,
             keyguardInteractor.isKeyguardShowing,
+            shadeInteractor.anyExpansion,
             biometricSettingsRepository.isCurrentUserInLockdown,
-        ) { affordance, isDozing, isKeyguardShowing, isUserInLockdown ->
-            if (!isDozing && isKeyguardShowing && !isUserInLockdown) {
+        ) { affordance, isDozing, isKeyguardShowing, qsExpansion, isUserInLockdown ->
+            if (!isDozing && isKeyguardShowing && (qsExpansion < 1.0f) && !isUserInLockdown) {
                 affordance
             } else {
                 KeyguardQuickAffordanceModel.Hidden
@@ -117,10 +120,14 @@
      * This is useful for experiences like the lock screen preview mode, where the affordances must
      * always be visible.
      */
-    fun quickAffordanceAlwaysVisible(
+    suspend fun quickAffordanceAlwaysVisible(
         position: KeyguardQuickAffordancePosition,
     ): Flow<KeyguardQuickAffordanceModel> {
-        return quickAffordanceInternal(position)
+        return if (isFeatureDisabledByDevicePolicy()) {
+            flowOf(KeyguardQuickAffordanceModel.Hidden)
+        } else {
+            quickAffordanceInternal(position)
+        }
     }
 
     /**
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/SystemUIKeyguardFaceAuthInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/SystemUIKeyguardFaceAuthInteractor.kt
index 532df4a..fb20000 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/SystemUIKeyguardFaceAuthInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/SystemUIKeyguardFaceAuthInteractor.kt
@@ -16,8 +16,10 @@
 
 package com.android.systemui.keyguard.domain.interactor
 
+import android.app.trust.TrustManager
 import android.content.Context
 import android.hardware.biometrics.BiometricFaceConstants
+import android.hardware.biometrics.BiometricSourceType
 import com.android.keyguard.FaceAuthUiEvent
 import com.android.keyguard.FaceWakeUpTriggersConfig
 import com.android.keyguard.KeyguardUpdateMonitor
@@ -83,6 +85,7 @@
     private val faceWakeUpTriggersConfig: FaceWakeUpTriggersConfig,
     private val powerInteractor: PowerInteractor,
     private val biometricSettingsRepository: BiometricSettingsRepository,
+    private val trustManager: TrustManager,
 ) : CoreStartable, KeyguardFaceAuthInteractor {
 
     private val listeners: MutableList<FaceAuthenticationListener> = mutableListOf()
@@ -291,6 +294,20 @@
             .onEach { running -> listeners.forEach { it.onRunningStateChanged(running) } }
             .flowOn(mainDispatcher)
             .launchIn(applicationScope)
+        repository.isAuthenticated
+            .sample(userRepository.selectedUserInfo, ::Pair)
+            .onEach { (isAuthenticated, userInfo) ->
+                if (!isAuthenticated) {
+                    faceAuthenticationLogger.clearFaceRecognized()
+                    trustManager.clearAllBiometricRecognized(BiometricSourceType.FACE, userInfo.id)
+                }
+            }
+            .flowOn(backgroundDispatcher)
+            .onEach { (isAuthenticated, _) ->
+                listeners.forEach { it.onAuthenticatedChanged(isAuthenticated) }
+            }
+            .flowOn(mainDispatcher)
+            .launchIn(applicationScope)
 
         biometricSettingsRepository.isFaceAuthEnrolledAndEnabled
             .onEach { enrolledAndEnabled ->
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/BiometricUnlockModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/BiometricUnlockModel.kt
index 8fe6309f..2ae5ce1 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/BiometricUnlockModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/BiometricUnlockModel.kt
@@ -51,9 +51,21 @@
     companion object {
         private val wakeAndUnlockModes =
             setOf(WAKE_AND_UNLOCK, WAKE_AND_UNLOCK_FROM_DREAM, WAKE_AND_UNLOCK_PULSING)
+        private val dismissesKeyguardModes =
+            setOf(
+                WAKE_AND_UNLOCK,
+                WAKE_AND_UNLOCK_PULSING,
+                UNLOCK_COLLAPSING,
+                WAKE_AND_UNLOCK_FROM_DREAM,
+                DISMISS_BOUNCER
+            )
 
         fun isWakeAndUnlock(model: BiometricUnlockModel): Boolean {
             return wakeAndUnlockModes.contains(model)
         }
+
+        fun dismissesKeyguard(model: BiometricUnlockModel): Boolean {
+            return dismissesKeyguardModes.contains(model)
+        }
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/FingerprintAuthenticationModels.kt b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/FingerprintAuthenticationModels.kt
index 3c143fe..cc385a8 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/FingerprintAuthenticationModels.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/FingerprintAuthenticationModels.kt
@@ -49,7 +49,7 @@
 }
 
 /** Fingerprint authentication failed message. */
-object FailFingerprintAuthenticationStatus : FingerprintAuthenticationStatus()
+data object FailFingerprintAuthenticationStatus : FingerprintAuthenticationStatus()
 
 /** Fingerprint authentication error message */
 data class ErrorFingerprintAuthenticationStatus(
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/DeviceEntryIconViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/DeviceEntryIconViewBinder.kt
index 4b4a19e..dcf4284 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/DeviceEntryIconViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/DeviceEntryIconViewBinder.kt
@@ -19,6 +19,7 @@
 
 import android.annotation.SuppressLint
 import android.content.res.ColorStateList
+import android.view.HapticFeedbackConstants
 import android.view.View
 import androidx.lifecycle.Lifecycle
 import androidx.lifecycle.repeatOnLifecycle
@@ -30,6 +31,7 @@
 import com.android.systemui.keyguard.ui.viewmodel.DeviceEntryIconViewModel
 import com.android.systemui.lifecycle.repeatWhenAttached
 import com.android.systemui.plugins.FalsingManager
+import com.android.systemui.statusbar.VibratorHelper
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.launch
 
@@ -51,6 +53,7 @@
         fgViewModel: DeviceEntryForegroundViewModel,
         bgViewModel: DeviceEntryBackgroundViewModel,
         falsingManager: FalsingManager,
+        vibratorHelper: VibratorHelper,
     ) {
         DeviceEntryUdfpsRefactor.isUnexpectedlyInLegacyMode()
         val longPressHandlingView = view.longPressHandlingView
@@ -62,6 +65,10 @@
                     if (falsingManager.isFalseLongTap(FalsingManager.LOW_PENALTY)) {
                         return
                     }
+                    vibratorHelper.performHapticFeedback(
+                        view,
+                        HapticFeedbackConstants.CONFIRM,
+                    )
                     viewModel.onLongPress()
                 }
             }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt
index e603ead..01a1ca3 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt
@@ -68,7 +68,6 @@
 import kotlinx.coroutines.DisposableHandle
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.filter
 import kotlinx.coroutines.flow.stateIn
 import kotlinx.coroutines.launch
 
@@ -253,27 +252,21 @@
 
                     if (deviceEntryHapticsInteractor != null && vibratorHelper != null) {
                         launch {
-                            deviceEntryHapticsInteractor.playSuccessHaptic
-                                .filter { it }
-                                .collect {
-                                    vibratorHelper.performHapticFeedback(
-                                        view,
-                                        HapticFeedbackConstants.CONFIRM,
-                                    )
-                                    deviceEntryHapticsInteractor.handleSuccessHaptic()
-                                }
+                            deviceEntryHapticsInteractor.playSuccessHaptic.collect {
+                                vibratorHelper.performHapticFeedback(
+                                    view,
+                                    HapticFeedbackConstants.CONFIRM,
+                                )
+                            }
                         }
 
                         launch {
-                            deviceEntryHapticsInteractor.playErrorHaptic
-                                .filter { it }
-                                .collect {
-                                    vibratorHelper.performHapticFeedback(
-                                        view,
-                                        HapticFeedbackConstants.REJECT,
-                                    )
-                                    deviceEntryHapticsInteractor.handleErrorHaptic()
-                                }
+                            deviceEntryHapticsInteractor.playErrorHaptic.collect {
+                                vibratorHelper.performHapticFeedback(
+                                    view,
+                                    HapticFeedbackConstants.REJECT,
+                                )
+                            }
                         }
                     }
                 }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AodNotificationIconsSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AodNotificationIconsSection.kt
index 96efb23..39a0547 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AodNotificationIconsSection.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AodNotificationIconsSection.kt
@@ -40,7 +40,7 @@
 import com.android.systemui.statusbar.notification.shared.NotificationIconContainerRefactor
 import com.android.systemui.statusbar.phone.NotificationIconAreaController
 import com.android.systemui.statusbar.phone.NotificationIconContainer
-import com.android.systemui.statusbar.policy.ConfigurationController
+import com.android.systemui.statusbar.ui.SystemBarUtilsState
 import javax.inject.Inject
 import kotlinx.coroutines.DisposableHandle
 
@@ -49,13 +49,13 @@
 constructor(
     private val context: Context,
     private val configurationState: ConfigurationState,
-    private val configurationController: ConfigurationController,
     private val featureFlags: FeatureFlagsClassic,
     private val iconBindingFailureTracker: StatusBarIconViewBindingFailureTracker,
     private val nicAodViewModel: NotificationIconContainerAlwaysOnDisplayViewModel,
     private val nicAodIconViewStore: AlwaysOnDisplayNotificationIconViewStore,
     private val notificationIconAreaController: NotificationIconAreaController,
     private val smartspaceViewModel: KeyguardSmartspaceViewModel,
+    private val systemBarUtilsState: SystemBarUtilsState,
 ) : KeyguardSection() {
 
     private var nicBindingDisposable: DisposableHandle? = null
@@ -89,11 +89,11 @@
         if (NotificationIconContainerRefactor.isEnabled) {
             nicBindingDisposable?.dispose()
             nicBindingDisposable =
-                NotificationIconContainerViewBinder.bind(
+                NotificationIconContainerViewBinder.bindWhileAttached(
                     nic,
                     nicAodViewModel,
                     configurationState,
-                    configurationController,
+                    systemBarUtilsState,
                     iconBindingFailureTracker,
                     nicAodIconViewStore,
                 )
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultDeviceEntrySection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultDeviceEntrySection.kt
index 77ab9f4..a693ec9 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultDeviceEntrySection.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultDeviceEntrySection.kt
@@ -49,6 +49,7 @@
 import com.android.systemui.res.R
 import com.android.systemui.shade.NotificationPanelView
 import com.android.systemui.statusbar.NotificationShadeWindowController
+import com.android.systemui.statusbar.VibratorHelper
 import com.android.systemui.statusbar.gesture.TapGestureDetector
 import dagger.Lazy
 import javax.inject.Inject
@@ -76,6 +77,7 @@
     @Application private val scope: CoroutineScope,
     private val swipeUpAnywhereGestureHandler: Lazy<SwipeUpAnywhereGestureHandler>,
     private val tapGestureDetector: Lazy<TapGestureDetector>,
+    private val vibratorHelper: Lazy<VibratorHelper>,
 ) : KeyguardSection() {
     private val deviceEntryIconViewId = R.id.device_entry_icon_view
     private val alternateBouncerViewId = R.id.alternate_bouncer
@@ -114,6 +116,7 @@
                     deviceEntryForegroundViewModel.get(),
                     deviceEntryBackgroundViewModel.get(),
                     falsingManager.get(),
+                    vibratorHelper.get(),
                 )
             }
             constraintLayout.findViewById<FrameLayout?>(alternateBouncerViewId)?.let {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultNotificationStackScrollLayoutSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultNotificationStackScrollLayoutSection.kt
index a64a422..e7b6e44 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultNotificationStackScrollLayoutSection.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultNotificationStackScrollLayoutSection.kt
@@ -26,7 +26,6 @@
 import androidx.constraintlayout.widget.ConstraintSet.TOP
 import com.android.systemui.Flags.migrateClocksToBlueprint
 import com.android.systemui.dagger.qualifiers.Main
-import com.android.systemui.deviceentry.shared.DeviceEntryUdfpsRefactor
 import com.android.systemui.keyguard.shared.KeyguardShadeMigrationNssl
 import com.android.systemui.keyguard.ui.viewmodel.KeyguardSmartspaceViewModel
 import com.android.systemui.res.R
@@ -92,13 +91,7 @@
             connect(R.id.nssl_placeholder, START, PARENT_ID, START)
             connect(R.id.nssl_placeholder, END, PARENT_ID, END)
 
-            val lockId =
-                if (DeviceEntryUdfpsRefactor.isEnabled) {
-                    R.id.device_entry_icon_view
-                } else {
-                    R.id.lock_icon_view
-                }
-            connect(R.id.nssl_placeholder, BOTTOM, lockId, TOP)
+            addNotificationPlaceholderBarrier(this)
         }
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/NotificationStackScrollLayoutSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/NotificationStackScrollLayoutSection.kt
index a25471c..400d0dc 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/NotificationStackScrollLayoutSection.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/NotificationStackScrollLayoutSection.kt
@@ -20,7 +20,12 @@
 import android.content.Context
 import android.view.View
 import android.view.ViewGroup
+import androidx.constraintlayout.widget.Barrier
 import androidx.constraintlayout.widget.ConstraintLayout
+import androidx.constraintlayout.widget.ConstraintSet
+import androidx.constraintlayout.widget.ConstraintSet.BOTTOM
+import androidx.constraintlayout.widget.ConstraintSet.TOP
+import com.android.systemui.deviceentry.shared.DeviceEntryUdfpsRefactor
 import com.android.systemui.keyguard.shared.KeyguardShadeMigrationNssl
 import com.android.systemui.keyguard.shared.model.KeyguardSection
 import com.android.systemui.res.R
@@ -54,6 +59,29 @@
     private val placeHolderId = R.id.nssl_placeholder
     private var disposableHandle: DisposableHandle? = null
 
+    /**
+     * Align the notification placeholder bottom to the top of either the lock icon or the ambient
+     * indication area, whichever is higher.
+     */
+    protected fun addNotificationPlaceholderBarrier(constraintSet: ConstraintSet) {
+        val lockId =
+            if (DeviceEntryUdfpsRefactor.isEnabled) {
+                R.id.device_entry_icon_view
+            } else {
+                R.id.lock_icon_view
+            }
+
+        constraintSet.apply {
+            createBarrier(
+                R.id.nssl_placeholder_barrier_bottom,
+                Barrier.TOP,
+                0,
+                *intArrayOf(lockId, R.id.ambient_indication_container)
+            )
+            connect(R.id.nssl_placeholder, BOTTOM, R.id.nssl_placeholder_barrier_bottom, TOP)
+        }
+    }
+
     override fun addViews(constraintLayout: ConstraintLayout) {
         if (!KeyguardShadeMigrationNssl.isEnabled) {
             return
@@ -85,9 +113,9 @@
             )
         if (sceneContainerFlags.flexiNotifsEnabled()) {
             NotificationStackAppearanceViewBinder.bind(
+                context,
                 sharedNotificationContainer,
                 notificationStackAppearanceViewModel,
-                sceneContainerFlags,
                 ambientState,
                 controller,
             )
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/SplitShadeNotificationStackScrollLayoutSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/SplitShadeNotificationStackScrollLayoutSection.kt
index f5963be..b0b5c81 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/SplitShadeNotificationStackScrollLayoutSection.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/SplitShadeNotificationStackScrollLayoutSection.kt
@@ -19,14 +19,12 @@
 
 import android.content.Context
 import androidx.constraintlayout.widget.ConstraintSet
-import androidx.constraintlayout.widget.ConstraintSet.BOTTOM
 import androidx.constraintlayout.widget.ConstraintSet.END
 import androidx.constraintlayout.widget.ConstraintSet.PARENT_ID
 import androidx.constraintlayout.widget.ConstraintSet.START
 import androidx.constraintlayout.widget.ConstraintSet.TOP
 import com.android.systemui.Flags.migrateClocksToBlueprint
 import com.android.systemui.dagger.qualifiers.Main
-import com.android.systemui.deviceentry.shared.DeviceEntryUdfpsRefactor
 import com.android.systemui.keyguard.shared.KeyguardShadeMigrationNssl
 import com.android.systemui.keyguard.ui.viewmodel.KeyguardSmartspaceViewModel
 import com.android.systemui.res.R
@@ -97,13 +95,7 @@
             connect(R.id.nssl_placeholder, START, PARENT_ID, START)
             connect(R.id.nssl_placeholder, END, PARENT_ID, END)
 
-            val lockId =
-                if (DeviceEntryUdfpsRefactor.isEnabled) {
-                    R.id.device_entry_icon_view
-                } else {
-                    R.id.lock_icon_view
-                }
-            connect(R.id.nssl_placeholder, BOTTOM, lockId, TOP)
+            addNotificationPlaceholderBarrier(this)
         }
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryIconViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryIconViewModel.kt
index bd6aae8..f95713b 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryIconViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryIconViewModel.kt
@@ -19,7 +19,6 @@
 import android.animation.FloatEvaluator
 import android.animation.IntEvaluator
 import com.android.keyguard.KeyguardViewController
-import com.android.systemui.deviceentry.domain.interactor.DeviceEntryHapticsInteractor
 import com.android.systemui.deviceentry.domain.interactor.DeviceEntryInteractor
 import com.android.systemui.deviceentry.domain.interactor.DeviceEntryUdfpsInteractor
 import com.android.systemui.keyguard.domain.interactor.BurnInInteractor
@@ -56,7 +55,6 @@
     val shadeDependentFlows: ShadeDependentFlows,
     private val sceneContainerFlags: SceneContainerFlags,
     private val keyguardViewController: Lazy<KeyguardViewController>,
-    private val deviceEntryHapticsInteractor: DeviceEntryHapticsInteractor,
     private val deviceEntryInteractor: DeviceEntryInteractor,
 ) {
     private val intEvaluator = IntEvaluator()
@@ -182,8 +180,6 @@
         }
 
     fun onLongPress() {
-        deviceEntryHapticsInteractor.vibrateSuccess()
-
         // TODO (b/309804148): play auth ripple via an interactor
 
         if (sceneContainerFlags.isEnabled()) {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt
index 4588e02..1d4520f 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt
@@ -111,10 +111,21 @@
 
     /** An observable for the alpha level for the entire keyguard root view. */
     val alpha: Flow<Float> =
-        merge(
-            keyguardInteractor.keyguardAlpha.distinctUntilChanged(),
-            occludedToLockscreenTransitionViewModel.lockscreenAlpha,
-        )
+        combine(
+                keyguardTransitionInteractor.transitionValue(GONE).onStart { emit(0f) },
+                merge(
+                    keyguardInteractor.keyguardAlpha,
+                    occludedToLockscreenTransitionViewModel.lockscreenAlpha,
+                )
+            ) { transitionToGone, alpha ->
+                if (transitionToGone == 1f) {
+                    // Ensures content is not visible when in GONE state
+                    0f
+                } else {
+                    alpha
+                }
+            }
+            .distinctUntilChanged()
 
     private fun burnIn(): Flow<BurnInModel> {
         val dozingAmount: Flow<Float> =
@@ -229,7 +240,9 @@
             .distinctUntilChanged()
 
     fun onNotificationContainerBoundsChanged(top: Float, bottom: Float) {
-        keyguardInteractor.setNotificationContainerBounds(NotificationContainerBounds(top, bottom))
+        keyguardInteractor.setNotificationContainerBounds(
+            NotificationContainerBounds(top = top, bottom = bottom)
+        )
     }
 
     /** Is there an expanded pulse, are we animating in response? */
diff --git a/packages/SystemUI/src/com/android/systemui/log/FaceAuthenticationLogger.kt b/packages/SystemUI/src/com/android/systemui/log/FaceAuthenticationLogger.kt
index 8c5690b..3c2facb 100644
--- a/packages/SystemUI/src/com/android/systemui/log/FaceAuthenticationLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/log/FaceAuthenticationLogger.kt
@@ -132,6 +132,10 @@
         logBuffer.log(TAG, DEBUG, "Face authentication failed")
     }
 
+    fun clearFaceRecognized() {
+        logBuffer.log(TAG, DEBUG, "Clear face recognized")
+    }
+
     fun authenticationError(
         errorCode: Int,
         errString: CharSequence?,
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/RecordIssueTile.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/RecordIssueTile.kt
new file mode 100644
index 0000000..a4088f8
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/RecordIssueTile.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.qs.tiles
+
+import android.content.Intent
+import android.os.Handler
+import android.os.Looper
+import android.service.quicksettings.Tile
+import android.text.TextUtils
+import android.view.View
+import android.widget.Switch
+import androidx.annotation.VisibleForTesting
+import com.android.internal.logging.MetricsLogger
+import com.android.systemui.Flags.recordIssueQsTile
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.plugins.ActivityStarter
+import com.android.systemui.plugins.FalsingManager
+import com.android.systemui.plugins.qs.QSTile
+import com.android.systemui.plugins.statusbar.StatusBarStateController
+import com.android.systemui.qs.QSHost
+import com.android.systemui.qs.QsEventLogger
+import com.android.systemui.qs.logging.QSLogger
+import com.android.systemui.qs.tileimpl.QSTileImpl
+import com.android.systemui.res.R
+import javax.inject.Inject
+
+class RecordIssueTile
+@Inject
+constructor(
+    host: QSHost,
+    uiEventLogger: QsEventLogger,
+    @Background backgroundLooper: Looper,
+    @Main mainHandler: Handler,
+    falsingManager: FalsingManager,
+    metricsLogger: MetricsLogger,
+    statusBarStateController: StatusBarStateController,
+    activityStarter: ActivityStarter,
+    qsLogger: QSLogger
+) :
+    QSTileImpl<QSTile.BooleanState>(
+        host,
+        uiEventLogger,
+        backgroundLooper,
+        mainHandler,
+        falsingManager,
+        metricsLogger,
+        statusBarStateController,
+        activityStarter,
+        qsLogger
+    ) {
+
+    @VisibleForTesting var isRecording: Boolean = false
+
+    override fun getTileLabel(): CharSequence = mContext.getString(R.string.qs_record_issue_label)
+
+    override fun isAvailable(): Boolean = recordIssueQsTile()
+
+    override fun newTileState(): QSTile.BooleanState =
+        QSTile.BooleanState().apply {
+            label = tileLabel
+            handlesLongClick = false
+        }
+
+    override fun handleClick(view: View?) {
+        isRecording = !isRecording
+        refreshState()
+    }
+
+    override fun getLongClickIntent(): Intent? = null
+
+    @VisibleForTesting
+    public override fun handleUpdateState(qsTileState: QSTile.BooleanState, arg: Any?) {
+        qsTileState.apply {
+            if (isRecording) {
+                value = true
+                state = Tile.STATE_ACTIVE
+                forceExpandIcon = false
+                secondaryLabel = mContext.getString(R.string.qs_record_issue_stop)
+                icon = ResourceIcon.get(R.drawable.qs_record_issue_icon_on)
+            } else {
+                value = false
+                state = Tile.STATE_INACTIVE
+                forceExpandIcon = true
+                secondaryLabel = mContext.getString(R.string.qs_record_issue_start)
+                icon = ResourceIcon.get(R.drawable.qs_record_issue_icon_off)
+            }
+            label = tileLabel
+            contentDescription =
+                if (TextUtils.isEmpty(secondaryLabel)) label else "$label, $secondaryLabel"
+            expandedAccessibilityClassName = Switch::class.java.name
+        }
+    }
+
+    companion object {
+        const val TILE_SPEC = "record_issue"
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/recordissue/RecordIssueModule.kt b/packages/SystemUI/src/com/android/systemui/recordissue/RecordIssueModule.kt
new file mode 100644
index 0000000..d67cf4d
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/recordissue/RecordIssueModule.kt
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.recordissue
+
+import com.android.systemui.qs.tileimpl.QSTileImpl
+import com.android.systemui.qs.tiles.RecordIssueTile
+import dagger.Binds
+import dagger.Module
+import dagger.multibindings.IntoMap
+import dagger.multibindings.StringKey
+
+@Module
+interface RecordIssueModule {
+    /** Inject RecordIssueTile into tileMap in QSModule */
+    @Binds
+    @IntoMap
+    @StringKey(RecordIssueTile.TILE_SPEC)
+    fun bindRecordIssueTile(recordIssueTile: RecordIssueTile): QSTileImpl<*>
+}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java
index e9779cd..5fbb60d 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java
@@ -59,6 +59,7 @@
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.plugins.statusbar.StatusBarStateController.StateListener;
 import com.android.systemui.res.R;
+import com.android.systemui.scene.shared.flag.SceneContainerFlags;
 import com.android.systemui.scene.ui.view.WindowRootViewComponent;
 import com.android.systemui.settings.UserTracker;
 import com.android.systemui.shade.domain.interactor.ShadeInteractor;
@@ -116,6 +117,7 @@
     private final AuthController mAuthController;
     private final Lazy<SelectedUserInteractor> mUserInteractor;
     private final Lazy<ShadeInteractor> mShadeInteractorLazy;
+    private final SceneContainerFlags mSceneContainerFlags;
     private ViewGroup mWindowRootView;
     private LayoutParams mLp;
     private boolean mHasTopUi;
@@ -162,7 +164,8 @@
             Lazy<ShadeInteractor> shadeInteractorLazy,
             ShadeWindowLogger logger,
             Lazy<SelectedUserInteractor> userInteractor,
-            UserTracker userTracker) {
+            UserTracker userTracker,
+            SceneContainerFlags sceneContainerFlags) {
         mContext = context;
         mWindowRootViewComponentFactory = windowRootViewComponentFactory;
         mWindowManager = windowManager;
@@ -180,6 +183,7 @@
         dumpManager.registerDumpable(this);
         mAuthController = authController;
         mUserInteractor = userInteractor;
+        mSceneContainerFlags = sceneContainerFlags;
         mLastKeyguardRotationAllowed = mKeyguardStateController.isKeyguardScreenRotationAllowed();
         mLockScreenDisplayTimeout = context.getResources()
                 .getInteger(R.integer.config_lockScreenDisplayTimeout);
@@ -287,6 +291,15 @@
         mLp.privateFlags |= PRIVATE_FLAG_BEHAVIOR_CONTROLLED;
         mLp.insetsFlags.behavior = BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE;
 
+        if (mSceneContainerFlags.isEnabled()) {
+            // This prevents the appearance and disappearance of the software keyboard (also known
+            // as the "IME") from scrolling/panning the window to make room for the keyboard.
+            //
+            // The scene container logic does its own adjustment and animation when the IME appears
+            // or disappears.
+            mLp.softInputMode = LayoutParams.SOFT_INPUT_ADJUST_NOTHING;
+        }
+
         mWindowManager.addView(mWindowRootView, mLp);
 
         mLpChanged.copyFrom(mLp);
diff --git a/packages/SystemUI/src/com/android/systemui/shade/data/repository/ShadeRepository.kt b/packages/SystemUI/src/com/android/systemui/shade/data/repository/ShadeRepository.kt
index e94a3eb..2445bdb 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/data/repository/ShadeRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/data/repository/ShadeRepository.kt
@@ -15,25 +15,14 @@
  */
 package com.android.systemui.shade.data.repository
 
-import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging
-import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
 import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.shade.ShadeExpansionChangeEvent
-import com.android.systemui.shade.ShadeExpansionListener
-import com.android.systemui.shade.ShadeExpansionStateManager
-import com.android.systemui.shade.domain.model.ShadeModel
 import javax.inject.Inject
-import kotlinx.coroutines.channels.awaitClose
-import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.StateFlow
 import kotlinx.coroutines.flow.asStateFlow
-import kotlinx.coroutines.flow.distinctUntilChanged
 
+/** Data for the shade, mostly related to expansion of the shade and quick settings. */
 interface ShadeRepository {
-    /** ShadeModel information regarding shade expansion events */
-    val shadeModel: Flow<ShadeModel>
-
     /**
      * Amount qs has expanded, [0-1]. 0 means fully collapsed, 1 means fully expanded. Quick
      * Settings can be expanded without the full shade expansion.
@@ -167,34 +156,7 @@
 
 /** Business logic for shade interactions */
 @SysUISingleton
-class ShadeRepositoryImpl
-@Inject
-constructor(shadeExpansionStateManager: ShadeExpansionStateManager) : ShadeRepository {
-    override val shadeModel: Flow<ShadeModel> =
-        conflatedCallbackFlow {
-                val callback =
-                    object : ShadeExpansionListener {
-                        override fun onPanelExpansionChanged(event: ShadeExpansionChangeEvent) {
-                            // Don't propagate ShadeExpansionChangeEvent.dragDownPxAmount field.
-                            // It is too noisy and produces extra events that consumers won't care
-                            // about
-                            val info =
-                                ShadeModel(
-                                    expansionAmount = event.fraction,
-                                    isExpanded = event.expanded,
-                                    isUserDragging = event.tracking
-                                )
-                            trySendWithFailureLogging(info, TAG, "updated shade expansion info")
-                        }
-                    }
-
-                val currentState = shadeExpansionStateManager.addExpansionListener(callback)
-                callback.onPanelExpansionChanged(currentState)
-
-                awaitClose { shadeExpansionStateManager.removeExpansionListener(callback) }
-            }
-            .distinctUntilChanged()
-
+class ShadeRepositoryImpl @Inject constructor() : ShadeRepository {
     private val _qsExpansion = MutableStateFlow(0f)
     override val qsExpansion: StateFlow<Float> = _qsExpansion.asStateFlow()
 
diff --git a/packages/SystemUI/src/com/android/systemui/shade/domain/model/ShadeModel.kt b/packages/SystemUI/src/com/android/systemui/shade/domain/model/ShadeModel.kt
deleted file mode 100644
index ce0f4283..0000000
--- a/packages/SystemUI/src/com/android/systemui/shade/domain/model/ShadeModel.kt
+++ /dev/null
@@ -1,28 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-package com.android.systemui.shade.domain.model
-
-import android.annotation.FloatRange
-
-/** Information about shade (NotificationPanel) expansion */
-data class ShadeModel(
-    /** 0 when collapsed, 1 when fully expanded. */
-    @FloatRange(from = 0.0, to = 1.0) val expansionAmount: Float = 0f,
-    /** Whether the panel should be considered expanded */
-    val isExpanded: Boolean = false,
-    /** Whether the user is actively dragging the panel. */
-    val isUserDragging: Boolean = false,
-)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/AlertingNotificationManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/AlertingNotificationManager.java
index faffb3e..d23c85a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/AlertingNotificationManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/AlertingNotificationManager.java
@@ -19,7 +19,6 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.os.Handler;
-import android.os.SystemClock;
 import android.util.ArrayMap;
 import android.util.ArraySet;
 import android.view.accessibility.AccessibilityEvent;
@@ -29,6 +28,7 @@
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.InflationFlag;
 import com.android.systemui.statusbar.policy.HeadsUpManagerLogger;
+import com.android.systemui.util.time.SystemClock;
 
 import java.util.stream.Stream;
 
@@ -39,21 +39,23 @@
  */
 public abstract class AlertingNotificationManager {
     private static final String TAG = "AlertNotifManager";
-    protected final Clock mClock = new Clock();
+    protected final SystemClock mSystemClock;
     protected final ArrayMap<String, AlertEntry> mAlertEntries = new ArrayMap<>();
     protected final HeadsUpManagerLogger mLogger;
 
-    public AlertingNotificationManager(HeadsUpManagerLogger logger, @Main Handler handler) {
-        mLogger = logger;
-        mHandler = handler;
-    }
-
     protected int mMinimumDisplayTime;
-    protected int mStickyDisplayTime;
-    protected int mAutoDismissNotificationDecay;
+    protected int mStickyForSomeTimeAutoDismissTime;
+    protected int mAutoDismissTime;
     @VisibleForTesting
     public Handler mHandler;
 
+    public AlertingNotificationManager(HeadsUpManagerLogger logger, @Main Handler handler,
+            SystemClock systemClock) {
+        mLogger = logger;
+        mHandler = handler;
+        mSystemClock = systemClock;
+    }
+
     /**
      * Called when posting a new notification that should alert the user and appear on screen.
      * Adds the notification to be managed.
@@ -251,7 +253,7 @@
     public long getEarliestRemovalTime(String key) {
         AlertEntry alerting = mAlertEntries.get(key);
         if (alerting != null) {
-            return Math.max(0, alerting.mEarliestRemovaltime - mClock.currentTimeMillis());
+            return Math.max(0, alerting.mEarliestRemovalTime - mSystemClock.elapsedRealtime());
         }
         return 0;
     }
@@ -259,7 +261,7 @@
     protected class AlertEntry implements Comparable<AlertEntry> {
         @Nullable public NotificationEntry mEntry;
         public long mPostTime;
-        public long mEarliestRemovaltime;
+        public long mEarliestRemovalTime;
 
         @Nullable protected Runnable mRemoveAlertRunnable;
 
@@ -283,8 +285,8 @@
         public void updateEntry(boolean updatePostTime, @Nullable String reason) {
             mLogger.logUpdateEntry(mEntry, updatePostTime, reason);
 
-            final long now = mClock.currentTimeMillis();
-            mEarliestRemovaltime = now + mMinimumDisplayTime;
+            final long now = mSystemClock.elapsedRealtime();
+            mEarliestRemovalTime = now + mMinimumDisplayTime;
 
             if (updatePostTime) {
                 mPostTime = Math.max(mPostTime, now);
@@ -318,7 +320,7 @@
          * @return true if the notification has been on screen long enough
          */
         public boolean wasShownLongEnough() {
-            return mEarliestRemovaltime < mClock.currentTimeMillis();
+            return mEarliestRemovalTime < mSystemClock.elapsedRealtime();
         }
 
         @Override
@@ -351,7 +353,7 @@
             if (mRemoveAlertRunnable != null) {
                 removeAutoRemovalCallbacks("removeAsSoonAsPossible (will be rescheduled)");
 
-                final long timeLeft = mEarliestRemovaltime - mClock.currentTimeMillis();
+                final long timeLeft = mEarliestRemovalTime - mSystemClock.elapsedRealtime();
                 mHandler.postDelayed(mRemoveAlertRunnable, timeLeft);
             }
         }
@@ -361,22 +363,16 @@
          * @return the post time
          */
         protected long calculatePostTime() {
-            return mClock.currentTimeMillis();
+            return mSystemClock.elapsedRealtime();
         }
 
         /**
          * @return When the notification should auto-dismiss itself, based on
-         * {@link SystemClock#elapsedRealTime()}
+         * {@link SystemClock#elapsedRealtime()}
          */
         protected long calculateFinishTime() {
             // Overridden by HeadsUpManager HeadsUpEntry #calculateFinishTime
             return 0;
         }
     }
-
-    protected final static class Clock {
-        public long currentTimeMillis() {
-            return SystemClock.elapsedRealtime();
-        }
-    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarModule.kt b/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarModule.kt
index 22912df..85f4c36 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarModule.kt
@@ -20,6 +20,7 @@
 import com.android.systemui.statusbar.data.StatusBarDataLayerModule
 import com.android.systemui.statusbar.phone.LightBarController
 import com.android.systemui.statusbar.phone.ongoingcall.OngoingCallController
+import com.android.systemui.statusbar.ui.SystemBarUtilsProxyImpl
 import dagger.Binds
 import dagger.Module
 import dagger.multibindings.ClassKey
@@ -33,7 +34,7 @@
  *   ([com.android.systemui.statusbar.pipeline.dagger.StatusBarPipelineModule],
  *   [com.android.systemui.statusbar.policy.dagger.StatusBarPolicyModule], etc.).
  */
-@Module(includes = [StatusBarDataLayerModule::class])
+@Module(includes = [StatusBarDataLayerModule::class, SystemBarUtilsProxyImpl.Module::class])
 abstract class StatusBarModule {
     @Binds
     @IntoMap
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/NotificationIconContainerViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/NotificationIconContainerViewBinder.kt
index ecca973..92391e7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/NotificationIconContainerViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/NotificationIconContainerViewBinder.kt
@@ -22,7 +22,6 @@
 import androidx.annotation.ColorInt
 import androidx.collection.ArrayMap
 import androidx.lifecycle.lifecycleScope
-import com.android.internal.policy.SystemBarUtils
 import com.android.internal.statusbar.StatusBarIcon
 import com.android.internal.util.ContrastColorUtil
 import com.android.systemui.common.ui.ConfigurationState
@@ -39,10 +38,8 @@
 import com.android.systemui.statusbar.notification.icon.ui.viewmodel.NotificationIconsViewData
 import com.android.systemui.statusbar.notification.icon.ui.viewmodel.NotificationIconsViewData.LimitType
 import com.android.systemui.statusbar.phone.NotificationIconContainer
-import com.android.systemui.statusbar.policy.ConfigurationController
-import com.android.systemui.statusbar.policy.onConfigChanged
+import com.android.systemui.statusbar.ui.SystemBarUtilsState
 import com.android.systemui.util.kotlin.mapValuesNotNullTo
-import com.android.systemui.util.kotlin.stateFlow
 import com.android.systemui.util.ui.isAnimating
 import com.android.systemui.util.ui.stopAnimating
 import com.android.systemui.util.ui.value
@@ -51,7 +48,6 @@
 import kotlinx.coroutines.Job
 import kotlinx.coroutines.coroutineScope
 import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.StateFlow
 import kotlinx.coroutines.flow.combine
 import kotlinx.coroutines.flow.mapNotNull
 import kotlinx.coroutines.launch
@@ -59,20 +55,20 @@
 /** Binds a view-model to a [NotificationIconContainer]. */
 object NotificationIconContainerViewBinder {
     @JvmStatic
-    fun bind(
+    fun bindWhileAttached(
         view: NotificationIconContainer,
         viewModel: NotificationIconContainerShelfViewModel,
         configuration: ConfigurationState,
-        configurationController: ConfigurationController,
+        systemBarUtilsState: SystemBarUtilsState,
         failureTracker: StatusBarIconViewBindingFailureTracker,
-        viewStore: ShelfNotificationIconViewStore,
+        viewStore: IconViewStore,
     ): DisposableHandle {
         return view.repeatWhenAttached {
             lifecycleScope.launch {
                 viewModel.icons.bindIcons(
                     view,
                     configuration,
-                    configurationController,
+                    systemBarUtilsState,
                     notifyBindingFailures = { failureTracker.shelfFailures = it },
                     viewStore,
                 )
@@ -81,68 +77,89 @@
     }
 
     @JvmStatic
-    fun bind(
+    fun bindWhileAttached(
         view: NotificationIconContainer,
         viewModel: NotificationIconContainerStatusBarViewModel,
         configuration: ConfigurationState,
-        configurationController: ConfigurationController,
+        systemBarUtilsState: SystemBarUtilsState,
         failureTracker: StatusBarIconViewBindingFailureTracker,
-        viewStore: StatusBarNotificationIconViewStore,
-    ): DisposableHandle {
-        val contrastColorUtil = ContrastColorUtil.getInstance(view.context)
-        return view.repeatWhenAttached {
-            lifecycleScope.run {
-                launch {
-                    val iconColors: Flow<NotificationIconColors> =
-                        viewModel.iconColors.mapNotNull { it.iconColors(view.viewBounds) }
-                    viewModel.icons.bindIcons(
-                        view,
-                        configuration,
-                        configurationController,
-                        notifyBindingFailures = { failureTracker.statusBarFailures = it },
-                        viewStore,
-                    ) { _, sbiv ->
-                        StatusBarIconViewBinder.bindIconColors(
-                            sbiv,
-                            iconColors,
-                            contrastColorUtil,
-                        )
-                    }
-                }
-                launch { viewModel.bindIsolatedIcon(view, viewStore) }
-                launch { viewModel.animationsEnabled.bindAnimationsEnabled(view) }
+        viewStore: IconViewStore,
+    ): DisposableHandle =
+        view.repeatWhenAttached {
+            lifecycleScope.launch {
+                bind(view, viewModel, configuration, systemBarUtilsState, failureTracker, viewStore)
             }
         }
+
+    suspend fun bind(
+        view: NotificationIconContainer,
+        viewModel: NotificationIconContainerStatusBarViewModel,
+        configuration: ConfigurationState,
+        systemBarUtilsState: SystemBarUtilsState,
+        failureTracker: StatusBarIconViewBindingFailureTracker,
+        viewStore: IconViewStore,
+    ): Unit = coroutineScope {
+        launch {
+            val contrastColorUtil = ContrastColorUtil.getInstance(view.context)
+            val iconColors: Flow<NotificationIconColors> =
+                viewModel.iconColors.mapNotNull { it.iconColors(view.viewBounds) }
+            viewModel.icons.bindIcons(
+                view,
+                configuration,
+                systemBarUtilsState,
+                notifyBindingFailures = { failureTracker.statusBarFailures = it },
+                viewStore,
+            ) { _, sbiv ->
+                StatusBarIconViewBinder.bindIconColors(
+                    sbiv,
+                    iconColors,
+                    contrastColorUtil,
+                )
+            }
+        }
+        launch { viewModel.bindIsolatedIcon(view, viewStore) }
+        launch { viewModel.animationsEnabled.bindAnimationsEnabled(view) }
     }
 
     @JvmStatic
-    fun bind(
+    fun bindWhileAttached(
         view: NotificationIconContainer,
         viewModel: NotificationIconContainerAlwaysOnDisplayViewModel,
         configuration: ConfigurationState,
-        configurationController: ConfigurationController,
+        systemBarUtilsState: SystemBarUtilsState,
         failureTracker: StatusBarIconViewBindingFailureTracker,
         viewStore: IconViewStore,
     ): DisposableHandle {
         return view.repeatWhenAttached {
             lifecycleScope.launch {
-                view.setUseIncreasedIconScale(true)
-                launch {
-                    viewModel.icons.bindIcons(
-                        view,
-                        configuration,
-                        configurationController,
-                        notifyBindingFailures = { failureTracker.aodFailures = it },
-                        viewStore,
-                    ) { _, sbiv ->
-                        viewModel.bindAodStatusBarIconView(sbiv, configuration)
-                    }
-                }
-                launch { viewModel.areContainerChangesAnimated.bindAnimationsEnabled(view) }
+                bind(view, viewModel, configuration, systemBarUtilsState, failureTracker, viewStore)
             }
         }
     }
 
+    suspend fun bind(
+        view: NotificationIconContainer,
+        viewModel: NotificationIconContainerAlwaysOnDisplayViewModel,
+        configuration: ConfigurationState,
+        systemBarUtilsState: SystemBarUtilsState,
+        failureTracker: StatusBarIconViewBindingFailureTracker,
+        viewStore: IconViewStore,
+    ): Unit = coroutineScope {
+        view.setUseIncreasedIconScale(true)
+        launch {
+            viewModel.icons.bindIcons(
+                view,
+                configuration,
+                systemBarUtilsState,
+                notifyBindingFailures = { failureTracker.aodFailures = it },
+                viewStore,
+            ) { _, sbiv ->
+                viewModel.bindAodStatusBarIconView(sbiv, configuration)
+            }
+        }
+        launch { viewModel.areContainerChangesAnimated.bindAnimationsEnabled(view) }
+    }
+
     private suspend fun NotificationIconContainerAlwaysOnDisplayViewModel.bindAodStatusBarIconView(
         sbiv: StatusBarIconView,
         configuration: ConfigurationState,
@@ -199,7 +216,7 @@
     private suspend fun Flow<NotificationIconsViewData>.bindIcons(
         view: NotificationIconContainer,
         configuration: ConfigurationState,
-        configurationController: ConfigurationController,
+        systemBarUtilsState: SystemBarUtilsState,
         notifyBindingFailures: (Collection<String>) -> Unit,
         viewStore: IconViewStore,
         bindIcon: suspend (iconKey: String, view: StatusBarIconView) -> Unit = { _, _ -> },
@@ -210,12 +227,8 @@
             )
         val iconHorizontalPaddingFlow: Flow<Int> =
             configuration.getDimensionPixelSize(R.dimen.status_bar_icon_horizontal_margin)
-        val statusBarHeightFlow: StateFlow<Int> =
-            stateFlow(changedSignals = configurationController.onConfigChanged) {
-                SystemBarUtils.getStatusBarHeight(view.context)
-            }
         val layoutParams: Flow<FrameLayout.LayoutParams> =
-            combine(iconSizeFlow, iconHorizontalPaddingFlow, statusBarHeightFlow) {
+            combine(iconSizeFlow, iconHorizontalPaddingFlow, systemBarUtilsState.statusBarHeight) {
                 iconSize,
                 iconHPadding,
                 statusBarHeight,
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
index 5cdead4..699e140 100644
--- 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
@@ -29,7 +29,7 @@
 import com.android.systemui.statusbar.notification.shared.NotificationIconContainerRefactor
 import com.android.systemui.statusbar.notification.shelf.ui.viewmodel.NotificationShelfViewModel
 import com.android.systemui.statusbar.phone.NotificationIconAreaController
-import com.android.systemui.statusbar.policy.ConfigurationController
+import com.android.systemui.statusbar.ui.SystemBarUtilsState
 import kotlinx.coroutines.awaitCancellation
 import kotlinx.coroutines.launch
 
@@ -39,7 +39,7 @@
         shelf: NotificationShelf,
         viewModel: NotificationShelfViewModel,
         configuration: ConfigurationState,
-        configurationController: ConfigurationController,
+        systemBarUtilsState: SystemBarUtilsState,
         falsingManager: FalsingManager,
         iconViewBindingFailureTracker: StatusBarIconViewBindingFailureTracker,
         notificationIconAreaController: NotificationIconAreaController,
@@ -48,11 +48,11 @@
         ActivatableNotificationViewBinder.bind(viewModel, shelf, falsingManager)
         shelf.apply {
             if (NotificationIconContainerRefactor.isEnabled) {
-                NotificationIconContainerViewBinder.bind(
+                NotificationIconContainerViewBinder.bindWhileAttached(
                     shelfIcons,
                     viewModel.icons,
                     configuration,
-                    configurationController,
+                    systemBarUtilsState,
                     iconViewBindingFailureTracker,
                     shelfIconViewStore,
                 )
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/data/repository/NotificationStackAppearanceRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/data/repository/NotificationStackAppearanceRepository.kt
index abf09ae..e78a694 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/data/repository/NotificationStackAppearanceRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/data/repository/NotificationStackAppearanceRepository.kt
@@ -26,5 +26,8 @@
 @SysUISingleton
 class NotificationStackAppearanceRepository @Inject constructor() {
     /** The bounds of the notification stack in the current scene. */
-    val stackBounds = MutableStateFlow(NotificationContainerBounds(0f, 0f))
+    val stackBounds = MutableStateFlow(NotificationContainerBounds())
+
+    /** The corner radius of the notification stack, in dp. */
+    val cornerRadiusDp = MutableStateFlow(32f)
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/domain/interactor/NotificationStackAppearanceInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/domain/interactor/NotificationStackAppearanceInteractor.kt
index 32e4e89..61a4dfc 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/domain/interactor/NotificationStackAppearanceInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/domain/interactor/NotificationStackAppearanceInteractor.kt
@@ -39,4 +39,7 @@
         check(bounds.top <= bounds.bottom) { "Invalid bounds: $bounds" }
         repository.stackBounds.value = bounds
     }
+
+    /** The corner radius of the notification stack, in dp. */
+    val cornerRadiusDp: StateFlow<Float> = repository.cornerRadiusDp.asStateFlow()
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationListViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationListViewBinder.kt
index a4e1a9c..9373d49 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationListViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationListViewBinder.kt
@@ -40,7 +40,7 @@
 import com.android.systemui.statusbar.notification.stack.ui.viewbinder.HideNotificationsBinder.bindHideList
 import com.android.systemui.statusbar.notification.stack.ui.viewmodel.NotificationListViewModel
 import com.android.systemui.statusbar.phone.NotificationIconAreaController
-import com.android.systemui.statusbar.policy.ConfigurationController
+import com.android.systemui.statusbar.ui.SystemBarUtilsState
 import javax.inject.Inject
 import kotlinx.coroutines.CoroutineDispatcher
 import kotlinx.coroutines.flow.combine
@@ -53,12 +53,12 @@
     private val viewModel: NotificationListViewModel,
     @Background private val backgroundDispatcher: CoroutineDispatcher,
     private val configuration: ConfigurationState,
-    private val configurationController: ConfigurationController,
     private val falsingManager: FalsingManager,
     private val iconAreaController: NotificationIconAreaController,
     private val iconViewBindingFailureTracker: StatusBarIconViewBindingFailureTracker,
     private val metricsLogger: MetricsLogger,
     private val shelfIconViewStore: ShelfNotificationIconViewStore,
+    private val systemBarUtilsState: SystemBarUtilsState,
 ) {
 
     fun bind(
@@ -91,7 +91,7 @@
             shelf,
             viewModel.shelf,
             configuration,
-            configurationController,
+            systemBarUtilsState,
             falsingManager,
             iconViewBindingFailureTracker,
             iconAreaController,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationStackAppearanceViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationStackAppearanceViewBinder.kt
index fa7a8fd..a9b542d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationStackAppearanceViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationStackAppearanceViewBinder.kt
@@ -16,14 +16,16 @@
 
 package com.android.systemui.statusbar.notification.stack.ui.viewbinder
 
+import android.content.Context
+import android.util.TypedValue
 import androidx.lifecycle.Lifecycle
 import androidx.lifecycle.repeatOnLifecycle
 import com.android.systemui.lifecycle.repeatWhenAttached
-import com.android.systemui.scene.shared.flag.SceneContainerFlags
 import com.android.systemui.statusbar.notification.stack.AmbientState
 import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController
 import com.android.systemui.statusbar.notification.stack.ui.view.SharedNotificationContainer
 import com.android.systemui.statusbar.notification.stack.ui.viewmodel.NotificationStackAppearanceViewModel
+import kotlin.math.roundToInt
 import kotlinx.coroutines.launch
 
 /** Binds the shared notification container to its view-model. */
@@ -31,9 +33,9 @@
 
     @JvmStatic
     fun bind(
+        context: Context,
         view: SharedNotificationContainer,
         viewModel: NotificationStackAppearanceViewModel,
-        sceneContainerFlags: SceneContainerFlags,
         ambientState: AmbientState,
         controller: NotificationStackScrollLayoutController,
     ) {
@@ -45,6 +47,14 @@
                             bounds.top,
                             controller.isAddOrRemoveAnimationPending
                         )
+                        controller.setRoundedClippingBounds(
+                            it.left,
+                            it.top,
+                            it.right,
+                            it.bottom,
+                            viewModel.cornerRadiusDp.value.dpToPx(context),
+                            viewModel.cornerRadiusDp.value.dpToPx(context),
+                        )
                     }
                 }
                 launch {
@@ -56,4 +66,13 @@
             }
         }
     }
+
+    private fun Float.dpToPx(context: Context): Int {
+        return TypedValue.applyDimension(
+                TypedValue.COMPLEX_UNIT_DIP,
+                this,
+                context.resources.displayMetrics
+            )
+            .roundToInt()
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationStackAppearanceViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationStackAppearanceViewModel.kt
index f4c0e92..834d3ff 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationStackAppearanceViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationStackAppearanceViewModel.kt
@@ -23,6 +23,7 @@
 import com.android.systemui.statusbar.notification.stack.domain.interactor.NotificationStackAppearanceInteractor
 import javax.inject.Inject
 import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.StateFlow
 
 /** ViewModel which represents the state of the NSSL/Controller in the world of flexiglass */
 @SysUISingleton
@@ -37,4 +38,7 @@
 
     /** The bounds of the notification stack in the current scene. */
     val stackBounds: Flow<NotificationContainerBounds> = stackAppearanceInteractor.stackBounds
+
+    /** The corner radius of the notification stack, in dp. */
+    val cornerRadiusDp: StateFlow<Float> = stackAppearanceInteractor.cornerRadiusDp
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModel.kt
index c6fd98e..9f22118 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModel.kt
@@ -24,6 +24,7 @@
 import com.android.systemui.statusbar.notification.stack.domain.interactor.NotificationStackAppearanceInteractor
 import com.android.systemui.statusbar.notification.stack.shared.flexiNotifsEnabled
 import javax.inject.Inject
+import kotlinx.coroutines.flow.StateFlow
 
 /**
  * ViewModel used by the Notification placeholders inside the scene container to update the
@@ -55,9 +56,14 @@
      *   pixels.
      */
     fun onBoundsChanged(
+        left: Float,
         top: Float,
+        right: Float,
         bottom: Float,
     ) {
-        interactor.setStackBounds(NotificationContainerBounds(top, bottom))
+        interactor.setStackBounds(NotificationContainerBounds(left, top, right, bottom))
     }
+
+    /** The corner radius of the placeholder, in dp. */
+    val cornerRadiusDp: StateFlow<Float> = interactor.cornerRadiusDp
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt
index 5b854e6..9594bc3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt
@@ -177,8 +177,8 @@
             }
             .stateIn(
                 scope = applicationScope,
-                started = SharingStarted.WhileSubscribed(),
-                initialValue = NotificationContainerBounds(0f, 0f),
+                started = SharingStarted.Lazily,
+                initialValue = NotificationContainerBounds(),
             )
 
     val alpha: Flow<Float> =
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java
index 4e77801..97fc35a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java
@@ -47,7 +47,6 @@
 import com.android.systemui.biometrics.AuthController;
 import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dagger.qualifiers.Main;
-import com.android.systemui.deviceentry.domain.interactor.DeviceEntryHapticsInteractor;
 import com.android.systemui.dump.DumpManager;
 import com.android.systemui.keyguard.KeyguardViewMediator;
 import com.android.systemui.keyguard.WakefulnessLifecycle;
@@ -180,8 +179,6 @@
     private final SystemClock mSystemClock;
     private final boolean mOrderUnlockAndWake;
     private final Lazy<SelectedUserInteractor> mSelectedUserInteractor;
-    private final DeviceEntryHapticsInteractor mHapticsInteractor;
-
     private long mLastFpFailureUptimeMillis;
     private int mNumConsecutiveFpFailures;
 
@@ -288,7 +285,6 @@
             ScreenOffAnimationController screenOffAnimationController,
             VibratorHelper vibrator,
             SystemClock systemClock,
-            DeviceEntryHapticsInteractor hapticsInteractor,
             Lazy<SelectedUserInteractor> selectedUserInteractor,
             BiometricUnlockInteractor biometricUnlockInteractor
     ) {
@@ -320,7 +316,6 @@
         mSystemClock = systemClock;
         mOrderUnlockAndWake = resources.getBoolean(
                 com.android.internal.R.bool.config_orderUnlockAndWake);
-        mHapticsInteractor = hapticsInteractor;
         mSelectedUserInteractor = selectedUserInteractor;
 
         dumpManager.registerDumpable(this);
@@ -442,7 +437,6 @@
         if (mode == MODE_WAKE_AND_UNLOCK
                 || mode == MODE_WAKE_AND_UNLOCK_PULSING || mode == MODE_UNLOCK_COLLAPSING
                 || mode == MODE_WAKE_AND_UNLOCK_FROM_DREAM || mode == MODE_DISMISS_BOUNCER) {
-            mHapticsInteractor.vibrateSuccess();
             onBiometricUnlockedWithKeyguardDismissal(biometricSourceType);
         }
         startWakeAndUnlock(mode);
@@ -726,14 +720,6 @@
             }
         }
 
-        // Suppress all face auth errors if fingerprint can be used to authenticate
-        if ((biometricSourceType == BiometricSourceType.FACE
-                && !mUpdateMonitor.isUnlockWithFingerprintPossible(
-                    mSelectedUserInteractor.get().getSelectedUserId()))
-                || (biometricSourceType == BiometricSourceType.FINGERPRINT)) {
-            mHapticsInteractor.vibrateError();
-        }
-
         cleanup();
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java
index 3a95e6d..644c896 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java
@@ -49,6 +49,8 @@
 import com.android.systemui.statusbar.policy.OnHeadsUpChangedListener;
 import com.android.systemui.statusbar.policy.OnHeadsUpPhoneListenerChange;
 import com.android.systemui.util.kotlin.JavaAdapter;
+import com.android.systemui.util.settings.GlobalSettings;
+import com.android.systemui.util.time.SystemClock;
 
 import java.io.PrintWriter;
 import java.util.ArrayList;
@@ -115,11 +117,14 @@
             VisualStabilityProvider visualStabilityProvider,
             ConfigurationController configurationController,
             @Main Handler handler,
+            GlobalSettings globalSettings,
+            SystemClock systemClock,
             AccessibilityManagerWrapper accessibilityManagerWrapper,
             UiEventLogger uiEventLogger,
             JavaAdapter javaAdapter,
             ShadeInteractor shadeInteractor) {
-        super(context, logger, handler, accessibilityManagerWrapper, uiEventLogger);
+        super(context, logger, handler, globalSettings, systemClock, accessibilityManagerWrapper,
+                uiEventLogger);
         Resources resources = mContext.getResources();
         mExtensionTime = resources.getInteger(R.integer.ambient_notification_extension_time);
         statusBarStateController.addCallback(mStatusBarStateListener);
@@ -206,7 +211,7 @@
     @Override
     public boolean shouldSwallowClick(@NonNull String key) {
         BaseHeadsUpManager.HeadsUpEntry entry = getHeadsUpEntry(key);
-        return entry != null && mClock.currentTimeMillis() < entry.mPostTime;
+        return entry != null && mSystemClock.elapsedRealtime() < entry.mPostTime;
     }
 
     public void onExpandingFinished() {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java
index 49880d4..cd99934 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java
@@ -74,8 +74,8 @@
 import com.android.systemui.statusbar.pipeline.shared.ui.binder.CollapsedStatusBarViewBinder;
 import com.android.systemui.statusbar.pipeline.shared.ui.binder.StatusBarVisibilityChangeListener;
 import com.android.systemui.statusbar.pipeline.shared.ui.viewmodel.CollapsedStatusBarViewModel;
-import com.android.systemui.statusbar.policy.ConfigurationController;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
+import com.android.systemui.statusbar.ui.SystemBarUtilsState;
 import com.android.systemui.statusbar.window.StatusBarWindowStateController;
 import com.android.systemui.statusbar.window.StatusBarWindowStateListener;
 import com.android.systemui.util.CarrierConfigTracker;
@@ -83,8 +83,6 @@
 import com.android.systemui.util.CarrierConfigTracker.DefaultDataSubscriptionChangedListener;
 import com.android.systemui.util.settings.SecureSettings;
 
-import kotlin.Unit;
-
 import java.io.PrintWriter;
 import java.util.ArrayList;
 import java.util.Arrays;
@@ -95,6 +93,8 @@
 
 import javax.inject.Inject;
 
+import kotlin.Unit;
+
 /**
  * Contains the collapsed status bar and handles hiding/showing based on disable flags
  * and keyguard state. Also manages lifecycle to make sure the views it contains are being
@@ -153,7 +153,7 @@
     private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
     private final NotificationIconContainerStatusBarViewModel mStatusBarIconsViewModel;
     private final ConfigurationState mConfigurationState;
-    private final ConfigurationController mConfigurationController;
+    private final SystemBarUtilsState mSystemBarUtilsState;
     private final StatusBarNotificationIconViewStore mStatusBarIconViewStore;
     private final DemoModeController mDemoModeController;
 
@@ -246,7 +246,7 @@
             KeyguardUpdateMonitor keyguardUpdateMonitor,
             NotificationIconContainerStatusBarViewModel statusBarIconsViewModel,
             ConfigurationState configurationState,
-            ConfigurationController configurationController,
+            SystemBarUtilsState systemBarUtilsState,
             StatusBarNotificationIconViewStore statusBarIconViewStore,
             DemoModeController demoModeController) {
         mStatusBarFragmentComponentFactory = statusBarFragmentComponentFactory;
@@ -275,7 +275,7 @@
         mKeyguardUpdateMonitor = keyguardUpdateMonitor;
         mStatusBarIconsViewModel = statusBarIconsViewModel;
         mConfigurationState = configurationState;
-        mConfigurationController = configurationController;
+        mSystemBarUtilsState = systemBarUtilsState;
         mStatusBarIconViewStore = statusBarIconViewStore;
         mDemoModeController = demoModeController;
     }
@@ -466,11 +466,11 @@
                         .inflate(R.layout.notification_icon_area, notificationIconArea, true);
             NotificationIconContainer notificationIcons =
                     notificationIconArea.requireViewById(R.id.notificationIcons);
-            NotificationIconContainerViewBinder.bind(
+            NotificationIconContainerViewBinder.bindWhileAttached(
                     notificationIcons,
                     mStatusBarIconsViewModel,
                     mConfigurationState,
-                    mConfigurationController,
+                    mSystemBarUtilsState,
                     mIconViewBindingFailureTracker,
                     mStatusBarIconViewStore);
         } else {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/InternetTileViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/InternetTileViewModel.kt
index 99b123f..ae58398 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/InternetTileViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/InternetTileViewModel.kt
@@ -161,13 +161,13 @@
             if (it == null) {
                 notConnectedFlow
             } else {
-                val secondary = it.contentDescription.toString()
+                val secondary = it.contentDescription
                 flowOf(
                     InternetTileModel.Active(
-                        secondaryTitle = secondary,
+                        secondaryLabel = secondary?.toText(),
                         iconId = it.res,
                         stateDescription = null,
-                        contentDescription = ContentDescription.Loaded(secondary),
+                        contentDescription = secondary,
                     )
                 )
             }
@@ -241,5 +241,11 @@
                 string.substring(1, length - 1)
             } else string
         }
+
+        private fun ContentDescription.toText(): Text =
+            when (this) {
+                is ContentDescription.Loaded -> Text.Loaded(this.description)
+                is ContentDescription.Resource -> Text.Resource(this.res)
+            }
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BaseHeadsUpManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BaseHeadsUpManager.java
index cec76f3..8054b04 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BaseHeadsUpManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BaseHeadsUpManager.java
@@ -25,8 +25,6 @@
 import android.content.res.Resources;
 import android.database.ContentObserver;
 import android.os.Handler;
-import android.os.SystemClock;
-import android.provider.Settings;
 import android.util.ArrayMap;
 import android.view.accessibility.AccessibilityManager;
 
@@ -40,6 +38,8 @@
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.InflationFlag;
 import com.android.systemui.util.ListenerSet;
+import com.android.systemui.util.settings.GlobalSettings;
+import com.android.systemui.util.time.SystemClock;
 
 import java.io.PrintWriter;
 
@@ -85,36 +85,40 @@
     public BaseHeadsUpManager(@NonNull final Context context,
             HeadsUpManagerLogger logger,
             @Main Handler handler,
+            GlobalSettings globalSettings,
+            SystemClock systemClock,
             AccessibilityManagerWrapper accessibilityManagerWrapper,
             UiEventLogger uiEventLogger) {
-        super(logger, handler);
+        super(logger, handler, systemClock);
         mContext = context;
         mAccessibilityMgr = accessibilityManagerWrapper;
         mUiEventLogger = uiEventLogger;
         Resources resources = context.getResources();
         mMinimumDisplayTime = resources.getInteger(R.integer.heads_up_notification_minimum_time);
-        mStickyDisplayTime = resources.getInteger(R.integer.sticky_heads_up_notification_time);
-        mAutoDismissNotificationDecay = resources.getInteger(R.integer.heads_up_notification_decay);
+        mStickyForSomeTimeAutoDismissTime = resources.getInteger(
+                R.integer.sticky_heads_up_notification_time);
+        mAutoDismissTime = resources.getInteger(R.integer.heads_up_notification_decay);
         mTouchAcceptanceDelay = resources.getInteger(R.integer.touch_acceptance_delay);
         mSnoozedPackages = new ArrayMap<>();
         int defaultSnoozeLengthMs =
                 resources.getInteger(R.integer.heads_up_default_snooze_length_ms);
 
-        mSnoozeLengthMs = Settings.Global.getInt(context.getContentResolver(),
-                SETTING_HEADS_UP_SNOOZE_LENGTH_MS, defaultSnoozeLengthMs);
+        mSnoozeLengthMs = globalSettings.getInt(SETTING_HEADS_UP_SNOOZE_LENGTH_MS,
+                defaultSnoozeLengthMs);
         ContentObserver settingsObserver = new ContentObserver(handler) {
             @Override
             public void onChange(boolean selfChange) {
-                final int packageSnoozeLengthMs = Settings.Global.getInt(
-                        context.getContentResolver(), SETTING_HEADS_UP_SNOOZE_LENGTH_MS, -1);
+                final int packageSnoozeLengthMs = globalSettings.getInt(
+                        SETTING_HEADS_UP_SNOOZE_LENGTH_MS, -1);
                 if (packageSnoozeLengthMs > -1 && packageSnoozeLengthMs != mSnoozeLengthMs) {
                     mSnoozeLengthMs = packageSnoozeLengthMs;
                     mLogger.logSnoozeLengthChange(packageSnoozeLengthMs);
                 }
             }
         };
-        context.getContentResolver().registerContentObserver(
-                Settings.Global.getUriFor(SETTING_HEADS_UP_SNOOZE_LENGTH_MS), false,
+        globalSettings.registerContentObserver(
+                globalSettings.getUriFor(SETTING_HEADS_UP_SNOOZE_LENGTH_MS),
+                /* notifyForDescendants = */ false,
                 settingsObserver);
     }
 
@@ -231,7 +235,7 @@
         final String key = snoozeKey(packageName, mUser);
         Long snoozedUntil = mSnoozedPackages.get(key);
         if (snoozedUntil != null) {
-            if (snoozedUntil > mClock.currentTimeMillis()) {
+            if (snoozedUntil > mSystemClock.elapsedRealtime()) {
                 mLogger.logIsSnoozedReturned(key);
                 return true;
             }
@@ -250,7 +254,7 @@
             String packageName = entry.mEntry.getSbn().getPackageName();
             String snoozeKey = snoozeKey(packageName, mUser);
             mLogger.logPackageSnoozed(snoozeKey);
-            mSnoozedPackages.put(snoozeKey, mClock.currentTimeMillis() + mSnoozeLengthMs);
+            mSnoozedPackages.put(snoozeKey, mSystemClock.elapsedRealtime() + mSnoozeLengthMs);
         }
     }
 
@@ -308,7 +312,7 @@
     protected void dumpInternal(@NonNull PrintWriter pw, @NonNull String[] args) {
         pw.print("  mTouchAcceptanceDelay="); pw.println(mTouchAcceptanceDelay);
         pw.print("  mSnoozeLengthMs="); pw.println(mSnoozeLengthMs);
-        pw.print("  now="); pw.println(mClock.currentTimeMillis());
+        pw.print("  now="); pw.println(mSystemClock.elapsedRealtime());
         pw.print("  mUser="); pw.println(mUser);
         for (AlertEntry entry: mAlertEntries.values()) {
             pw.print("  HeadsUpEntry="); pw.println(entry.mEntry);
@@ -519,12 +523,12 @@
 
         /**
          * @return When the notification should auto-dismiss itself, based on
-         * {@link SystemClock#elapsedRealTime()}
+         * {@link SystemClock#elapsedRealtime()}
          */
         @Override
         protected long calculateFinishTime() {
             final long duration = getRecommendedHeadsUpTimeoutMs(
-                    isStickyForSomeTime() ? mStickyDisplayTime : mAutoDismissNotificationDecay);
+                    isStickyForSomeTime() ? mStickyForSomeTimeAutoDismissTime : mAutoDismissTime);
 
             return mPostTime + duration;
         }
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 80c6802..756c440 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateControllerImpl.java
@@ -484,7 +484,12 @@
         }
 
         @Override
-        public void onBiometricsCleared() {
+        public void onFingerprintsCleared() {
+            update(false /* alwaysUpdate */);
+        }
+
+        @Override
+        public void onFacesCleared() {
             update(false /* alwaysUpdate */);
         }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/ui/SystemBarUtilsProxy.kt b/packages/SystemUI/src/com/android/systemui/statusbar/ui/SystemBarUtilsProxy.kt
new file mode 100644
index 0000000..2b3fb70
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/ui/SystemBarUtilsProxy.kt
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.ui
+
+import android.content.Context
+import com.android.internal.policy.SystemBarUtils
+import com.android.systemui.dagger.qualifiers.Application
+import dagger.Binds
+import javax.inject.Inject
+
+/**
+ * Proxy interface to [SystemBarUtils], allowing injection of different logic for testing.
+ *
+ * Developers should almost always prefer [SystemBarUtilsState] instead.
+ */
+interface SystemBarUtilsProxy {
+    fun getStatusBarHeight(): Int
+}
+
+class SystemBarUtilsProxyImpl
+@Inject
+constructor(
+    @Application private val context: Context,
+) : SystemBarUtilsProxy {
+    override fun getStatusBarHeight(): Int = SystemBarUtils.getStatusBarHeight(context)
+
+    @dagger.Module
+    interface Module {
+        @Binds fun bindImpl(impl: SystemBarUtilsProxyImpl): SystemBarUtilsProxy
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/ui/SystemBarUtilsState.kt b/packages/SystemUI/src/com/android/systemui/statusbar/ui/SystemBarUtilsState.kt
new file mode 100644
index 0000000..ce811e2
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/ui/SystemBarUtilsState.kt
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.ui
+
+import com.android.internal.policy.SystemBarUtils
+import com.android.systemui.statusbar.policy.ConfigurationController
+import com.android.systemui.statusbar.policy.onConfigChanged
+import javax.inject.Inject
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.onStart
+
+/**
+ * Tracks state from [SystemBarUtils]. Using this is both more efficient and more testable than
+ * using [SystemBarUtils] directly.
+ */
+class SystemBarUtilsState
+@Inject
+constructor(
+    configurationController: ConfigurationController,
+    proxy: SystemBarUtilsProxy,
+) {
+    /** @see SystemBarUtils.getStatusBarHeight */
+    val statusBarHeight: Flow<Int> =
+        configurationController.onConfigChanged
+            .onStart<Any> { emit(Unit) }
+            .map { proxy.getStatusBarHeight() }
+}
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerBaseTest.java
index 24917b3..88f63ad 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerBaseTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerBaseTest.java
@@ -63,7 +63,7 @@
 import com.android.systemui.statusbar.phone.NotificationIconAreaController;
 import com.android.systemui.statusbar.phone.NotificationIconContainer;
 import com.android.systemui.statusbar.phone.ScreenOffAnimationController;
-import com.android.systemui.statusbar.policy.ConfigurationController;
+import com.android.systemui.statusbar.ui.SystemBarUtilsState;
 import com.android.systemui.util.concurrency.FakeExecutor;
 import com.android.systemui.util.settings.SecureSettings;
 import com.android.systemui.util.time.FakeSystemClock;
@@ -185,7 +185,7 @@
                 mKeyguardSliceViewController,
                 mNotificationIconAreaController,
                 mSmartspaceController,
-                mock(ConfigurationController.class),
+                mock(SystemBarUtilsState.class),
                 mock(ScreenOffAnimationController.class),
                 mock(StatusBarIconViewBindingFailureTracker.class),
                 mKeyguardUnlockAnimationController,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuAnimationControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuAnimationControllerTest.java
index 2a1cfd1..215f93d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuAnimationControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuAnimationControllerTest.java
@@ -17,7 +17,6 @@
 package com.android.systemui.accessibility.floatingmenu;
 
 import static com.google.common.truth.Truth.assertThat;
-
 import static org.mockito.Mockito.any;
 import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.mock;
@@ -26,9 +25,7 @@
 import static org.mockito.Mockito.verifyZeroInteractions;
 
 import android.graphics.PointF;
-import android.platform.test.annotations.RequiresFlagsEnabled;
-import android.platform.test.flag.junit.CheckFlagsRule;
-import android.platform.test.flag.junit.DeviceFlagsValueProvider;
+import android.platform.test.annotations.EnableFlags;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
 import android.view.View;
@@ -74,10 +71,6 @@
     @Rule
     public MockitoRule mockito = MockitoJUnit.rule();
 
-    @Rule
-    public final CheckFlagsRule mCheckFlagsRule =
-            DeviceFlagsValueProvider.createCheckFlagsRule();
-
     @Mock
     private AccessibilityManager mAccessibilityManager;
 
@@ -233,7 +226,7 @@
     }
 
     @Test
-    @RequiresFlagsEnabled(Flags.FLAG_FLOATING_MENU_ANIMATED_TUCK)
+    @EnableFlags(Flags.FLAG_FLOATING_MENU_ANIMATED_TUCK)
     public void tuck_animates() {
         mMenuAnimationController.cancelAnimations();
         mMenuAnimationController.moveToEdgeAndHide();
@@ -242,7 +235,7 @@
     }
 
     @Test
-    @RequiresFlagsEnabled(Flags.FLAG_FLOATING_MENU_ANIMATED_TUCK)
+    @EnableFlags(Flags.FLAG_FLOATING_MENU_ANIMATED_TUCK)
     public void untuck_animates() {
         mMenuAnimationController.cancelAnimations();
         mMenuAnimationController.moveOutEdgeAndShow();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerTest.java
index 0f1364d..be6f3ff 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerTest.java
@@ -21,11 +21,8 @@
 import static android.view.WindowInsets.Type.displayCutout;
 import static android.view.WindowInsets.Type.ime;
 import static android.view.WindowInsets.Type.systemBars;
-
 import static com.android.systemui.accessibility.floatingmenu.MenuViewLayer.LayerIndex;
-
 import static com.google.common.truth.Truth.assertThat;
-
 import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.verify;
@@ -41,10 +38,8 @@
 import android.graphics.Rect;
 import android.os.Build;
 import android.os.UserHandle;
-import android.platform.test.annotations.RequiresFlagsDisabled;
-import android.platform.test.annotations.RequiresFlagsEnabled;
-import android.platform.test.flag.junit.CheckFlagsRule;
-import android.platform.test.flag.junit.DeviceFlagsValueProvider;
+import android.platform.test.annotations.DisableFlags;
+import android.platform.test.annotations.EnableFlags;
 import android.provider.Settings;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
@@ -103,10 +98,6 @@
     @Rule
     public MockitoRule mockito = MockitoJUnit.rule();
 
-    @Rule
-    public final CheckFlagsRule mCheckFlagsRule =
-            DeviceFlagsValueProvider.createCheckFlagsRule();
-
     @Mock
     private IAccessibilityFloatingMenu mFloatingMenu;
 
@@ -230,7 +221,7 @@
     }
 
     @Test
-    @RequiresFlagsDisabled(Flags.FLAG_FLOATING_MENU_IME_DISPLACEMENT_ANIMATION)
+    @DisableFlags(Flags.FLAG_FLOATING_MENU_IME_DISPLACEMENT_ANIMATION)
     public void showingImeInsetsChange_overlapOnIme_menuShownAboveIme_old() {
         mMenuAnimationController.moveAndPersistPosition(new PointF(0, IME_TOP + 100));
         final PointF beforePosition = mMenuView.getMenuPosition();
@@ -243,7 +234,7 @@
     }
 
     @Test
-    @RequiresFlagsEnabled(Flags.FLAG_FLOATING_MENU_IME_DISPLACEMENT_ANIMATION)
+    @EnableFlags(Flags.FLAG_FLOATING_MENU_IME_DISPLACEMENT_ANIMATION)
     public void showingImeInsetsChange_overlapOnIme_menuShownAboveIme() {
         mMenuAnimationController.moveAndPersistPosition(new PointF(0, IME_TOP + 100));
         final PointF beforePosition = mMenuView.getMenuPosition();
@@ -259,7 +250,7 @@
     }
 
     @Test
-    @RequiresFlagsDisabled(Flags.FLAG_FLOATING_MENU_IME_DISPLACEMENT_ANIMATION)
+    @DisableFlags(Flags.FLAG_FLOATING_MENU_IME_DISPLACEMENT_ANIMATION)
     public void hidingImeInsetsChange_overlapOnIme_menuBackToOriginalPosition_old() {
         mMenuAnimationController.moveAndPersistPosition(new PointF(0, IME_TOP + 200));
         final PointF beforePosition = mMenuView.getMenuPosition();
@@ -271,7 +262,7 @@
     }
 
     @Test
-    @RequiresFlagsEnabled(Flags.FLAG_FLOATING_MENU_IME_DISPLACEMENT_ANIMATION)
+    @EnableFlags(Flags.FLAG_FLOATING_MENU_IME_DISPLACEMENT_ANIMATION)
     public void hidingImeInsetsChange_overlapOnIme_menuBackToOriginalPosition() {
         mMenuAnimationController.moveAndPersistPosition(new PointF(0, IME_TOP + 200));
         final PointF beforePosition = mMenuView.getMenuPosition();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewTest.java
index 8f0a97c..8da6cf9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewTest.java
@@ -17,9 +17,7 @@
 package com.android.systemui.accessibility.floatingmenu;
 
 import static android.app.UiModeManager.MODE_NIGHT_YES;
-
 import static com.google.common.truth.Truth.assertThat;
-
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.verify;
@@ -27,9 +25,7 @@
 import android.app.UiModeManager;
 import android.graphics.Rect;
 import android.graphics.drawable.GradientDrawable;
-import android.platform.test.annotations.RequiresFlagsEnabled;
-import android.platform.test.flag.junit.CheckFlagsRule;
-import android.platform.test.flag.junit.DeviceFlagsValueProvider;
+import android.platform.test.annotations.EnableFlags;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
 import android.view.WindowManager;
@@ -66,10 +62,6 @@
     @Rule
     public MockitoRule mockito = MockitoJUnit.rule();
 
-    @Rule
-    public final CheckFlagsRule mCheckFlagsRule =
-            DeviceFlagsValueProvider.createCheckFlagsRule();
-
     @Mock
     private AccessibilityManager mAccessibilityManager;
 
@@ -147,7 +139,7 @@
     }
 
     @Test
-    @RequiresFlagsEnabled(Flags.FLAG_FLOATING_MENU_RADII_ANIMATION)
+    @EnableFlags(Flags.FLAG_FLOATING_MENU_RADII_ANIMATION)
     public void onEdgeChanged_startsRadiiAnimation() {
         final RadiiAnimator radiiAnimator = getRadiiAnimator();
         mMenuView.onEdgeChanged();
@@ -155,7 +147,7 @@
     }
 
     @Test
-    @RequiresFlagsEnabled(Flags.FLAG_FLOATING_MENU_RADII_ANIMATION)
+    @EnableFlags(Flags.FLAG_FLOATING_MENU_RADII_ANIMATION)
     public void onDraggingStart_startsRadiiAnimation() {
         final RadiiAnimator radiiAnimator = getRadiiAnimator();
         mMenuView.onDraggingStart();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/DefaultUdfpsTouchOverlayViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/DefaultUdfpsTouchOverlayViewModelTest.kt
index 575d8bf..fa17672 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/DefaultUdfpsTouchOverlayViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/DefaultUdfpsTouchOverlayViewModelTest.kt
@@ -30,7 +30,6 @@
 import com.android.systemui.runCurrent
 import com.android.systemui.runTest
 import com.android.systemui.shade.data.repository.FakeShadeRepository
-import com.android.systemui.shade.domain.model.ShadeModel
 import com.android.systemui.statusbar.phone.SystemUIDialogManager
 import com.android.systemui.user.domain.UserDomainLayerModule
 import com.android.systemui.util.mockito.mock
@@ -83,23 +82,13 @@
 
     private fun TestComponent.shadeExpanded(expanded: Boolean) {
         if (expanded) {
-            shadeRepository.setShadeModel(
-                ShadeModel(
-                    expansionAmount = 1f,
-                    isExpanded = true,
-                    isUserDragging = false,
-                )
-            )
+            shadeRepository.setLegacyShadeExpansion(1f)
+            shadeRepository.setLegacyShadeTracking(false)
             shadeRepository.setLegacyExpandedOrAwaitingInputTransfer(true)
         } else {
             keyguardRepository.setStatusBarState(StatusBarState.SHADE)
-            shadeRepository.setShadeModel(
-                ShadeModel(
-                    expansionAmount = 0f,
-                    isExpanded = false,
-                    isUserDragging = false,
-                )
-            )
+            shadeRepository.setLegacyShadeExpansion(0f)
+            shadeRepository.setLegacyShadeTracking(false)
             shadeRepository.setLegacyExpandedOrAwaitingInputTransfer(false)
         }
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/DeviceEntryUdfpsTouchOverlayViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/DeviceEntryUdfpsTouchOverlayViewModelTest.kt
index f5f1622..863d9eb 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/DeviceEntryUdfpsTouchOverlayViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/DeviceEntryUdfpsTouchOverlayViewModelTest.kt
@@ -20,7 +20,7 @@
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.flags.Flags
-import com.android.systemui.flags.featureFlagsClassic
+import com.android.systemui.flags.fakeFeatureFlagsClassic
 import com.android.systemui.keyguard.ui.transitions.DeviceEntryIconTransition
 import com.android.systemui.keyguard.ui.viewmodel.deviceEntryIconViewModelTransitionsMock
 import com.android.systemui.kosmos.testScope
@@ -49,7 +49,7 @@
 class DeviceEntryUdfpsTouchOverlayViewModelTest : SysuiTestCase() {
     val kosmos =
         testKosmos().apply {
-            featureFlagsClassic.apply { set(Flags.FULL_SCREEN_USER_SWITCHER, true) }
+            fakeFeatureFlagsClassic.apply { set(Flags.FULL_SCREEN_USER_SWITCHER, true) }
         }
     val testScope = kosmos.testScope
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bouncer/domain/interactor/BouncerMessageInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bouncer/domain/interactor/BouncerMessageInteractorTest.kt
index 094616f..aa0d7b6 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bouncer/domain/interactor/BouncerMessageInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/bouncer/domain/interactor/BouncerMessageInteractorTest.kt
@@ -35,7 +35,7 @@
 import com.android.systemui.bouncer.ui.BouncerView
 import com.android.systemui.classifier.FalsingCollector
 import com.android.systemui.coroutines.collectLastValue
-import com.android.systemui.flags.FakeFeatureFlags
+import com.android.systemui.flags.FakeFeatureFlagsClassic
 import com.android.systemui.flags.Flags
 import com.android.systemui.flags.SystemPropertiesHelper
 import com.android.systemui.keyguard.DismissCallbackRegistry
@@ -62,7 +62,6 @@
 import org.junit.runner.RunWith
 import org.mockito.ArgumentMatchers.eq
 import org.mockito.Mock
-import org.mockito.Mockito
 import org.mockito.Mockito.mock
 import org.mockito.Mockito.verify
 import org.mockito.MockitoAnnotations
@@ -108,17 +107,18 @@
 
     suspend fun TestScope.init() {
         userRepository.setSelectedUserInfo(PRIMARY_USER)
-        val featureFlags = FakeFeatureFlags().apply { set(Flags.REVAMPED_BOUNCER_MESSAGES, true) }
+        val featureFlags =
+            FakeFeatureFlagsClassic().apply { set(Flags.REVAMPED_BOUNCER_MESSAGES, true) }
         primaryBouncerInteractor =
             PrimaryBouncerInteractor(
                 bouncerRepository,
-                Mockito.mock(BouncerView::class.java),
-                Mockito.mock(Handler::class.java),
-                Mockito.mock(KeyguardStateController::class.java),
-                Mockito.mock(KeyguardSecurityModel::class.java),
-                Mockito.mock(PrimaryBouncerCallbackInteractor::class.java),
-                Mockito.mock(FalsingCollector::class.java),
-                Mockito.mock(DismissCallbackRegistry::class.java),
+                mock(BouncerView::class.java),
+                mock(Handler::class.java),
+                mock(KeyguardStateController::class.java),
+                mock(KeyguardSecurityModel::class.java),
+                mock(PrimaryBouncerCallbackInteractor::class.java),
+                mock(FalsingCollector::class.java),
+                mock(DismissCallbackRegistry::class.java),
                 context,
                 keyguardUpdateMonitor,
                 fakeTrustRepository,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/helper/BouncerSceneLayoutTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/helper/BouncerSceneLayoutTest.kt
index 395d712..ca95822 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/helper/BouncerSceneLayoutTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/helper/BouncerSceneLayoutTest.kt
@@ -18,10 +18,10 @@
 
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
-import com.android.systemui.bouncer.ui.helper.BouncerSceneLayout.SIDE_BY_SIDE
-import com.android.systemui.bouncer.ui.helper.BouncerSceneLayout.SPLIT
-import com.android.systemui.bouncer.ui.helper.BouncerSceneLayout.STACKED
-import com.android.systemui.bouncer.ui.helper.BouncerSceneLayout.STANDARD
+import com.android.systemui.bouncer.ui.helper.BouncerSceneLayout.BELOW_USER_SWITCHER
+import com.android.systemui.bouncer.ui.helper.BouncerSceneLayout.BESIDE_USER_SWITCHER
+import com.android.systemui.bouncer.ui.helper.BouncerSceneLayout.SPLIT_BOUNCER
+import com.android.systemui.bouncer.ui.helper.BouncerSceneLayout.STANDARD_BOUNCER
 import com.google.common.truth.Truth.assertThat
 import java.util.Locale
 import org.junit.Test
@@ -84,33 +84,33 @@
             listOf(
                     Phone to
                         Expected(
-                            whenNaturallyHeld = STANDARD,
-                            whenUnnaturallyHeld = SPLIT,
+                            whenNaturallyHeld = STANDARD_BOUNCER,
+                            whenUnnaturallyHeld = SPLIT_BOUNCER,
                         ),
                     Tablet to
                         Expected(
-                            whenNaturallyHeld = SIDE_BY_SIDE,
-                            whenUnnaturallyHeld = STACKED,
+                            whenNaturallyHeld = BESIDE_USER_SWITCHER,
+                            whenUnnaturallyHeld = BELOW_USER_SWITCHER,
                         ),
                     Folded to
                         Expected(
-                            whenNaturallyHeld = STANDARD,
-                            whenUnnaturallyHeld = SPLIT,
+                            whenNaturallyHeld = STANDARD_BOUNCER,
+                            whenUnnaturallyHeld = SPLIT_BOUNCER,
                         ),
                     Unfolded to
                         Expected(
-                            whenNaturallyHeld = SIDE_BY_SIDE,
-                            whenUnnaturallyHeld = STANDARD,
+                            whenNaturallyHeld = BESIDE_USER_SWITCHER,
+                            whenUnnaturallyHeld = STANDARD_BOUNCER,
                         ),
                     TallerFolded to
                         Expected(
-                            whenNaturallyHeld = STANDARD,
-                            whenUnnaturallyHeld = SPLIT,
+                            whenNaturallyHeld = STANDARD_BOUNCER,
+                            whenUnnaturallyHeld = SPLIT_BOUNCER,
                         ),
                     TallerUnfolded to
                         Expected(
-                            whenNaturallyHeld = SIDE_BY_SIDE,
-                            whenUnnaturallyHeld = SIDE_BY_SIDE,
+                            whenNaturallyHeld = BESIDE_USER_SWITCHER,
+                            whenUnnaturallyHeld = BESIDE_USER_SWITCHER,
                         ),
                 )
                 .flatMap { (device, expected) ->
@@ -124,13 +124,13 @@
                             )
                         )
 
-                        if (expected.whenNaturallyHeld == SIDE_BY_SIDE) {
+                        if (expected.whenNaturallyHeld == BESIDE_USER_SWITCHER) {
                             add(
                                 TestCase(
                                     device = device,
                                     held = device.naturallyHeld,
                                     isSideBySideSupported = false,
-                                    expected = STANDARD,
+                                    expected = STANDARD_BOUNCER,
                                 )
                             )
                         }
@@ -144,13 +144,13 @@
                             )
                         )
 
-                        if (expected.whenUnnaturallyHeld == SIDE_BY_SIDE) {
+                        if (expected.whenUnnaturallyHeld == BESIDE_USER_SWITCHER) {
                             add(
                                 TestCase(
                                     device = device,
                                     held = device.naturallyHeld.flip(),
                                     isSideBySideSupported = false,
-                                    expected = STANDARD,
+                                    expected = STANDARD_BOUNCER,
                                 )
                             )
                         }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/deviceentry/data/repository/DeviceEntryHapticsInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/deviceentry/data/repository/DeviceEntryHapticsInteractorTest.kt
index 9b8e581..59bcf01 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/deviceentry/data/repository/DeviceEntryHapticsInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/deviceentry/data/repository/DeviceEntryHapticsInteractorTest.kt
@@ -18,157 +18,165 @@
 
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
-import com.android.keyguard.logging.BiometricUnlockLogger
 import com.android.systemui.SysuiTestCase
-import com.android.systemui.biometrics.data.repository.FakeFingerprintPropertyRepository
+import com.android.systemui.biometrics.data.repository.fingerprintPropertyRepository
 import com.android.systemui.biometrics.shared.model.FingerprintSensorType
 import com.android.systemui.biometrics.shared.model.SensorStrength
-import com.android.systemui.classifier.FalsingCollector
 import com.android.systemui.coroutines.collectLastValue
-import com.android.systemui.deviceentry.domain.interactor.DeviceEntryHapticsInteractor
-import com.android.systemui.keyevent.data.repository.FakeKeyEventRepository
-import com.android.systemui.keyevent.domain.interactor.KeyEventInteractor
-import com.android.systemui.keyguard.data.repository.FakeBiometricSettingsRepository
-import com.android.systemui.plugins.statusbar.StatusBarStateController
-import com.android.systemui.power.data.repository.FakePowerRepository
-import com.android.systemui.power.domain.interactor.PowerInteractor
+import com.android.systemui.deviceentry.domain.interactor.deviceEntryHapticsInteractor
+import com.android.systemui.keyevent.data.repository.fakeKeyEventRepository
+import com.android.systemui.keyguard.data.repository.biometricSettingsRepository
+import com.android.systemui.keyguard.data.repository.deviceEntryFingerprintAuthRepository
+import com.android.systemui.keyguard.data.repository.fakeDeviceEntryFaceAuthRepository
+import com.android.systemui.keyguard.shared.model.BiometricUnlockSource
+import com.android.systemui.keyguard.shared.model.FailFingerprintAuthenticationStatus
+import com.android.systemui.keyguard.shared.model.FailedFaceAuthenticationStatus
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.power.data.repository.powerRepository
 import com.android.systemui.power.shared.model.WakeSleepReason
 import com.android.systemui.power.shared.model.WakefulnessState
-import com.android.systemui.statusbar.phone.ScreenOffAnimationController
-import com.android.systemui.util.time.FakeSystemClock
+import com.android.systemui.testKosmos
+import com.android.systemui.util.time.fakeSystemClock
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 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.Mockito.mock
 
 @OptIn(ExperimentalCoroutinesApi::class)
 @SmallTest
 @RunWith(AndroidJUnit4::class)
 class DeviceEntryHapticsInteractorTest : SysuiTestCase() {
-
-    private lateinit var repository: DeviceEntryHapticsRepository
-    private lateinit var fingerprintPropertyRepository: FakeFingerprintPropertyRepository
-    private lateinit var biometricSettingsRepository: FakeBiometricSettingsRepository
-    private lateinit var keyEventRepository: FakeKeyEventRepository
-    private lateinit var powerRepository: FakePowerRepository
-    private lateinit var systemClock: FakeSystemClock
-    private lateinit var underTest: DeviceEntryHapticsInteractor
-
-    @Before
-    fun setUp() {
-        repository = DeviceEntryHapticsRepositoryImpl()
-        fingerprintPropertyRepository = FakeFingerprintPropertyRepository()
-        biometricSettingsRepository = FakeBiometricSettingsRepository()
-        keyEventRepository = FakeKeyEventRepository()
-        powerRepository = FakePowerRepository()
-        systemClock = FakeSystemClock()
-        underTest =
-            DeviceEntryHapticsInteractor(
-                repository = repository,
-                fingerprintPropertyRepository = fingerprintPropertyRepository,
-                biometricSettingsRepository = biometricSettingsRepository,
-                keyEventInteractor = KeyEventInteractor(keyEventRepository),
-                powerInteractor =
-                    PowerInteractor(
-                        powerRepository,
-                        mock(FalsingCollector::class.java),
-                        mock(ScreenOffAnimationController::class.java),
-                        mock(StatusBarStateController::class.java),
-                    ),
-                systemClock = systemClock,
-                logger = mock(BiometricUnlockLogger::class.java),
-            )
-    }
+    private val kosmos = testKosmos()
+    private val testScope = kosmos.testScope
+    private val underTest = kosmos.deviceEntryHapticsInteractor
 
     @Test
-    fun nonPowerButtonFPS_vibrateSuccess() = runTest {
-        val playSuccessHaptic by collectLastValue(underTest.playSuccessHaptic)
-        setFingerprintSensorType(FingerprintSensorType.UDFPS_ULTRASONIC)
-        underTest.vibrateSuccess()
-        assertThat(playSuccessHaptic).isTrue()
-    }
+    fun nonPowerButtonFPS_vibrateSuccess() =
+        testScope.runTest {
+            val playSuccessHaptic by collectLastValue(underTest.playSuccessHaptic)
+            setFingerprintSensorType(FingerprintSensorType.UDFPS_ULTRASONIC)
+            runCurrent()
+            enterDeviceFromBiometricUnlock()
+            assertThat(playSuccessHaptic).isNotNull()
+        }
 
     @Test
-    fun powerButtonFPS_vibrateSuccess() = runTest {
-        val playSuccessHaptic by collectLastValue(underTest.playSuccessHaptic)
-        setPowerButtonFingerprintProperty()
-        setFingerprintEnrolled()
-        keyEventRepository.setPowerButtonDown(false)
+    fun powerButtonFPS_vibrateSuccess() =
+        testScope.runTest {
+            val playSuccessHaptic by collectLastValue(underTest.playSuccessHaptic)
+            setPowerButtonFingerprintProperty()
+            setFingerprintEnrolled()
+            kosmos.fakeKeyEventRepository.setPowerButtonDown(false)
 
-        // It's been 10 seconds since the last power button wakeup
-        setAwakeFromPowerButton()
-        runCurrent()
-        systemClock.setUptimeMillis(systemClock.uptimeMillis() + 10000)
+            // It's been 10 seconds since the last power button wakeup
+            setAwakeFromPowerButton()
+            runCurrent()
+            kosmos.fakeSystemClock.setUptimeMillis(kosmos.fakeSystemClock.uptimeMillis() + 10000)
 
-        underTest.vibrateSuccess()
-        assertThat(playSuccessHaptic).isTrue()
-    }
+            enterDeviceFromBiometricUnlock()
+            assertThat(playSuccessHaptic).isNotNull()
+        }
 
     @Test
-    fun powerButtonFPS_powerDown_doNotVibrateSuccess() = runTest {
-        val playSuccessHaptic by collectLastValue(underTest.playSuccessHaptic)
-        setPowerButtonFingerprintProperty()
-        setFingerprintEnrolled()
-        keyEventRepository.setPowerButtonDown(true) // power button is currently DOWN
+    fun powerButtonFPS_powerDown_doNotVibrateSuccess() =
+        testScope.runTest {
+            val playSuccessHaptic by collectLastValue(underTest.playSuccessHaptic)
+            setPowerButtonFingerprintProperty()
+            setFingerprintEnrolled()
+            kosmos.fakeKeyEventRepository.setPowerButtonDown(true) // power button is currently DOWN
 
-        // It's been 10 seconds since the last power button wakeup
-        setAwakeFromPowerButton()
-        runCurrent()
-        systemClock.setUptimeMillis(systemClock.uptimeMillis() + 10000)
+            // It's been 10 seconds since the last power button wakeup
+            setAwakeFromPowerButton()
+            runCurrent()
+            kosmos.fakeSystemClock.setUptimeMillis(kosmos.fakeSystemClock.uptimeMillis() + 10000)
 
-        underTest.vibrateSuccess()
-        assertThat(playSuccessHaptic).isFalse()
-    }
+            enterDeviceFromBiometricUnlock()
+            assertThat(playSuccessHaptic).isNull()
+        }
 
     @Test
-    fun powerButtonFPS_powerButtonRecentlyPressed_doNotVibrateSuccess() = runTest {
-        val playSuccessHaptic by collectLastValue(underTest.playSuccessHaptic)
-        setPowerButtonFingerprintProperty()
-        setFingerprintEnrolled()
-        keyEventRepository.setPowerButtonDown(false)
+    fun powerButtonFPS_powerButtonRecentlyPressed_doNotVibrateSuccess() =
+        testScope.runTest {
+            val playSuccessHaptic by collectLastValue(underTest.playSuccessHaptic)
+            setPowerButtonFingerprintProperty()
+            setFingerprintEnrolled()
+            kosmos.fakeKeyEventRepository.setPowerButtonDown(false)
 
-        // It's only been 50ms since the last power button wakeup
-        setAwakeFromPowerButton()
-        runCurrent()
-        systemClock.setUptimeMillis(systemClock.uptimeMillis() + 50)
+            // It's only been 50ms since the last power button wakeup
+            setAwakeFromPowerButton()
+            runCurrent()
+            kosmos.fakeSystemClock.setUptimeMillis(kosmos.fakeSystemClock.uptimeMillis() + 50)
 
-        underTest.vibrateSuccess()
-        assertThat(playSuccessHaptic).isFalse()
-    }
+            enterDeviceFromBiometricUnlock()
+            assertThat(playSuccessHaptic).isNull()
+        }
 
     @Test
-    fun nonPowerButtonFPS_vibrateError() = runTest {
-        val playErrorHaptic by collectLastValue(underTest.playErrorHaptic)
-        setFingerprintSensorType(FingerprintSensorType.UDFPS_ULTRASONIC)
-        underTest.vibrateError()
-        assertThat(playErrorHaptic).isTrue()
-    }
+    fun nonPowerButtonFPS_vibrateError() =
+        testScope.runTest {
+            val playErrorHaptic by collectLastValue(underTest.playErrorHaptic)
+            setFingerprintSensorType(FingerprintSensorType.UDFPS_ULTRASONIC)
+            runCurrent()
+            fingerprintFailure()
+            assertThat(playErrorHaptic).isNotNull()
+        }
 
     @Test
-    fun powerButtonFPS_vibrateError() = runTest {
-        val playErrorHaptic by collectLastValue(underTest.playErrorHaptic)
-        setPowerButtonFingerprintProperty()
-        setFingerprintEnrolled()
-        underTest.vibrateError()
-        assertThat(playErrorHaptic).isTrue()
-    }
+    fun nonPowerButtonFPS_coExFaceFailure_doNotVibrateError() =
+        testScope.runTest {
+            val playErrorHaptic by collectLastValue(underTest.playErrorHaptic)
+            setFingerprintSensorType(FingerprintSensorType.UDFPS_ULTRASONIC)
+            coExEnrolledAndEnabled()
+            runCurrent()
+            faceFailure()
+            assertThat(playErrorHaptic).isNull()
+        }
 
     @Test
-    fun powerButtonFPS_powerDown_doNotVibrateError() = runTest {
-        val playErrorHaptic by collectLastValue(underTest.playErrorHaptic)
-        setPowerButtonFingerprintProperty()
-        setFingerprintEnrolled()
-        keyEventRepository.setPowerButtonDown(true)
-        underTest.vibrateError()
-        assertThat(playErrorHaptic).isFalse()
+    fun powerButtonFPS_vibrateError() =
+        testScope.runTest {
+            val playErrorHaptic by collectLastValue(underTest.playErrorHaptic)
+            setPowerButtonFingerprintProperty()
+            setFingerprintEnrolled()
+            runCurrent()
+            fingerprintFailure()
+            assertThat(playErrorHaptic).isNotNull()
+        }
+
+    @Test
+    fun powerButtonFPS_powerDown_doNotVibrateError() =
+        testScope.runTest {
+            val playErrorHaptic by collectLastValue(underTest.playErrorHaptic)
+            setPowerButtonFingerprintProperty()
+            setFingerprintEnrolled()
+            kosmos.fakeKeyEventRepository.setPowerButtonDown(true)
+            runCurrent()
+            fingerprintFailure()
+            assertThat(playErrorHaptic).isNull()
+        }
+
+    private suspend fun enterDeviceFromBiometricUnlock() {
+        kosmos.fakeDeviceEntryRepository.enteringDeviceFromBiometricUnlock(
+            BiometricUnlockSource.FINGERPRINT_SENSOR
+        )
+    }
+
+    private fun fingerprintFailure() {
+        kosmos.deviceEntryFingerprintAuthRepository.setAuthenticationStatus(
+            FailFingerprintAuthenticationStatus
+        )
+    }
+
+    private fun faceFailure() {
+        kosmos.fakeDeviceEntryFaceAuthRepository.setAuthenticationStatus(
+            FailedFaceAuthenticationStatus()
+        )
     }
 
     private fun setFingerprintSensorType(fingerprintSensorType: FingerprintSensorType) {
-        fingerprintPropertyRepository.setProperties(
+        kosmos.fingerprintPropertyRepository.setProperties(
             sensorId = 0,
             strength = SensorStrength.STRONG,
             sensorType = fingerprintSensorType,
@@ -181,15 +189,20 @@
     }
 
     private fun setFingerprintEnrolled() {
-        biometricSettingsRepository.setIsFingerprintAuthEnrolledAndEnabled(true)
+        kosmos.biometricSettingsRepository.setIsFingerprintAuthEnrolledAndEnabled(true)
     }
 
     private fun setAwakeFromPowerButton() {
-        powerRepository.updateWakefulness(
+        kosmos.powerRepository.updateWakefulness(
             WakefulnessState.AWAKE,
             WakeSleepReason.POWER_BUTTON,
             WakeSleepReason.POWER_BUTTON,
             powerButtonLaunchGestureTriggered = false,
         )
     }
+
+    private fun coExEnrolledAndEnabled() {
+        setFingerprintEnrolled()
+        kosmos.biometricSettingsRepository.setIsFaceAuthEnrolledAndEnabled(true)
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/display/domain/interactor/ConnectedDisplayInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/display/domain/interactor/ConnectedDisplayInteractorTest.kt
index 42b0f50..37c7409 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/display/domain/interactor/ConnectedDisplayInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/display/domain/interactor/ConnectedDisplayInteractorTest.kt
@@ -18,6 +18,7 @@
 
 import android.companion.virtual.VirtualDeviceManager
 import android.companion.virtual.flags.Flags.FLAG_INTERACTIVE_SCREEN_MIRROR
+import android.platform.test.annotations.EnableFlags
 import android.testing.AndroidTestingRunner
 import android.testing.TestableLooper
 import android.view.Display
@@ -75,7 +76,6 @@
 
     @Before
     fun setup() {
-        mSetFlagsRule.disableFlags(FLAG_INTERACTIVE_SCREEN_MIRROR)
         whenever(virtualDeviceManager.isVirtualDeviceOwnedMirrorDisplay(anyInt())).thenReturn(false)
         fakeKeyguardRepository.setKeyguardShowing(false)
     }
@@ -160,9 +160,9 @@
         }
 
     @Test
+    @EnableFlags(FLAG_INTERACTIVE_SCREEN_MIRROR)
     fun displayState_virtualDeviceOwnedMirrorVirtualDisplay_connected() =
         testScope.runTest {
-            mSetFlagsRule.enableFlags(FLAG_INTERACTIVE_SCREEN_MIRROR)
             whenever(virtualDeviceManager.isVirtualDeviceOwnedMirrorDisplay(anyInt()))
                 .thenReturn(true)
             val value by lastValue()
@@ -183,9 +183,9 @@
         }
 
     @Test
+    @EnableFlags(FLAG_INTERACTIVE_SCREEN_MIRROR)
     fun virtualDeviceOwnedMirrorVirtualDisplay_emitsConnectedDisplayAddition() =
         testScope.runTest {
-            mSetFlagsRule.enableFlags(FLAG_INTERACTIVE_SCREEN_MIRROR)
             whenever(virtualDeviceManager.isVirtualDeviceOwnedMirrorDisplay(anyInt()))
                 .thenReturn(true)
             var count = 0
diff --git a/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsClassicDebugTest.kt b/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsClassicDebugTest.kt
index a903d25..523127e0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsClassicDebugTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsClassicDebugTest.kt
@@ -21,6 +21,8 @@
 import android.content.pm.PackageManager.NameNotFoundException
 import android.content.res.Resources
 import android.content.res.Resources.NotFoundException
+import android.platform.test.annotations.DisableFlags
+import android.platform.test.annotations.EnableFlags
 import android.test.suitebuilder.annotation.SmallTest
 import com.android.systemui.Flags.FLAG_SYSUI_TEAMFOOD
 import com.android.systemui.SysuiTestCase
@@ -68,15 +70,14 @@
     private val serverFlagReader = ServerFlagReaderFake()
 
     private val teamfoodableFlagA = UnreleasedFlag(name = "a", namespace = "test", teamfood = true)
-    private val teamfoodableFlagB = ReleasedFlag(name = "b", namespace = "test", teamfood = true)
+    private val releasedFlagB = ReleasedFlag(name = "b", namespace = "test")
 
     @Before
     fun setup() {
         MockitoAnnotations.initMocks(this)
-        mSetFlagsRule.disableFlags(FLAG_SYSUI_TEAMFOOD)
 
         flagMap.put(teamfoodableFlagA.name, teamfoodableFlagA)
-        flagMap.put(teamfoodableFlagB.name, teamfoodableFlagB)
+        flagMap.put(releasedFlagB.name, releasedFlagB)
         mFeatureFlagsClassicDebug =
             FeatureFlagsClassicDebug(
                 flagManager,
@@ -99,7 +100,6 @@
 
     @Test
     fun readBooleanFlag() {
-        // Remember that the TEAMFOOD flag is id#1 and has special behavior.
         whenever(flagManager.readFlagValue<Boolean>(eq("3"), any())).thenReturn(true)
         whenever(flagManager.readFlagValue<Boolean>(eq("4"), any())).thenReturn(false)
 
@@ -122,9 +122,10 @@
     }
 
     @Test
+    @DisableFlags(FLAG_SYSUI_TEAMFOOD)
     fun teamFoodFlag_False() {
         assertThat(mFeatureFlagsClassicDebug.isEnabled(teamfoodableFlagA)).isFalse()
-        assertThat(mFeatureFlagsClassicDebug.isEnabled(teamfoodableFlagB)).isTrue()
+        assertThat(mFeatureFlagsClassicDebug.isEnabled(releasedFlagB)).isTrue()
 
         // Regular boolean flags should still test the same.
         // Only our teamfoodableFlag should change.
@@ -132,10 +133,10 @@
     }
 
     @Test
+    @EnableFlags(FLAG_SYSUI_TEAMFOOD)
     fun teamFoodFlag_True() {
-        mSetFlagsRule.enableFlags(FLAG_SYSUI_TEAMFOOD)
         assertThat(mFeatureFlagsClassicDebug.isEnabled(teamfoodableFlagA)).isTrue()
-        assertThat(mFeatureFlagsClassicDebug.isEnabled(teamfoodableFlagB)).isTrue()
+        assertThat(mFeatureFlagsClassicDebug.isEnabled(releasedFlagB)).isTrue()
 
         // Regular boolean flags should still test the same.
         // Only our teamfoodableFlag should change.
@@ -143,14 +144,14 @@
     }
 
     @Test
+    @EnableFlags(FLAG_SYSUI_TEAMFOOD)
     fun teamFoodFlag_Overridden() {
         whenever(flagManager.readFlagValue<Boolean>(eq(teamfoodableFlagA.name), any()))
             .thenReturn(true)
-        whenever(flagManager.readFlagValue<Boolean>(eq(teamfoodableFlagB.name), any()))
+        whenever(flagManager.readFlagValue<Boolean>(eq(releasedFlagB.name), any()))
             .thenReturn(false)
-        mSetFlagsRule.enableFlags(FLAG_SYSUI_TEAMFOOD)
         assertThat(mFeatureFlagsClassicDebug.isEnabled(teamfoodableFlagA)).isTrue()
-        assertThat(mFeatureFlagsClassicDebug.isEnabled(teamfoodableFlagB)).isFalse()
+        assertThat(mFeatureFlagsClassicDebug.isEnabled(releasedFlagB)).isFalse()
 
         // Regular boolean flags should still test the same.
         // Only our teamfoodableFlag should change.
@@ -400,6 +401,7 @@
     }
 
     @Test
+    @DisableFlags(FLAG_SYSUI_TEAMFOOD)
     fun serverSide_OverrideUncached_NoRestart() {
         // No one has read the flag, so it's not in the cache.
         serverFlagReader.setFlagValue(
@@ -411,6 +413,7 @@
     }
 
     @Test
+    @DisableFlags(FLAG_SYSUI_TEAMFOOD)
     fun serverSide_Override_Restarts() {
         // Read it to put it in the cache.
         mFeatureFlagsClassicDebug.isEnabled(teamfoodableFlagA)
@@ -423,6 +426,7 @@
     }
 
     @Test
+    @DisableFlags(FLAG_SYSUI_TEAMFOOD)
     fun serverSide_RedundantOverride_NoRestart() {
         // Read it to put it in the cache.
         mFeatureFlagsClassicDebug.isEnabled(teamfoodableFlagA)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/CustomizationProviderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/CustomizationProviderTest.kt
index 40c9432..076d725 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/CustomizationProviderTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/CustomizationProviderTest.kt
@@ -53,9 +53,11 @@
 import com.android.systemui.res.R
 import com.android.systemui.settings.UserFileManager
 import com.android.systemui.settings.UserTracker
+import com.android.systemui.shade.domain.interactor.shadeInteractor
 import com.android.systemui.shared.customization.data.content.CustomizationProviderContract as Contract
 import com.android.systemui.shared.keyguard.shared.model.KeyguardQuickAffordanceSlots
 import com.android.systemui.statusbar.policy.KeyguardStateController
+import com.android.systemui.testKosmos
 import com.android.systemui.util.FakeSharedPreferences
 import com.android.systemui.util.mockito.any
 import com.android.systemui.util.mockito.mock
@@ -101,6 +103,8 @@
     private lateinit var underTest: CustomizationProvider
     private lateinit var testScope: TestScope
 
+    private val kosmos = testKosmos()
+
     @Before
     fun setUp() {
         MockitoAnnotations.initMocks(this)
@@ -185,6 +189,7 @@
                                 },
                         )
                         .keyguardInteractor,
+                shadeInteractor = kosmos.shadeInteractor,
                 lockPatternUtils = lockPatternUtils,
                 keyguardStateController = keyguardStateController,
                 userTracker = userTracker,
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 d246f0e..ae5f625 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
@@ -97,6 +97,7 @@
 import com.android.systemui.log.SessionTracker;
 import com.android.systemui.navigationbar.NavigationModeController;
 import com.android.systemui.scene.FakeWindowRootViewComponent;
+import com.android.systemui.scene.shared.flag.SceneContainerFlags;
 import com.android.systemui.scene.ui.view.WindowRootView;
 import com.android.systemui.settings.UserTracker;
 import com.android.systemui.shade.NotificationShadeWindowControllerImpl;
@@ -214,6 +215,7 @@
     private @Mock CoroutineDispatcher mDispatcher;
     private @Mock DreamingToLockscreenTransitionViewModel mDreamingToLockscreenTransitionViewModel;
     private @Mock SystemPropertiesHelper mSystemPropertiesHelper;
+    private @Mock SceneContainerFlags mSceneContainerFlags;
 
     private FakeFeatureFlags mFeatureFlags;
     private final int mDefaultUserId = 100;
@@ -258,7 +260,8 @@
                 () -> mShadeInteractor,
                 mShadeWindowLogger,
                 () -> mSelectedUserInteractor,
-                mUserTracker);
+                mUserTracker,
+                mSceneContainerFlags);
         mFeatureFlags = new FakeFeatureFlags();
         mFeatureFlags.set(Flags.KEYGUARD_WM_STATE_REFACTOR, false);
         mFeatureFlags.set(Flags.REFACTOR_GETCURRENTUSER, true);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardFaceAuthInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardFaceAuthInteractorTest.kt
index de12b8f..4ab8e28 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardFaceAuthInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardFaceAuthInteractorTest.kt
@@ -17,8 +17,10 @@
 
 package com.android.systemui.keyguard.domain.interactor
 
+import android.app.trust.TrustManager
 import android.content.pm.UserInfo
 import android.hardware.biometrics.BiometricFaceConstants
+import android.hardware.biometrics.BiometricSourceType
 import android.os.Handler
 import android.os.PowerManager
 import androidx.test.ext.junit.runners.AndroidJUnit4
@@ -62,6 +64,7 @@
 import com.android.systemui.user.data.model.SelectionStatus
 import com.android.systemui.user.data.repository.FakeUserRepository
 import com.android.systemui.user.domain.interactor.SelectedUserInteractor
+import com.android.systemui.util.mockito.eq
 import com.android.systemui.util.mockito.whenever
 import com.android.systemui.util.time.FakeSystemClock
 import com.google.common.truth.Truth.assertThat
@@ -74,8 +77,11 @@
 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.mock
+import org.mockito.Mockito.never
+import org.mockito.Mockito.verify
 import org.mockito.MockitoAnnotations
 
 @OptIn(ExperimentalCoroutinesApi::class)
@@ -99,7 +105,8 @@
 
     @Mock private lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor
     @Mock private lateinit var faceWakeUpTriggersConfig: FaceWakeUpTriggersConfig
-    @Mock private lateinit var mSelectedUserInteractor: SelectedUserInteractor
+    @Mock private lateinit var selectedUserInteractor: SelectedUserInteractor
+    @Mock private lateinit var trustManager: TrustManager
 
     @Before
     fun setup() {
@@ -146,7 +153,7 @@
                         keyguardUpdateMonitor,
                         FakeTrustRepository(),
                         testScope.backgroundScope,
-                        mSelectedUserInteractor,
+                        selectedUserInteractor,
                         underTest,
                     )
                 },
@@ -169,6 +176,7 @@
                 faceWakeUpTriggersConfig,
                 powerInteractor,
                 fakeBiometricSettingsRepository,
+                trustManager,
             )
     }
 
@@ -498,6 +506,22 @@
             assertThat(faceAuthRepository.isLockedOut.value).isTrue()
         }
 
+    @Test
+    fun whenIsAuthenticatedFalse_clearFaceBiometrics() =
+        testScope.runTest {
+            underTest.start()
+
+            faceAuthRepository.isAuthenticated.value = true
+            runCurrent()
+            verify(trustManager, never())
+                .clearAllBiometricRecognized(eq(BiometricSourceType.FACE), anyInt())
+
+            faceAuthRepository.isAuthenticated.value = false
+            runCurrent()
+
+            verify(trustManager).clearAllBiometricRecognized(eq(BiometricSourceType.FACE), anyInt())
+        }
+
     companion object {
         private const val primaryUserId = 1
         private val primaryUser = UserInfo(primaryUserId, "test user", UserInfo.FLAG_PRIMARY)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorParameterizedTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorParameterizedTest.kt
index 66c8a22..b4ae7e3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorParameterizedTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorParameterizedTest.kt
@@ -46,7 +46,9 @@
 import com.android.systemui.settings.FakeUserTracker
 import com.android.systemui.settings.UserFileManager
 import com.android.systemui.settings.UserTracker
+import com.android.systemui.shade.domain.interactor.shadeInteractor
 import com.android.systemui.statusbar.policy.KeyguardStateController
+import com.android.systemui.testKosmos
 import com.android.systemui.util.FakeSharedPreferences
 import com.android.systemui.util.mockito.any
 import com.android.systemui.util.mockito.mock
@@ -242,6 +244,8 @@
     private lateinit var biometricSettingsRepository: FakeBiometricSettingsRepository
     private lateinit var userTracker: UserTracker
 
+    private val kosmos = testKosmos()
+
     @Before
     fun setUp() {
         MockitoAnnotations.initMocks(this)
@@ -311,6 +315,7 @@
                             featureFlags = featureFlags,
                         )
                         .keyguardInteractor,
+                shadeInteractor = kosmos.shadeInteractor,
                 lockPatternUtils = lockPatternUtils,
                 keyguardStateController = keyguardStateController,
                 userTracker = userTracker,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt
index bf6d5c4..b8a8bdf 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt
@@ -43,7 +43,6 @@
 import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.setAwakeForTest
 import com.android.systemui.power.domain.interactor.PowerInteractorFactory
 import com.android.systemui.shade.data.repository.FakeShadeRepository
-import com.android.systemui.shade.domain.model.ShadeModel
 import com.android.systemui.user.domain.interactor.SelectedUserInteractor
 import com.android.systemui.util.mockito.any
 import com.android.systemui.util.mockito.mock
@@ -1138,7 +1137,7 @@
             runCurrent()
 
             // WHEN primary bouncer shows
-            bouncerRepository.setPrimaryShow(true) // beverlyt
+            bouncerRepository.setPrimaryShow(true)
             runCurrent()
 
             val info =
@@ -1233,6 +1232,36 @@
         }
 
     @Test
+    fun dreamingToAod() =
+        testScope.runTest {
+            // GIVEN a prior transition has run to DREAMING
+            keyguardRepository.setDreaming(true)
+            runTransitionAndSetWakefulness(KeyguardState.LOCKSCREEN, KeyguardState.DREAMING)
+            runCurrent()
+
+            // WHEN the device starts DOZE_AOD
+            keyguardRepository.setDozeTransitionModel(
+                DozeTransitionModel(
+                    from = DozeStateModel.INITIALIZED,
+                    to = DozeStateModel.DOZE_AOD,
+                )
+            )
+            runCurrent()
+
+            val info =
+                withArgCaptor<TransitionInfo> {
+                    verify(transitionRepository).startTransition(capture())
+                }
+            // THEN a transition to AOD should occur
+            assertThat(info.ownerName).isEqualTo("FromDreamingTransitionInteractor")
+            assertThat(info.from).isEqualTo(KeyguardState.DREAMING)
+            assertThat(info.to).isEqualTo(KeyguardState.AOD)
+            assertThat(info.animator).isNotNull()
+
+            coroutineContext.cancelChildren()
+        }
+
+    @Test
     fun lockscreenToOccluded() =
         testScope.runTest {
             // GIVEN a prior transition has run to LOCKSCREEN
@@ -1329,12 +1358,8 @@
             // GIVEN the keyguard is showing locked
             keyguardRepository.setStatusBarState(StatusBarState.KEYGUARD)
             runCurrent()
-            shadeRepository.setShadeModel(
-                ShadeModel(
-                    expansionAmount = .9f,
-                    isUserDragging = true,
-                )
-            )
+            shadeRepository.setLegacyShadeTracking(true)
+            shadeRepository.setLegacyShadeExpansion(.9f)
             runCurrent()
 
             // THEN a transition from LOCKSCREEN => PRIMARY_BOUNCER should occur
@@ -1350,12 +1375,8 @@
             // WHEN the user stops dragging and shade is back to expanded
             clearInvocations(transitionRepository)
             runTransitionAndSetWakefulness(KeyguardState.LOCKSCREEN, KeyguardState.PRIMARY_BOUNCER)
-            shadeRepository.setShadeModel(
-                ShadeModel(
-                    expansionAmount = 1f,
-                    isUserDragging = false,
-                )
-            )
+            shadeRepository.setLegacyShadeTracking(false)
+            shadeRepository.setLegacyShadeExpansion(1f)
             runCurrent()
 
             // THEN a transition from PRIMARY_BOUNCER => LOCKSCREEN should occur
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultDeviceEntrySectionTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultDeviceEntrySectionTest.kt
index 67fba42..22569e2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultDeviceEntrySectionTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultDeviceEntrySectionTest.kt
@@ -39,6 +39,7 @@
 import com.android.systemui.res.R
 import com.android.systemui.shade.NotificationPanelView
 import com.android.systemui.statusbar.NotificationShadeWindowController
+import com.android.systemui.statusbar.VibratorHelper
 import com.android.systemui.statusbar.gesture.TapGestureDetector
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -91,6 +92,7 @@
                 TestScope().backgroundScope,
                 { mock(SwipeUpAnywhereGestureHandler::class.java) },
                 { mock(TapGestureDetector::class.java) },
+                { mock(VibratorHelper::class.java) },
             )
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/BouncerToGoneFlowsTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/BouncerToGoneFlowsTest.kt
index c9b14a4..daafe12 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/BouncerToGoneFlowsTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/BouncerToGoneFlowsTest.kt
@@ -22,7 +22,7 @@
 import com.android.systemui.bouncer.domain.interactor.primaryBouncerInteractor
 import com.android.systemui.coroutines.collectValues
 import com.android.systemui.flags.Flags
-import com.android.systemui.flags.featureFlagsClassic
+import com.android.systemui.flags.fakeFeatureFlagsClassic
 import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
 import com.android.systemui.keyguard.shared.model.KeyguardState
 import com.android.systemui.keyguard.shared.model.KeyguardState.PRIMARY_BOUNCER
@@ -55,7 +55,7 @@
 
     private val kosmos =
         testKosmos().apply {
-            featureFlagsClassic.apply {
+            fakeFeatureFlagsClassic.apply {
                 set(Flags.REFACTOR_KEYGUARD_DISMISS_INTENT, false)
                 set(Flags.FULL_SCREEN_USER_SWITCHER, false)
             }
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 1584be0..af38523c 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
@@ -54,9 +54,11 @@
 import com.android.systemui.res.R
 import com.android.systemui.settings.UserFileManager
 import com.android.systemui.settings.UserTracker
+import com.android.systemui.shade.domain.interactor.shadeInteractor
 import com.android.systemui.shared.keyguard.shared.model.KeyguardQuickAffordanceSlots
 import com.android.systemui.statusbar.policy.AccessibilityManagerWrapper
 import com.android.systemui.statusbar.policy.KeyguardStateController
+import com.android.systemui.testKosmos
 import com.android.systemui.util.FakeSharedPreferences
 import com.android.systemui.util.mockito.any
 import com.android.systemui.util.mockito.mock
@@ -108,6 +110,8 @@
     private lateinit var dockManager: DockManagerFake
     private lateinit var biometricSettingsRepository: FakeBiometricSettingsRepository
 
+    private val kosmos = testKosmos()
+
     @Before
     fun setUp() {
         MockitoAnnotations.initMocks(this)
@@ -221,6 +225,7 @@
                 quickAffordanceInteractor =
                     KeyguardQuickAffordanceInteractor(
                         keyguardInteractor = keyguardInteractor,
+                        shadeInteractor = kosmos.shadeInteractor,
                         lockPatternUtils = lockPatternUtils,
                         keyguardStateController = keyguardStateController,
                         userTracker = userTracker,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordancesCombinedViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordancesCombinedViewModelTest.kt
index 0c30d10..b6a661b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordancesCombinedViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordancesCombinedViewModelTest.kt
@@ -264,12 +264,14 @@
         whenever(lockscreenToPrimaryBouncerTransitionViewModel.shortcutsAlpha)
             .thenReturn(emptyFlow())
         whenever(shadeInteractor.qsExpansion).thenReturn(intendedShadeAlphaMutableStateFlow)
+        whenever(shadeInteractor.anyExpansion).thenReturn(MutableStateFlow(0f))
 
         underTest =
             KeyguardQuickAffordancesCombinedViewModel(
                 quickAffordanceInteractor =
                     KeyguardQuickAffordanceInteractor(
                         keyguardInteractor = keyguardInteractor,
+                        shadeInteractor = shadeInteractor,
                         lockPatternUtils = lockPatternUtils,
                         keyguardStateController = keyguardStateController,
                         userTracker = userTracker,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelTest.kt
index 58624d3..6878007 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelTest.kt
@@ -144,19 +144,43 @@
     @Test
     fun alpha() =
         testScope.runTest {
-            val value = collectLastValue(underTest.alpha)
-            assertThat(value()).isEqualTo(0f)
+            val alpha by collectLastValue(underTest.alpha)
+
+            keyguardTransitionRepository.sendTransitionSteps(
+                from = KeyguardState.OFF,
+                to = KeyguardState.LOCKSCREEN,
+                testScope = testScope,
+            )
 
             repository.setKeyguardAlpha(0.1f)
-            assertThat(value()).isEqualTo(0.1f)
+            assertThat(alpha).isEqualTo(0.1f)
             repository.setKeyguardAlpha(0.5f)
-            assertThat(value()).isEqualTo(0.5f)
+            assertThat(alpha).isEqualTo(0.5f)
             repository.setKeyguardAlpha(0.2f)
-            assertThat(value()).isEqualTo(0.2f)
+            assertThat(alpha).isEqualTo(0.2f)
             repository.setKeyguardAlpha(0f)
-            assertThat(value()).isEqualTo(0f)
+            assertThat(alpha).isEqualTo(0f)
             occludedToLockscreenAlpha.value = 0.8f
-            assertThat(value()).isEqualTo(0.8f)
+            assertThat(alpha).isEqualTo(0.8f)
+        }
+
+    @Test
+    fun alphaWhenGoneEqualsZero() =
+        testScope.runTest {
+            val alpha by collectLastValue(underTest.alpha)
+
+            keyguardTransitionRepository.sendTransitionSteps(
+                from = KeyguardState.LOCKSCREEN,
+                to = KeyguardState.GONE,
+                testScope = testScope,
+            )
+
+            repository.setKeyguardAlpha(0.1f)
+            assertThat(alpha).isEqualTo(0f)
+            repository.setKeyguardAlpha(0.5f)
+            assertThat(alpha).isEqualTo(0f)
+            repository.setKeyguardAlpha(1f)
+            assertThat(alpha).isEqualTo(0f)
         }
 
     @Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToAodTransitionViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToAodTransitionViewModelTest.kt
index c15a2c6..e139466 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToAodTransitionViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToAodTransitionViewModelTest.kt
@@ -23,7 +23,7 @@
 import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.coroutines.collectValues
 import com.android.systemui.flags.Flags.FULL_SCREEN_USER_SWITCHER
-import com.android.systemui.flags.featureFlagsClassic
+import com.android.systemui.flags.fakeFeatureFlagsClassic
 import com.android.systemui.keyguard.data.repository.biometricSettingsRepository
 import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
 import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
@@ -47,7 +47,9 @@
 @RunWith(AndroidJUnit4::class)
 class LockscreenToAodTransitionViewModelTest : SysuiTestCase() {
     private val kosmos =
-        testKosmos().apply { featureFlagsClassic.apply { set(FULL_SCREEN_USER_SWITCHER, false) } }
+        testKosmos().apply {
+            fakeFeatureFlagsClassic.apply { set(FULL_SCREEN_USER_SWITCHER, false) }
+        }
     private val testScope = kosmos.testScope
     private val repository = kosmos.fakeKeyguardTransitionRepository
     private val shadeRepository = kosmos.shadeRepository
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToPrimaryBouncerTransitionViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToPrimaryBouncerTransitionViewModelTest.kt
index b31968c..7a564ac 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToPrimaryBouncerTransitionViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToPrimaryBouncerTransitionViewModelTest.kt
@@ -21,7 +21,7 @@
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.flags.Flags
-import com.android.systemui.flags.featureFlagsClassic
+import com.android.systemui.flags.fakeFeatureFlagsClassic
 import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
 import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
 import com.android.systemui.keyguard.shared.model.KeyguardState
@@ -45,7 +45,7 @@
 class LockscreenToPrimaryBouncerTransitionViewModelTest : SysuiTestCase() {
     private val kosmos =
         testKosmos().apply {
-            featureFlagsClassic.apply { set(Flags.FULL_SCREEN_USER_SWITCHER, false) }
+            fakeFeatureFlagsClassic.apply { set(Flags.FULL_SCREEN_USER_SWITCHER, false) }
         }
     private val testScope = kosmos.testScope
     private val repository = kosmos.fakeKeyguardTransitionRepository
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/RecordIssueTileTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/RecordIssueTileTest.kt
new file mode 100644
index 0000000..d8199c5
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/RecordIssueTileTest.kt
@@ -0,0 +1,122 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.tiles
+
+import android.os.Handler
+import android.service.quicksettings.Tile
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper
+import androidx.test.filters.SmallTest
+import com.android.internal.logging.MetricsLogger
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.classifier.FalsingManagerFake
+import com.android.systemui.plugins.ActivityStarter
+import com.android.systemui.plugins.statusbar.StatusBarStateController
+import com.android.systemui.qs.QSHost
+import com.android.systemui.qs.QsEventLogger
+import com.android.systemui.qs.logging.QSLogger
+import com.android.systemui.res.R
+import com.android.systemui.util.mockito.whenever
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.MockitoAnnotations
+
+/**
+ * This class tests the functionality of the RecordIssueTile. The initial state of the tile is
+ * always be inactive at the start of these tests.
+ */
+@RunWith(AndroidTestingRunner::class)
+@TestableLooper.RunWithLooper(setAsMainLooper = true)
+@SmallTest
+class RecordIssueTileTest : SysuiTestCase() {
+
+    @Mock private lateinit var host: QSHost
+    @Mock private lateinit var qsEventLogger: QsEventLogger
+    @Mock private lateinit var metricsLogger: MetricsLogger
+    @Mock private lateinit var statusBarStateController: StatusBarStateController
+    @Mock private lateinit var activityStarter: ActivityStarter
+    @Mock private lateinit var qsLogger: QSLogger
+
+    private lateinit var tile: RecordIssueTile
+
+    @Before
+    fun setUp() {
+        MockitoAnnotations.initMocks(this)
+        whenever(host.context).thenReturn(mContext)
+
+        val testableLooper = TestableLooper.get(this)
+        tile =
+            RecordIssueTile(
+                host,
+                qsEventLogger,
+                testableLooper.looper,
+                Handler(testableLooper.looper),
+                FalsingManagerFake(),
+                metricsLogger,
+                statusBarStateController,
+                activityStarter,
+                qsLogger
+            )
+    }
+
+    @Test
+    fun qsTileUi_shouldLookCorrect_whenInactive() {
+        tile.isRecording = false
+
+        val testState = tile.newTileState()
+        tile.handleUpdateState(testState, null)
+
+        assertThat(testState.state).isEqualTo(Tile.STATE_INACTIVE)
+        assertThat(testState.secondaryLabel.toString())
+            .isEqualTo(mContext.getString(R.string.qs_record_issue_start))
+    }
+
+    @Test
+    fun qsTileUi_shouldLookCorrect_whenRecording() {
+        tile.isRecording = true
+
+        val testState = tile.newTileState()
+        tile.handleUpdateState(testState, null)
+
+        assertThat(testState.state).isEqualTo(Tile.STATE_ACTIVE)
+        assertThat(testState.secondaryLabel.toString())
+            .isEqualTo(mContext.getString(R.string.qs_record_issue_stop))
+    }
+
+    @Test
+    fun inActiveQsTile_switchesToActive_whenClicked() {
+        tile.isRecording = false
+
+        val testState = tile.newTileState()
+        tile.handleUpdateState(testState, null)
+
+        assertThat(testState.state).isEqualTo(Tile.STATE_INACTIVE)
+    }
+
+    @Test
+    fun activeQsTile_switchesToInActive_whenClicked() {
+        tile.isRecording = true
+
+        val testState = tile.newTileState()
+        tile.handleUpdateState(testState, null)
+
+        assertThat(testState.state).isEqualTo(Tile.STATE_ACTIVE)
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowControllerImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowControllerImplTest.java
index 39739e7..5ffbe65 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowControllerImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowControllerImplTest.java
@@ -75,6 +75,7 @@
 import com.android.systemui.scene.data.repository.SceneContainerRepository;
 import com.android.systemui.scene.domain.interactor.SceneInteractor;
 import com.android.systemui.scene.shared.flag.FakeSceneContainerFlags;
+import com.android.systemui.scene.shared.flag.SceneContainerFlags;
 import com.android.systemui.scene.shared.logger.SceneLogger;
 import com.android.systemui.settings.UserTracker;
 import com.android.systemui.shade.data.repository.FakeShadeRepository;
@@ -138,6 +139,7 @@
     @Mock private ShadeWindowLogger mShadeWindowLogger;
     @Mock private SelectedUserInteractor mSelectedUserInteractor;
     @Mock private UserTracker mUserTracker;
+    @Mock private SceneContainerFlags mSceneContainerFlags;
     @Captor private ArgumentCaptor<WindowManager.LayoutParams> mLayoutParameters;
     @Captor private ArgumentCaptor<StatusBarStateController.StateListener> mStateListener;
 
@@ -274,7 +276,8 @@
                 () -> mShadeInteractor,
                 mShadeWindowLogger,
                 () -> mSelectedUserInteractor,
-                mUserTracker) {
+                mUserTracker,
+                mSceneContainerFlags) {
                     @Override
                     protected boolean isDebuggable() {
                         return false;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/data/repository/ShadeRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/data/repository/ShadeRepositoryImplTest.kt
index f8aa359..750693c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/data/repository/ShadeRepositoryImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/data/repository/ShadeRepositoryImplTest.kt
@@ -19,33 +19,20 @@
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
-import com.android.systemui.shade.ShadeExpansionChangeEvent
-import com.android.systemui.shade.ShadeExpansionStateManager
-import com.android.systemui.shade.domain.model.ShadeModel
-import com.android.systemui.util.mockito.any
-import com.android.systemui.util.mockito.withArgCaptor
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.flow.launchIn
-import kotlinx.coroutines.flow.onEach
 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.Mock
-import org.mockito.Mockito.verify
-import org.mockito.Mockito.`when`
-import org.mockito.MockitoAnnotations
 
 @OptIn(ExperimentalCoroutinesApi::class)
 @SmallTest
 @RunWith(AndroidJUnit4::class)
 class ShadeRepositoryImplTest : SysuiTestCase() {
 
-    @Mock private lateinit var shadeExpansionStateManager: ShadeExpansionStateManager
     private val testDispatcher = StandardTestDispatcher()
     private val testScope = TestScope(testDispatcher)
 
@@ -53,57 +40,10 @@
 
     @Before
     fun setUp() {
-        MockitoAnnotations.initMocks(this)
-
-        underTest = ShadeRepositoryImpl(shadeExpansionStateManager)
-        `when`(shadeExpansionStateManager.addExpansionListener(any()))
-            .thenReturn(ShadeExpansionChangeEvent(0f, false, false, 0f))
+        underTest = ShadeRepositoryImpl()
     }
 
     @Test
-    fun shadeExpansionChangeEvent() =
-        testScope.runTest {
-            var latest: ShadeModel? = null
-            val job = underTest.shadeModel.onEach { latest = it }.launchIn(this)
-            runCurrent()
-            assertThat(latest?.expansionAmount).isEqualTo(0f)
-            assertThat(latest?.isExpanded).isEqualTo(false)
-            assertThat(latest?.isUserDragging).isEqualTo(false)
-
-            val captor = withArgCaptor {
-                verify(shadeExpansionStateManager).addExpansionListener(capture())
-            }
-
-            captor.onPanelExpansionChanged(
-                ShadeExpansionChangeEvent(
-                    fraction = 1f,
-                    expanded = true,
-                    tracking = false,
-                    dragDownPxAmount = 0f,
-                )
-            )
-            runCurrent()
-            assertThat(latest?.expansionAmount).isEqualTo(1f)
-            assertThat(latest?.isExpanded).isEqualTo(true)
-            assertThat(latest?.isUserDragging).isEqualTo(false)
-
-            captor.onPanelExpansionChanged(
-                ShadeExpansionChangeEvent(
-                    fraction = .67f,
-                    expanded = false,
-                    tracking = true,
-                    dragDownPxAmount = 0f,
-                )
-            )
-            runCurrent()
-            assertThat(latest?.expansionAmount).isEqualTo(.67f)
-            assertThat(latest?.isExpanded).isEqualTo(false)
-            assertThat(latest?.isUserDragging).isEqualTo(true)
-
-            job.cancel()
-        }
-
-    @Test
     fun updateQsExpansion() =
         testScope.runTest {
             assertThat(underTest.qsExpansion.value).isEqualTo(0f)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/AlertingNotificationManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/AlertingNotificationManagerTest.java
index b98dc00..a3cff87e6 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/AlertingNotificationManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/AlertingNotificationManagerTest.java
@@ -39,12 +39,15 @@
 
 import androidx.test.filters.SmallTest;
 
-import com.android.systemui.res.R;
 import com.android.systemui.SysuiTestCase;
+import com.android.systemui.res.R;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder;
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
 import com.android.systemui.statusbar.policy.HeadsUpManagerLogger;
+import com.android.systemui.util.settings.FakeGlobalSettings;
+import com.android.systemui.util.time.SystemClock;
+import com.android.systemui.util.time.SystemClockImpl;
 
 import org.junit.After;
 import org.junit.Before;
@@ -74,18 +77,26 @@
     protected final Runnable mTestTimeoutRunnable = () -> mTimedOut = true;
 
     protected Handler mTestHandler;
+    protected final FakeGlobalSettings mGlobalSettings = new FakeGlobalSettings();
+    protected final SystemClock mSystemClock = new SystemClockImpl();
     protected boolean mTimedOut = false;
 
     @Mock protected ExpandableNotificationRow mRow;
 
+    static {
+        assertThat(TEST_MINIMUM_DISPLAY_TIME).isLessThan(TEST_AUTO_DISMISS_TIME);
+        assertThat(TEST_AUTO_DISMISS_TIME).isLessThan(TEST_STICKY_AUTO_DISMISS_TIME);
+        assertThat(TEST_STICKY_AUTO_DISMISS_TIME).isLessThan(TEST_TIMEOUT_TIME);
+    }
+
     private static class TestableAlertingNotificationManager extends AlertingNotificationManager {
         private AlertEntry mLastCreatedEntry;
 
-        private TestableAlertingNotificationManager(Handler handler) {
-            super(new HeadsUpManagerLogger(logcatLogBuffer()), handler);
+        private TestableAlertingNotificationManager(Handler handler, SystemClock systemClock) {
+            super(new HeadsUpManagerLogger(logcatLogBuffer()), handler, systemClock);
             mMinimumDisplayTime = TEST_MINIMUM_DISPLAY_TIME;
-            mAutoDismissNotificationDecay = TEST_AUTO_DISMISS_TIME;
-            mStickyDisplayTime = TEST_STICKY_AUTO_DISMISS_TIME;
+            mAutoDismissTime = TEST_AUTO_DISMISS_TIME;
+            mStickyForSomeTimeAutoDismissTime = TEST_STICKY_AUTO_DISMISS_TIME;
         }
 
         @Override
@@ -107,7 +118,7 @@
     }
 
     protected AlertingNotificationManager createAlertingNotificationManager() {
-        return new TestableAlertingNotificationManager(mTestHandler);
+        return new TestableAlertingNotificationManager(mTestHandler, mSystemClock);
     }
 
     protected StatusBarNotification createSbn(int id, Notification n) {
@@ -167,10 +178,6 @@
     @Before
     public void setUp() {
         mTestHandler = Handler.createAsync(Looper.myLooper());
-
-        assertThat(TEST_MINIMUM_DISPLAY_TIME).isLessThan(TEST_AUTO_DISMISS_TIME);
-        assertThat(TEST_AUTO_DISMISS_TIME).isLessThan(TEST_STICKY_AUTO_DISMISS_TIME);
-        assertThat(TEST_STICKY_AUTO_DISMISS_TIME).isLessThan(TEST_TIMEOUT_TIME);
     }
 
     @After
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerTest.java
index 8bc5e70..34c7b09 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerTest.java
@@ -55,8 +55,10 @@
 import android.os.Bundle;
 import android.os.UserHandle;
 import android.os.UserManager;
+import android.platform.test.annotations.DisableFlags;
+import android.platform.test.annotations.EnableFlags;
+import android.platform.test.flag.junit.FlagsParameterization;
 import android.provider.Settings;
-import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
 import android.util.SparseArray;
 
@@ -96,12 +98,26 @@
 
 import java.util.ArrayList;
 import java.util.Collection;
+import java.util.List;
 import java.util.concurrent.Executor;
 
+import platform.test.runner.parameterized.ParameterizedAndroidJunit4;
+import platform.test.runner.parameterized.Parameters;
+
 @SmallTest
-@RunWith(AndroidTestingRunner.class)
+@RunWith(ParameterizedAndroidJunit4.class)
 @TestableLooper.RunWithLooper
 public class NotificationLockscreenUserManagerTest extends SysuiTestCase {
+
+    @Parameters(name = "{0}")
+    public static List<FlagsParameterization> getParams() {
+        return FlagsParameterization.allCombinationsOf(FLAG_ALLOW_PRIVATE_PROFILE);
+    }
+
+    public NotificationLockscreenUserManagerTest(FlagsParameterization flags) {
+        mSetFlagsRule.setFlagsParameterization(flags);
+    }
+
     private static final int TEST_PROFILE_USERHANDLE = 12;
     @Mock
     private NotificationPresenter mPresenter;
@@ -762,8 +778,8 @@
     }
 
     @Test
+    @EnableFlags(FLAG_ALLOW_PRIVATE_PROFILE)
     public void testProfileAvailabilityIntent() {
-        mSetFlagsRule.enableFlags(FLAG_ALLOW_PRIVATE_PROFILE);
         mLockscreenUserManager.mCurrentProfiles.clear();
         assertEquals(0, mLockscreenUserManager.mCurrentProfiles.size());
         mLockscreenUserManager.mCurrentProfiles.append(0, mock(UserInfo.class));
@@ -773,8 +789,8 @@
     }
 
     @Test
+    @EnableFlags(FLAG_ALLOW_PRIVATE_PROFILE)
     public void testProfileUnAvailabilityIntent() {
-        mSetFlagsRule.enableFlags(FLAG_ALLOW_PRIVATE_PROFILE);
         mLockscreenUserManager.mCurrentProfiles.clear();
         assertEquals(0, mLockscreenUserManager.mCurrentProfiles.size());
         mLockscreenUserManager.mCurrentProfiles.append(0, mock(UserInfo.class));
@@ -784,8 +800,8 @@
     }
 
     @Test
+    @DisableFlags(FLAG_ALLOW_PRIVATE_PROFILE)
     public void testManagedProfileAvailabilityIntent() {
-        mSetFlagsRule.disableFlags(FLAG_ALLOW_PRIVATE_PROFILE);
         mLockscreenUserManager.mCurrentProfiles.clear();
         mLockscreenUserManager.mCurrentManagedProfiles.clear();
         assertEquals(0, mLockscreenUserManager.mCurrentProfiles.size());
@@ -798,8 +814,8 @@
     }
 
     @Test
+    @DisableFlags(FLAG_ALLOW_PRIVATE_PROFILE)
     public void testManagedProfileUnAvailabilityIntent() {
-        mSetFlagsRule.disableFlags(FLAG_ALLOW_PRIVATE_PROFILE);
         mLockscreenUserManager.mCurrentProfiles.clear();
         mLockscreenUserManager.mCurrentManagedProfiles.clear();
         assertEquals(0, mLockscreenUserManager.mCurrentProfiles.size());
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/StackCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/StackCoordinatorTest.kt
index fa5fad0..255cf6f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/StackCoordinatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/StackCoordinatorTest.kt
@@ -15,11 +15,12 @@
  */
 package com.android.systemui.statusbar.notification.collection.coordinator
 
+import android.platform.test.annotations.DisableFlags
+import android.platform.test.annotations.EnableFlags
 import android.testing.AndroidTestingRunner
 import android.testing.TestableLooper.RunWithLooper
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
-import com.android.systemui.flags.setFlagValue
 import com.android.systemui.statusbar.notification.collection.NotifPipeline
 import com.android.systemui.statusbar.notification.collection.NotificationEntry
 import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder
@@ -40,10 +41,9 @@
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.mockito.Mock
-import org.mockito.Mockito.reset
 import org.mockito.Mockito.verify
-import org.mockito.Mockito.`when` as whenever
 import org.mockito.MockitoAnnotations.initMocks
+import org.mockito.Mockito.`when` as whenever
 
 @SmallTest
 @RunWith(AndroidTestingRunner::class)
@@ -66,12 +66,6 @@
     fun setUp() {
         initMocks(this)
         entry = NotificationEntryBuilder().setSection(section).build()
-        setUpWithFlags()
-    }
-
-    private fun setUpWithFlags(vararg flags: Pair<String, Boolean>) {
-        flags.forEach { (name, value) -> mSetFlagsRule.setFlagValue(name, value) }
-        reset(pipeline)
         coordinator =
             StackCoordinator(
                 groupExpansionManagerImpl,
@@ -86,15 +80,15 @@
     }
 
     @Test
+    @DisableFlags(NotificationIconContainerRefactor.FLAG_NAME)
     fun testUpdateNotificationIcons() {
-        setUpWithFlags(NotificationIconContainerRefactor.FLAG_NAME to false)
         afterRenderListListener.onAfterRenderList(listOf(entry), stackController)
         verify(notificationIconAreaController).updateNotificationIcons(eq(listOf(entry)))
     }
 
     @Test
+    @EnableFlags(NotificationIconContainerRefactor.FLAG_NAME)
     fun testSetRenderedListOnInteractor() {
-        setUpWithFlags(NotificationIconContainerRefactor.FLAG_NAME to true)
         afterRenderListListener.onAfterRenderList(listOf(entry), stackController)
         verify(renderListInteractor).setRenderedList(eq(listOf(entry)))
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/footer/ui/view/FooterViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/footer/ui/view/FooterViewTest.java
index 22c5bae..57dac3a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/footer/ui/view/FooterViewTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/footer/ui/view/FooterViewTest.java
@@ -16,12 +16,11 @@
 
 package com.android.systemui.statusbar.notification.footer.ui.view;
 
+import static com.android.systemui.log.LogAssertKt.assertLogsWtf;
 import static com.google.common.truth.Truth.assertThat;
-
 import static junit.framework.Assert.assertFalse;
 import static junit.framework.Assert.assertNotNull;
 import static junit.framework.Assert.assertTrue;
-
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.clearInvocations;
@@ -31,33 +30,47 @@
 import static org.mockito.Mockito.verify;
 
 import android.content.Context;
-import android.testing.AndroidTestingRunner;
+import android.platform.test.annotations.DisableFlags;
+import android.platform.test.annotations.EnableFlags;
+import android.platform.test.flag.junit.FlagsParameterization;
 import android.view.LayoutInflater;
 import android.view.View;
 import android.widget.TextView;
 
 import androidx.test.filters.SmallTest;
 
-import com.android.systemui.Flags;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.res.R;
+import com.android.systemui.statusbar.notification.footer.shared.FooterViewRefactor;
 
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
+import java.util.List;
+
+import platform.test.runner.parameterized.ParameterizedAndroidJunit4;
+import platform.test.runner.parameterized.Parameters;
+
 @SmallTest
-@RunWith(AndroidTestingRunner.class)
+@RunWith(ParameterizedAndroidJunit4.class)
 public class FooterViewTest extends SysuiTestCase {
 
+    @Parameters(name = "{0}")
+    public static List<FlagsParameterization> getFlags() {
+        return FlagsParameterization.allCombinationsOf(FooterViewRefactor.FLAG_NAME);
+    }
+
+    public FooterViewTest(FlagsParameterization flags) {
+        mSetFlagsRule.setFlagsParameterization(flags);
+    }
+
     FooterView mView;
 
     Context mSpyContext = spy(mContext);
 
     @Before
     public void setUp() {
-        mSetFlagsRule.enableFlags(Flags.FLAG_NOTIFICATIONS_FOOTER_VIEW_REFACTOR);
-
         mView = (FooterView) LayoutInflater.from(mSpyContext).inflate(
                 R.layout.status_bar_notification_footer, null, false);
         mView.setAnimationDuration(0);
@@ -114,6 +127,7 @@
     }
 
     @Test
+    @EnableFlags(FooterViewRefactor.FLAG_NAME)
     public void testSetClearAllButtonText_resourceOnlyFetchedOnce() {
         int resId = R.string.clear_all_notifications_text;
         mView.setClearAllButtonText(resId);
@@ -132,6 +146,16 @@
     }
 
     @Test
+    @DisableFlags(FooterViewRefactor.FLAG_NAME)
+    public void testSetClearAllButtonText_expectsFlagEnabled() {
+        clearInvocations(mSpyContext);
+        int resId = R.string.clear_all_notifications_text;
+        assertLogsWtf(()-> mView.setClearAllButtonText(resId));
+        verify(mSpyContext, never()).getString(anyInt());
+    }
+
+    @Test
+    @EnableFlags(FooterViewRefactor.FLAG_NAME)
     public void testSetClearAllButtonDescription_resourceOnlyFetchedOnce() {
         int resId = R.string.accessibility_clear_all;
         mView.setClearAllButtonDescription(resId);
@@ -150,6 +174,16 @@
     }
 
     @Test
+    @DisableFlags(FooterViewRefactor.FLAG_NAME)
+    public void testSetClearAllButtonDescription_expectsFlagEnabled() {
+        clearInvocations(mSpyContext);
+        int resId = R.string.accessibility_clear_all;
+        assertLogsWtf(()-> mView.setClearAllButtonDescription(resId));
+        verify(mSpyContext, never()).getString(anyInt());
+    }
+
+    @Test
+    @EnableFlags(FooterViewRefactor.FLAG_NAME)
     public void testSetMessageString_resourceOnlyFetchedOnce() {
         int resId = R.string.unlock_to_see_notif_text;
         mView.setMessageString(resId);
@@ -168,6 +202,16 @@
     }
 
     @Test
+    @DisableFlags(FooterViewRefactor.FLAG_NAME)
+    public void testSetMessageString_expectsFlagEnabled() {
+        clearInvocations(mSpyContext);
+        int resId = R.string.unlock_to_see_notif_text;
+        assertLogsWtf(()-> mView.setMessageString(resId));
+        verify(mSpyContext, never()).getString(anyInt());
+    }
+
+    @Test
+    @EnableFlags(FooterViewRefactor.FLAG_NAME)
     public void testSetMessageIcon_resourceOnlyFetchedOnce() {
         int resId = R.drawable.ic_friction_lock_closed;
         mView.setMessageIcon(resId);
@@ -183,6 +227,15 @@
     }
 
     @Test
+    @DisableFlags(FooterViewRefactor.FLAG_NAME)
+    public void testSetMessageIcon_expectsFlagEnabled() {
+        clearInvocations(mSpyContext);
+        int resId = R.drawable.ic_friction_lock_closed;
+        assertLogsWtf(()-> mView.setMessageIcon(resId));
+        verify(mSpyContext, never()).getDrawable(anyInt());
+    }
+
+    @Test
     public void testSetFooterLabelVisible() {
         mView.setFooterLabelVisible(true);
         assertThat(mView.findViewById(R.id.manage_text).getVisibility()).isEqualTo(View.GONE);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/footer/ui/viewmodel/FooterViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/footer/ui/viewmodel/FooterViewModelTest.kt
index 0ba820f..8ab13f5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/footer/ui/viewmodel/FooterViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/footer/ui/viewmodel/FooterViewModelTest.kt
@@ -16,9 +16,9 @@
 
 package com.android.systemui.statusbar.notification.footer.ui.viewmodel
 
+import android.platform.test.annotations.EnableFlags
 import android.testing.AndroidTestingRunner
 import androidx.test.filters.SmallTest
-import com.android.systemui.Flags
 import com.android.systemui.SysUITestComponent
 import com.android.systemui.SysUITestModule
 import com.android.systemui.SysuiTestCase
@@ -38,6 +38,7 @@
 import com.android.systemui.shade.data.repository.FakeShadeRepository
 import com.android.systemui.statusbar.notification.collection.render.NotifStats
 import com.android.systemui.statusbar.notification.data.repository.ActiveNotificationListRepository
+import com.android.systemui.statusbar.notification.footer.shared.FooterViewRefactor
 import com.android.systemui.statusbar.notification.row.ui.viewmodel.ActivatableNotificationViewModelModule
 import com.android.systemui.statusbar.phone.DozeParameters
 import com.android.systemui.user.domain.interactor.HeadlessSystemUserModeModule
@@ -55,6 +56,7 @@
 
 @RunWith(AndroidTestingRunner::class)
 @SmallTest
+@EnableFlags(FooterViewRefactor.FLAG_NAME)
 class FooterViewModelTest : SysuiTestCase() {
     private lateinit var footerViewModel: FooterViewModel
 
@@ -106,8 +108,6 @@
     fun setUp() {
         MockitoAnnotations.initMocks(this)
 
-        mSetFlagsRule.enableFlags(Flags.FLAG_NOTIFICATIONS_FOOTER_VIEW_REFACTOR)
-
         // The underTest in the component is Optional, because that matches the provider we
         // currently have for the footer view model.
         footerViewModel = testComponent.underTest.get()
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
index 7361f6b..7ed3312 100644
--- 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
@@ -16,6 +16,7 @@
 
 package com.android.systemui.statusbar.notification.interruption
 
+import android.platform.test.annotations.DisableFlags
 import android.testing.AndroidTestingRunner
 import androidx.test.filters.SmallTest
 import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder
@@ -34,11 +35,8 @@
 
 @SmallTest
 @RunWith(AndroidTestingRunner::class)
+@DisableFlags(VisualInterruptionRefactor.FLAG_NAME)
 class NotificationInterruptStateProviderWrapperTest : VisualInterruptionDecisionProviderTestBase() {
-    init {
-        mSetFlagsRule.disableFlags(VisualInterruptionRefactor.FLAG_NAME)
-    }
-
     override val provider by lazy {
         NotificationInterruptStateProviderWrapper(
             NotificationInterruptStateProviderImpl(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderImplTest.kt
index d2c046c..da68d9c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderImplTest.kt
@@ -16,6 +16,7 @@
 
 package com.android.systemui.statusbar.notification.interruption
 
+import android.platform.test.annotations.EnableFlags
 import android.testing.AndroidTestingRunner
 import androidx.test.filters.SmallTest
 import com.android.systemui.statusbar.notification.collection.NotificationEntry
@@ -27,11 +28,8 @@
 
 @SmallTest
 @RunWith(AndroidTestingRunner::class)
+@EnableFlags(VisualInterruptionRefactor.FLAG_NAME)
 class VisualInterruptionDecisionProviderImplTest : VisualInterruptionDecisionProviderTestBase() {
-    init {
-        mSetFlagsRule.enableFlags(VisualInterruptionRefactor.FLAG_NAME)
-    }
-
     override val provider by lazy {
         VisualInterruptionDecisionProviderImpl(
             ambientDisplayConfiguration,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModelTest.kt
index f00abc9..21774aa 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModelTest.kt
@@ -19,10 +19,10 @@
 package com.android.systemui.statusbar.notification.stack.ui.viewmodel
 
 import android.app.NotificationManager.Policy
+import android.platform.test.annotations.EnableFlags
 import android.provider.Settings
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
-import com.android.systemui.Flags
 import com.android.systemui.SysUITestComponent
 import com.android.systemui.SysUITestModule
 import com.android.systemui.SysuiTestCase
@@ -41,6 +41,7 @@
 import com.android.systemui.shade.data.repository.FakeShadeRepository
 import com.android.systemui.statusbar.notification.data.repository.ActiveNotificationListRepository
 import com.android.systemui.statusbar.notification.data.repository.setActiveNotifs
+import com.android.systemui.statusbar.notification.footer.shared.FooterViewRefactor
 import com.android.systemui.statusbar.notification.footer.ui.viewmodel.FooterViewModelModule
 import com.android.systemui.statusbar.notification.row.ui.viewmodel.ActivatableNotificationViewModelModule
 import com.android.systemui.statusbar.policy.FakeConfigurationController
@@ -59,6 +60,7 @@
 
 @SmallTest
 @RunWith(AndroidJUnit4::class)
+@EnableFlags(FooterViewRefactor.FLAG_NAME)
 class NotificationListViewModelTest : SysuiTestCase() {
 
     @SysUISingleton
@@ -104,8 +106,6 @@
     @Before
     fun setUp() {
         MockitoAnnotations.initMocks(this)
-
-        mSetFlagsRule.enableFlags(Flags.FLAG_NOTIFICATIONS_FOOTER_VIEW_REFACTOR)
     }
 
     @Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt
index 2209e5e..f0205b3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt
@@ -26,7 +26,7 @@
 import com.android.systemui.common.ui.data.repository.fakeConfigurationRepository
 import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.flags.Flags
-import com.android.systemui.flags.featureFlagsClassic
+import com.android.systemui.flags.fakeFeatureFlagsClassic
 import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
 import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
 import com.android.systemui.keyguard.domain.interactor.keyguardInteractor
@@ -55,7 +55,7 @@
 
     val kosmos =
         testKosmos().apply {
-            featureFlagsClassic.apply { set(Flags.FULL_SCREEN_USER_SWITCHER, false) }
+            fakeFeatureFlagsClassic.apply { set(Flags.FULL_SCREEN_USER_SWITCHER, false) }
         }
     val testScope = kosmos.testScope
     val configurationRepository = kosmos.fakeConfigurationRepository
@@ -413,7 +413,7 @@
             val top = 123f
             val bottom = 456f
             keyguardRootViewModel.onNotificationContainerBoundsChanged(top, bottom)
-            assertThat(bounds).isEqualTo(NotificationContainerBounds(top, bottom))
+            assertThat(bounds).isEqualTo(NotificationContainerBounds(top = top, bottom = bottom))
         }
 
     @Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/BiometricsUnlockControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/BiometricsUnlockControllerTest.java
index 051a4c1..6f65eb4 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/BiometricsUnlockControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/BiometricsUnlockControllerTest.java
@@ -47,7 +47,6 @@
 import com.android.keyguard.logging.BiometricUnlockLogger;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.biometrics.AuthController;
-import com.android.systemui.deviceentry.domain.interactor.DeviceEntryHapticsInteractor;
 import com.android.systemui.dump.DumpManager;
 import com.android.systemui.keyguard.KeyguardViewMediator;
 import com.android.systemui.keyguard.WakefulnessLifecycle;
@@ -122,8 +121,6 @@
     @Mock
     private ViewRootImpl mViewRootImpl;
     @Mock
-    private DeviceEntryHapticsInteractor mDeviceEntryHapticsInteractor;
-    @Mock
     private SelectedUserInteractor mSelectedUserInteractor;
     @Mock
     private BiometricUnlockInteractor mBiometricUnlockInteractor;
@@ -160,7 +157,6 @@
                 mAuthController, mStatusBarStateController,
                 mSessionTracker, mLatencyTracker, mScreenOffAnimationController, mVibratorHelper,
                 mSystemClock,
-                mDeviceEntryHapticsInteractor,
                 () -> mSelectedUserInteractor,
                 mBiometricUnlockInteractor
         );
@@ -466,26 +462,6 @@
     }
 
     @Test
-    public void onFingerprintSuccess_requestSuccessHaptic() {
-        // WHEN biometric fingerprint succeeds
-        givenFingerprintModeUnlockCollapsing();
-        mBiometricUnlockController.startWakeAndUnlock(BiometricSourceType.FINGERPRINT,
-                true);
-
-        // THEN always vibrate the device
-        verify(mDeviceEntryHapticsInteractor).vibrateSuccess();
-    }
-
-    @Test
-    public void onFingerprintFail_requestErrorHaptic() {
-        // WHEN biometric fingerprint fails
-        mBiometricUnlockController.onBiometricAuthFailed(BiometricSourceType.FINGERPRINT);
-
-        // THEN always vibrate the device
-        verify(mDeviceEntryHapticsInteractor).vibrateError();
-    }
-
-    @Test
     public void onFingerprintDetect_showBouncer() {
         // WHEN fingerprint detect occurs
         mBiometricUnlockController.onBiometricDetected(UserHandle.USER_CURRENT,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhoneTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhoneTest.java
index 48b95d4..37ee322 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhoneTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhoneTest.java
@@ -46,6 +46,8 @@
 import com.android.systemui.statusbar.policy.HeadsUpManager;
 import com.android.systemui.statusbar.policy.HeadsUpManagerLogger;
 import com.android.systemui.util.kotlin.JavaAdapter;
+import com.android.systemui.util.settings.GlobalSettings;
+import com.android.systemui.util.time.SystemClock;
 
 import org.junit.After;
 import org.junit.Before;
@@ -87,6 +89,8 @@
                 KeyguardBypassController keyguardBypassController,
                 ConfigurationController configurationController,
                 Handler handler,
+                GlobalSettings globalSettings,
+                SystemClock systemClock,
                 AccessibilityManagerWrapper accessibilityManagerWrapper,
                 UiEventLogger uiEventLogger,
                 JavaAdapter javaAdapter,
@@ -101,13 +105,15 @@
                     visualStabilityProvider,
                     configurationController,
                     handler,
+                    globalSettings,
+                    systemClock,
                     accessibilityManagerWrapper,
                     uiEventLogger,
                     javaAdapter,
                     shadeInteractor
             );
             mMinimumDisplayTime = TEST_MINIMUM_DISPLAY_TIME;
-            mAutoDismissNotificationDecay = TEST_AUTO_DISMISS_TIME;
+            mAutoDismissTime = TEST_AUTO_DISMISS_TIME;
         }
     }
 
@@ -121,6 +127,8 @@
                 mBypassController,
                 mConfigurationController,
                 mTestHandler,
+                mGlobalSettings,
+                mSystemClock,
                 mAccessibilityManagerWrapper,
                 mUiEventLogger,
                 mJavaAdapter,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LegacyNotificationIconAreaControllerImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LegacyNotificationIconAreaControllerImplTest.java
index c24d9ad..b3708ba 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LegacyNotificationIconAreaControllerImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LegacyNotificationIconAreaControllerImplTest.java
@@ -20,6 +20,7 @@
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
+import android.platform.test.annotations.DisableFlags;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
 
@@ -49,6 +50,7 @@
 @SmallTest
 @RunWith(AndroidTestingRunner.class)
 @TestableLooper.RunWithLooper
+@DisableFlags(NotificationIconContainerRefactor.FLAG_NAME)
 public class LegacyNotificationIconAreaControllerImplTest extends SysuiTestCase {
 
     @Mock
@@ -83,7 +85,6 @@
     @Before
     public void setup() {
         MockitoAnnotations.initMocks(this);
-        mSetFlagsRule.disableFlags(NotificationIconContainerRefactor.FLAG_NAME);
         mController = new LegacyNotificationIconAreaControllerImpl(
                 mContext,
                 mStatusBarStateController,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenterTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenterTest.java
index bbdc9ce..1dafcc4 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenterTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenterTest.java
@@ -15,12 +15,13 @@
 package com.android.systemui.statusbar.phone;
 
 import static android.view.Display.DEFAULT_DISPLAY;
-
 import static com.android.systemui.statusbar.notification.interruption.VisualInterruptionType.BUBBLE;
 import static com.android.systemui.statusbar.notification.interruption.VisualInterruptionType.PEEK;
 import static com.android.systemui.statusbar.notification.interruption.VisualInterruptionType.PULSE;
-
 import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertThrows;
 import static org.junit.Assert.assertTrue;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.times;
@@ -30,14 +31,14 @@
 import android.app.Notification;
 import android.app.PendingIntent;
 import android.app.StatusBarManager;
-import android.platform.test.flag.junit.SetFlagsRule;
+import android.platform.test.annotations.DisableFlags;
+import android.platform.test.annotations.EnableFlags;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
 import android.testing.TestableLooper.RunWithLooper;
 
 import androidx.test.filters.SmallTest;
 
-import com.android.systemui.Flags;
 import com.android.systemui.InitController;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.plugins.ActivityStarter;
@@ -64,6 +65,7 @@
 import com.android.systemui.statusbar.notification.interruption.VisualInterruptionCondition;
 import com.android.systemui.statusbar.notification.interruption.VisualInterruptionDecisionProvider;
 import com.android.systemui.statusbar.notification.interruption.VisualInterruptionFilter;
+import com.android.systemui.statusbar.notification.interruption.VisualInterruptionRefactor;
 import com.android.systemui.statusbar.notification.interruption.VisualInterruptionType;
 import com.android.systemui.statusbar.notification.row.NotificationGutsManager;
 import com.android.systemui.statusbar.notification.stack.NotificationListContainer;
@@ -73,7 +75,6 @@
 import com.android.systemui.statusbar.policy.KeyguardStateController;
 
 import org.junit.Before;
-import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.ArgumentCaptor;
@@ -100,10 +101,6 @@
     private final KeyguardStateController mKeyguardStateController =
             mock(KeyguardStateController.class);
 
-    @Rule
-    public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(
-            SetFlagsRule.DefaultInitValueType.DEVICE_DEFAULT);
-
     @Before
     public void setup() {
         mCommandQueue = new CommandQueue(mContext, new FakeDisplayTracker(mContext));
@@ -114,29 +111,46 @@
         mDependency.injectMockDependency(NotificationShadeWindowController.class);
 
         when(mNotificationAlertsInteractor.areNotificationAlertsEnabled()).thenReturn(true);
+
+        createPresenter();
+        if (VisualInterruptionRefactor.isEnabled()) {
+            verifyAndCaptureSuppressors();
+        } else {
+            verifyAndCaptureLegacySuppressor();
+        }
     }
 
     @Test
+    @DisableFlags(VisualInterruptionRefactor.FLAG_NAME)
     public void testInit_refactorDisabled() {
-        ensureRefactorDisabledState();
+        assertFalse(VisualInterruptionRefactor.isEnabled());
+        assertNull(mAlertsDisabledCondition);
+        assertNull(mVrModeCondition);
+        assertNull(mNeedsRedactionFilter);
+        assertNull(mPanelsDisabledCondition);
+        assertNotNull(mInterruptSuppressor);
     }
 
     @Test
+    @EnableFlags(VisualInterruptionRefactor.FLAG_NAME)
     public void testInit_refactorEnabled() {
-        ensureRefactorEnabledState();
+        assertTrue(VisualInterruptionRefactor.isEnabled());
+        assertNotNull(mAlertsDisabledCondition);
+        assertNotNull(mVrModeCondition);
+        assertNotNull(mNeedsRedactionFilter);
+        assertNotNull(mPanelsDisabledCondition);
+        assertNull(mInterruptSuppressor);
     }
 
     @Test
+    @DisableFlags(VisualInterruptionRefactor.FLAG_NAME)
     public void testNoSuppressHeadsUp_default_refactorDisabled() {
-        ensureRefactorDisabledState();
-
         assertFalse(mInterruptSuppressor.suppressAwakeHeadsUp(createNotificationEntry()));
     }
 
     @Test
+    @EnableFlags(VisualInterruptionRefactor.FLAG_NAME)
     public void testNoSuppressHeadsUp_default_refactorEnabled() {
-        ensureRefactorEnabledState();
-
         assertFalse(mAlertsDisabledCondition.shouldSuppress());
         assertFalse(mVrModeCondition.shouldSuppress());
         assertFalse(mNeedsRedactionFilter.shouldSuppress(createNotificationEntry()));
@@ -144,9 +158,8 @@
     }
 
     @Test
+    @DisableFlags(VisualInterruptionRefactor.FLAG_NAME)
     public void testSuppressHeadsUp_disabledStatusBar_refactorDisabled() {
-        ensureRefactorDisabledState();
-
         mCommandQueue.disable(DEFAULT_DISPLAY, StatusBarManager.DISABLE_EXPAND, 0,
                 false /* animate */);
         TestableLooper.get(this).processAllMessages();
@@ -156,9 +169,8 @@
     }
 
     @Test
+    @EnableFlags(VisualInterruptionRefactor.FLAG_NAME)
     public void testSuppressHeadsUp_disabledStatusBar_refactorEnabled() {
-        ensureRefactorEnabledState();
-
         mCommandQueue.disable(DEFAULT_DISPLAY, StatusBarManager.DISABLE_EXPAND, 0,
                 false /* animate */);
         TestableLooper.get(this).processAllMessages();
@@ -168,9 +180,8 @@
     }
 
     @Test
+    @DisableFlags(VisualInterruptionRefactor.FLAG_NAME)
     public void testSuppressHeadsUp_disabledNotificationShade_refactorDisabled() {
-        ensureRefactorDisabledState();
-
         mCommandQueue.disable(DEFAULT_DISPLAY, 0, StatusBarManager.DISABLE2_NOTIFICATION_SHADE,
                 false /* animate */);
         TestableLooper.get(this).processAllMessages();
@@ -180,9 +191,8 @@
     }
 
     @Test
+    @EnableFlags(VisualInterruptionRefactor.FLAG_NAME)
     public void testSuppressHeadsUp_disabledNotificationShade_refactorEnabled() {
-        ensureRefactorEnabledState();
-
         mCommandQueue.disable(DEFAULT_DISPLAY, 0, StatusBarManager.DISABLE2_NOTIFICATION_SHADE,
                 false /* animate */);
         TestableLooper.get(this).processAllMessages();
@@ -192,9 +202,8 @@
     }
 
     @Test
+    @EnableFlags(VisualInterruptionRefactor.FLAG_NAME)
     public void testPanelsDisabledConditionSuppressesPeek() {
-        ensureRefactorEnabledState();
-
         final Set<VisualInterruptionType> types = mPanelsDisabledCondition.getTypes();
         assertTrue(types.contains(PEEK));
         assertFalse(types.contains(PULSE));
@@ -202,9 +211,8 @@
     }
 
     @Test
+    @DisableFlags(VisualInterruptionRefactor.FLAG_NAME)
     public void testNoSuppressHeadsUp_FSI_nonOccludedKeyguard_refactorDisabled() {
-        ensureRefactorDisabledState();
-
         when(mKeyguardStateController.isShowing()).thenReturn(true);
         when(mKeyguardStateController.isOccluded()).thenReturn(false);
 
@@ -212,9 +220,8 @@
     }
 
     @Test
+    @EnableFlags(VisualInterruptionRefactor.FLAG_NAME)
     public void testNoSuppressHeadsUp_FSI_nonOccludedKeyguard_refactorEnabled() {
-        ensureRefactorEnabledState();
-
         when(mKeyguardStateController.isShowing()).thenReturn(true);
         when(mKeyguardStateController.isOccluded()).thenReturn(false);
 
@@ -227,9 +234,8 @@
     }
 
     @Test
+    @DisableFlags(VisualInterruptionRefactor.FLAG_NAME)
     public void testSuppressInterruptions_vrMode_refactorDisabled() {
-        ensureRefactorDisabledState();
-
         mStatusBarNotificationPresenter.mVrMode = true;
 
         assertTrue("Vr mode should suppress interruptions",
@@ -237,9 +243,8 @@
     }
 
     @Test
+    @EnableFlags(VisualInterruptionRefactor.FLAG_NAME)
     public void testSuppressInterruptions_vrMode_refactorEnabled() {
-        ensureRefactorEnabledState();
-
         mStatusBarNotificationPresenter.mVrMode = true;
 
         assertTrue("Vr mode should suppress interruptions", mVrModeCondition.shouldSuppress());
@@ -251,9 +256,8 @@
     }
 
     @Test
+    @DisableFlags(VisualInterruptionRefactor.FLAG_NAME)
     public void testSuppressInterruptions_statusBarAlertsDisabled_refactorDisabled() {
-        ensureRefactorDisabledState();
-
         when(mNotificationAlertsInteractor.areNotificationAlertsEnabled()).thenReturn(false);
 
         assertTrue("When alerts aren't enabled, interruptions are suppressed",
@@ -261,9 +265,8 @@
     }
 
     @Test
+    @EnableFlags(VisualInterruptionRefactor.FLAG_NAME)
     public void testSuppressInterruptions_statusBarAlertsDisabled_refactorEnabled() {
-        ensureRefactorEnabledState();
-
         when(mNotificationAlertsInteractor.areNotificationAlertsEnabled()).thenReturn(false);
 
         assertTrue("When alerts aren't enabled, interruptions are suppressed",
@@ -349,18 +352,6 @@
         mInterruptSuppressor = suppressorCaptor.getValue();
     }
 
-    private void ensureRefactorDisabledState() {
-        mSetFlagsRule.disableFlags(Flags.FLAG_VISUAL_INTERRUPTIONS_REFACTOR);
-        createPresenter();
-        verifyAndCaptureLegacySuppressor();
-    }
-
-    private void ensureRefactorEnabledState() {
-        mSetFlagsRule.enableFlags(Flags.FLAG_VISUAL_INTERRUPTIONS_REFACTOR);
-        createPresenter();
-        verifyAndCaptureSuppressors();
-    }
-
     private NotificationEntry createNotificationEntry() {
         return new NotificationEntryBuilder()
                 .setPkg("a")
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java
index 17c2938..1cc611c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java
@@ -69,8 +69,8 @@
 import com.android.systemui.statusbar.phone.ongoingcall.OngoingCallController;
 import com.android.systemui.statusbar.pipeline.shared.ui.viewmodel.FakeCollapsedStatusBarViewBinder;
 import com.android.systemui.statusbar.pipeline.shared.ui.viewmodel.FakeCollapsedStatusBarViewModel;
-import com.android.systemui.statusbar.policy.ConfigurationController;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
+import com.android.systemui.statusbar.ui.SystemBarUtilsState;
 import com.android.systemui.statusbar.window.StatusBarWindowStateController;
 import com.android.systemui.statusbar.window.StatusBarWindowStateListener;
 import com.android.systemui.util.CarrierConfigTracker;
@@ -717,7 +717,7 @@
                 mKeyguardUpdateMonitor,
                 mock(NotificationIconContainerStatusBarViewModel.class),
                 mock(ConfigurationState.class),
-                mock(ConfigurationController.class),
+                mock(SystemBarUtilsState.class),
                 mock(StatusBarNotificationIconViewStore.class),
                 mock(DemoModeController.class));
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/CollapsedStatusBarViewModelImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/CollapsedStatusBarViewModelImplTest.kt
index 09dc1e5..89842d6 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/CollapsedStatusBarViewModelImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/CollapsedStatusBarViewModelImplTest.kt
@@ -16,9 +16,10 @@
 
 package com.android.systemui.statusbar.pipeline.shared.ui.viewmodel
 
+import android.platform.test.annotations.DisableFlags
+import android.platform.test.annotations.EnableFlags
 import androidx.test.filters.SmallTest
 import com.android.systemui.CoroutineTestScopeModule
-import com.android.systemui.Flags
 import com.android.systemui.SysUITestComponent
 import com.android.systemui.SysUITestModule
 import com.android.systemui.SysuiTestCase
@@ -29,6 +30,7 @@
 import com.android.systemui.keyguard.shared.model.KeyguardState
 import com.android.systemui.keyguard.shared.model.TransitionState
 import com.android.systemui.keyguard.shared.model.TransitionStep
+import com.android.systemui.log.assertLogsWtf
 import com.android.systemui.runTest
 import com.android.systemui.statusbar.data.model.StatusBarMode
 import com.android.systemui.statusbar.data.repository.FakeStatusBarModeRepository
@@ -37,14 +39,15 @@
 import com.android.systemui.statusbar.notification.data.repository.ActiveNotificationListRepository
 import com.android.systemui.statusbar.notification.data.repository.ActiveNotificationsStore
 import com.android.systemui.statusbar.notification.shared.ActiveNotificationModel
+import com.android.systemui.statusbar.notification.shared.NotificationsLiveDataStoreRefactor
 import com.google.common.truth.Truth.assertThat
 import dagger.BindsInstance
 import dagger.Component
 import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.emptyFlow
 import kotlinx.coroutines.flow.launchIn
 import kotlinx.coroutines.test.TestScope
 import kotlinx.coroutines.test.UnconfinedTestDispatcher
-import org.junit.Before
 import org.junit.Test
 
 @SmallTest
@@ -79,11 +82,6 @@
                 testScope = CoroutineTestScopeModule(TestScope(UnconfinedTestDispatcher())),
             )
 
-    @Before
-    fun setUp() {
-        mSetFlagsRule.enableFlags(Flags.FLAG_NOTIFICATIONS_LIVE_DATA_STORE_REFACTOR)
-    }
-
     @Test
     fun isTransitioningFromLockscreenToOccluded_started_isTrue() =
         testComponent.runTest {
@@ -347,6 +345,7 @@
         }
 
     @Test
+    @EnableFlags(NotificationsLiveDataStoreRefactor.FLAG_NAME)
     fun areNotificationsLightsOut_lowProfileWithNotifications_true() =
         testComponent.runTest {
             statusBarModeRepository.defaultDisplay.statusBarMode.value =
@@ -360,6 +359,7 @@
         }
 
     @Test
+    @EnableFlags(NotificationsLiveDataStoreRefactor.FLAG_NAME)
     fun areNotificationsLightsOut_lowProfileWithoutNotifications_false() =
         testComponent.runTest {
             statusBarModeRepository.defaultDisplay.statusBarMode.value =
@@ -373,6 +373,7 @@
         }
 
     @Test
+    @EnableFlags(NotificationsLiveDataStoreRefactor.FLAG_NAME)
     fun areNotificationsLightsOut_defaultStatusBarModeWithoutNotifications_false() =
         testComponent.runTest {
             statusBarModeRepository.defaultDisplay.statusBarMode.value = StatusBarMode.TRANSPARENT
@@ -385,6 +386,7 @@
         }
 
     @Test
+    @EnableFlags(NotificationsLiveDataStoreRefactor.FLAG_NAME)
     fun areNotificationsLightsOut_defaultStatusBarModeWithNotifications_false() =
         testComponent.runTest {
             statusBarModeRepository.defaultDisplay.statusBarMode.value = StatusBarMode.TRANSPARENT
@@ -396,6 +398,16 @@
             assertThat(actual).isFalse()
         }
 
+    @Test
+    @DisableFlags(NotificationsLiveDataStoreRefactor.FLAG_NAME)
+    fun areNotificationsLightsOut_requiresFlagEnabled() =
+        testComponent.runTest {
+            assertLogsWtf {
+                val flow = underTest.areNotificationsLightsOut(DISPLAY_ID)
+                assertThat(flow).isEqualTo(emptyFlow<Boolean>())
+            }
+        }
+
     private fun activeNotificationsStore(notifications: List<ActiveNotificationModel>) =
         ActiveNotificationsStore.Builder()
             .apply { notifications.forEach(::addIndividualNotif) }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/InternetTileViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/InternetTileViewModelTest.kt
index 22dce3a..1bdf644 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/InternetTileViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/InternetTileViewModelTest.kt
@@ -21,6 +21,7 @@
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.common.shared.model.ContentDescription.Companion.loadContentDescription
 import com.android.systemui.common.shared.model.Text
+import com.android.systemui.common.shared.model.Text.Companion.loadText
 import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.flags.FakeFeatureFlagsClassic
 import com.android.systemui.flags.Flags
@@ -355,14 +356,14 @@
 
             connectivityRepository.setEthernetConnected(default = true, validated = true)
 
-            assertThat(latest?.secondaryLabel).isNull()
-            assertThat(latest?.secondaryTitle)
-                .isEqualTo(ethernetIcon!!.contentDescription.toString())
+            assertThat(latest?.secondaryLabel.loadText(context))
+                .isEqualTo(ethernetIcon!!.contentDescription.loadContentDescription(context))
+            assertThat(latest?.secondaryTitle).isNull()
             assertThat(latest?.iconId).isEqualTo(R.drawable.stat_sys_ethernet_fully)
             assertThat(latest?.icon).isNull()
             assertThat(latest?.stateDescription).isNull()
             assertThat(latest?.contentDescription.loadContentDescription(context))
-                .isEqualTo(latest?.secondaryTitle)
+                .isEqualTo(latest?.secondaryLabel.loadText(context))
         }
 
     @Test
@@ -373,14 +374,14 @@
 
             connectivityRepository.setEthernetConnected(default = true, validated = false)
 
-            assertThat(latest?.secondaryLabel).isNull()
-            assertThat(latest?.secondaryTitle)
-                .isEqualTo(ethernetIcon!!.contentDescription.toString())
+            assertThat(latest?.secondaryLabel.loadText(context))
+                .isEqualTo(ethernetIcon!!.contentDescription.loadContentDescription(context))
+            assertThat(latest?.secondaryTitle).isNull()
             assertThat(latest?.iconId).isEqualTo(R.drawable.stat_sys_ethernet)
             assertThat(latest?.icon).isNull()
             assertThat(latest?.stateDescription).isNull()
             assertThat(latest?.contentDescription.loadContentDescription(context))
-                .isEqualTo(latest?.secondaryTitle)
+                .isEqualTo(latest?.secondaryLabel.loadText(context))
         }
 
     private fun setWifiNetworkWithHotspot(hotspot: WifiNetworkModel.HotspotDeviceType) {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/HeadsUpManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BaseHeadsUpManagerTest.java
similarity index 96%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/HeadsUpManagerTest.java
rename to packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BaseHeadsUpManagerTest.java
index 4f3f564..2940c39 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/HeadsUpManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BaseHeadsUpManagerTest.java
@@ -34,7 +34,6 @@
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
-import static org.mockito.MockitoAnnotations.initMocks;
 
 import android.app.Notification;
 import android.app.PendingIntent;
@@ -57,17 +56,25 @@
 import com.android.systemui.statusbar.AlertingNotificationManagerTest;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder;
+import com.android.systemui.util.settings.GlobalSettings;
+import com.android.systemui.util.time.SystemClock;
 
 import org.junit.After;
 import org.junit.Before;
+import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
 
 @SmallTest
 @RunWith(AndroidTestingRunner.class)
 @TestableLooper.RunWithLooper
-public class HeadsUpManagerTest extends AlertingNotificationManagerTest {
+public class BaseHeadsUpManagerTest extends AlertingNotificationManagerTest {
+    @Rule
+    public MockitoRule rule = MockitoJUnit.rule();
+
     private static final int TEST_TOUCH_ACCEPTANCE_TIME = 200;
     private static final int TEST_A11Y_AUTO_DISMISS_TIME = 1_000;
     private static final int TEST_A11Y_TIMEOUT_TIME = 3_000;
@@ -76,17 +83,33 @@
     private final HeadsUpManagerLogger mLogger = spy(new HeadsUpManagerLogger(logcatLogBuffer()));
     @Mock private AccessibilityManagerWrapper mAccessibilityMgr;
 
+    static {
+        assertThat(TEST_MINIMUM_DISPLAY_TIME).isLessThan(TEST_AUTO_DISMISS_TIME);
+        assertThat(TEST_AUTO_DISMISS_TIME).isLessThan(TEST_STICKY_AUTO_DISMISS_TIME);
+        assertThat(TEST_STICKY_AUTO_DISMISS_TIME).isLessThan(TEST_A11Y_AUTO_DISMISS_TIME);
+
+        assertThat(TEST_TOUCH_ACCEPTANCE_TIME + TEST_AUTO_DISMISS_TIME).isLessThan(
+                TEST_TIMEOUT_TIME);
+        assertThat(TEST_TOUCH_ACCEPTANCE_TIME + TEST_STICKY_AUTO_DISMISS_TIME).isLessThan(
+                TEST_TIMEOUT_TIME);
+        assertThat(TEST_TOUCH_ACCEPTANCE_TIME + TEST_A11Y_AUTO_DISMISS_TIME).isLessThan(
+                TEST_A11Y_TIMEOUT_TIME);
+    }
+
     private final class TestableHeadsUpManager extends BaseHeadsUpManager {
         TestableHeadsUpManager(Context context,
                 HeadsUpManagerLogger logger,
                 Handler handler,
+                GlobalSettings globalSettings,
+                SystemClock systemClock,
                 AccessibilityManagerWrapper accessibilityManagerWrapper,
                 UiEventLogger uiEventLogger) {
-            super(context, logger, handler, accessibilityManagerWrapper, uiEventLogger);
+            super(context, logger, handler, globalSettings, systemClock,
+                    accessibilityManagerWrapper, uiEventLogger);
             mTouchAcceptanceDelay = TEST_TOUCH_ACCEPTANCE_TIME;
             mMinimumDisplayTime = TEST_MINIMUM_DISPLAY_TIME;
-            mAutoDismissNotificationDecay = TEST_AUTO_DISMISS_TIME;
-            mStickyDisplayTime = TEST_STICKY_AUTO_DISMISS_TIME;
+            mAutoDismissTime = TEST_AUTO_DISMISS_TIME;
+            mStickyForSomeTimeAutoDismissTime = TEST_STICKY_AUTO_DISMISS_TIME;
         }
 
         // The following are only implemented by HeadsUpManagerPhone. If you need them, use that.
@@ -160,8 +183,8 @@
     }
 
     private BaseHeadsUpManager createHeadsUpManager() {
-        return new TestableHeadsUpManager(mContext, mLogger, mTestHandler, mAccessibilityMgr,
-                mUiEventLoggerFake);
+        return new TestableHeadsUpManager(mContext, mLogger, mTestHandler, mGlobalSettings,
+                mSystemClock, mAccessibilityMgr, mUiEventLoggerFake);
     }
 
     @Override
@@ -214,19 +237,7 @@
     @Before
     @Override
     public void setUp() {
-        initMocks(this);
         super.setUp();
-
-        assertThat(TEST_MINIMUM_DISPLAY_TIME).isLessThan(TEST_AUTO_DISMISS_TIME);
-        assertThat(TEST_AUTO_DISMISS_TIME).isLessThan(TEST_STICKY_AUTO_DISMISS_TIME);
-        assertThat(TEST_STICKY_AUTO_DISMISS_TIME).isLessThan(TEST_A11Y_AUTO_DISMISS_TIME);
-
-        assertThat(TEST_TOUCH_ACCEPTANCE_TIME + TEST_AUTO_DISMISS_TIME).isLessThan(
-                TEST_TIMEOUT_TIME);
-        assertThat(TEST_TOUCH_ACCEPTANCE_TIME + TEST_STICKY_AUTO_DISMISS_TIME).isLessThan(
-                TEST_TIMEOUT_TIME);
-        assertThat(TEST_TOUCH_ACCEPTANCE_TIME + TEST_A11Y_AUTO_DISMISS_TIME).isLessThan(
-                TEST_A11Y_TIMEOUT_TIME);
     }
 
     @After
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/KeyguardStateControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/KeyguardStateControllerTest.java
index 01dad38..479309c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/KeyguardStateControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/KeyguardStateControllerTest.java
@@ -153,6 +153,36 @@
     }
 
     @Test
+    public void testCanSkipLockScreen_updateCalledOnFacesCleared() {
+        verify(mKeyguardUpdateMonitor).registerCallback(mUpdateCallbackCaptor.capture());
+
+        // Cannot skip after there's a password/pin/pattern
+        when(mLockPatternUtils.isSecure(anyInt())).thenReturn(true);
+        ((KeyguardStateControllerImpl) mKeyguardStateController).update(false /* alwaysUpdate */);
+        assertThat(mKeyguardStateController.canDismissLockScreen()).isFalse();
+
+        // Unless user is authenticated
+        when(mKeyguardUpdateMonitor.getUserCanSkipBouncer(anyInt())).thenReturn(true);
+        mUpdateCallbackCaptor.getValue().onFacesCleared();
+        assertThat(mKeyguardStateController.canDismissLockScreen()).isTrue();
+    }
+
+    @Test
+    public void testCanSkipLockScreen_updateCalledOnFingerprintssCleared() {
+        verify(mKeyguardUpdateMonitor).registerCallback(mUpdateCallbackCaptor.capture());
+
+        // Cannot skip after there's a password/pin/pattern
+        when(mLockPatternUtils.isSecure(anyInt())).thenReturn(true);
+        ((KeyguardStateControllerImpl) mKeyguardStateController).update(false /* alwaysUpdate */);
+        assertThat(mKeyguardStateController.canDismissLockScreen()).isFalse();
+
+        // Unless user is authenticated
+        when(mKeyguardUpdateMonitor.getUserCanSkipBouncer(anyInt())).thenReturn(true);
+        mUpdateCallbackCaptor.getValue().onFingerprintsCleared();
+        assertThat(mKeyguardStateController.canDismissLockScreen()).isTrue();
+    }
+
+    @Test
     public void testIsUnlocked() {
         // Is unlocked whenever the keyguard is not showing
         assertThat(mKeyguardStateController.isShowing()).isFalse();
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 8585d46..5b9b390 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
@@ -119,6 +119,7 @@
 import com.android.systemui.scene.data.repository.SceneContainerRepository;
 import com.android.systemui.scene.domain.interactor.SceneInteractor;
 import com.android.systemui.scene.shared.flag.FakeSceneContainerFlags;
+import com.android.systemui.scene.shared.flag.SceneContainerFlags;
 import com.android.systemui.scene.shared.logger.SceneLogger;
 import com.android.systemui.settings.FakeDisplayTracker;
 import com.android.systemui.settings.UserTracker;
@@ -343,6 +344,8 @@
     private Icon mAppBubbleIcon;
     @Mock
     private Display mDefaultDisplay;
+    @Mock
+    private SceneContainerFlags mSceneContainerFlags;
 
     private final SceneTestUtils mUtils = new SceneTestUtils(this);
     private final TestScope mTestScope = mUtils.getTestScope();
@@ -503,7 +506,8 @@
                 () -> mShadeInteractor,
                 mShadeWindowLogger,
                 () -> mSelectedUserInteractor,
-                mUserTracker
+                mUserTracker,
+                mSceneContainerFlags
         );
         mNotificationShadeWindowController.fetchWindowRootView();
         mNotificationShadeWindowController.attach();
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/data/repository/DeviceEntryHapticsRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/android/content/pm/LauncherAppsKosmos.kt
similarity index 69%
copy from packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/data/repository/DeviceEntryHapticsRepositoryKosmos.kt
copy to packages/SystemUI/tests/utils/src/android/content/pm/LauncherAppsKosmos.kt
index 8bb07d9..94fc1fc 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/data/repository/DeviceEntryHapticsRepositoryKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/android/content/pm/LauncherAppsKosmos.kt
@@ -14,10 +14,9 @@
  * limitations under the License.
  */
 
-package com.android.systemui.deviceentry.data.repository
+package android.content.pm
 
 import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.util.mockito.mock
 
-var Kosmos.deviceEntryHapticsRepository: DeviceEntryHapticsRepository by
-    Kosmos.Fixture { fakeDeviceEntryHapticsRepository }
-val Kosmos.fakeDeviceEntryHapticsRepository by Kosmos.Fixture { FakeDeviceEntryHapticsRepository() }
+val Kosmos.launcherApps by Kosmos.Fixture { mock<LauncherApps>() }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/authentication/data/repository/FakeAuthenticationRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/authentication/data/repository/FakeAuthenticationRepository.kt
index 4fdea97..7c5696c 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/authentication/data/repository/FakeAuthenticationRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/authentication/data/repository/FakeAuthenticationRepository.kt
@@ -20,10 +20,10 @@
 import com.android.internal.widget.LockPatternView
 import com.android.internal.widget.LockscreenCredential
 import com.android.keyguard.KeyguardSecurityModel.SecurityMode
+import com.android.systemui.authentication.shared.model.AuthenticationLockoutModel
 import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
 import com.android.systemui.authentication.shared.model.AuthenticationPatternCoordinate
 import com.android.systemui.authentication.shared.model.AuthenticationResultModel
-import com.android.systemui.authentication.shared.model.AuthenticationThrottlingModel
 import com.android.systemui.dagger.SysUISingleton
 import dagger.Binds
 import dagger.Module
@@ -40,9 +40,6 @@
     private val currentTime: () -> Long,
 ) : AuthenticationRepository {
 
-    private val _isAutoConfirmFeatureEnabled = MutableStateFlow(false)
-    override val isAutoConfirmFeatureEnabled: StateFlow<Boolean> =
-        _isAutoConfirmFeatureEnabled.asStateFlow()
     override val authenticationChallengeResult = MutableSharedFlow<Boolean>()
 
     override val hintedPinLength: Int = HINTING_PIN_LENGTH
@@ -50,8 +47,13 @@
     private val _isPatternVisible = MutableStateFlow(true)
     override val isPatternVisible: StateFlow<Boolean> = _isPatternVisible.asStateFlow()
 
-    override val throttling: MutableStateFlow<AuthenticationThrottlingModel?> =
-        MutableStateFlow(null)
+    override val lockout: MutableStateFlow<AuthenticationLockoutModel?> = MutableStateFlow(null)
+
+    override val hasLockoutOccurred = MutableStateFlow(false)
+
+    private val _isAutoConfirmFeatureEnabled = MutableStateFlow(false)
+    override val isAutoConfirmFeatureEnabled: StateFlow<Boolean> =
+        _isAutoConfirmFeatureEnabled.asStateFlow()
 
     private val _authenticationMethod =
         MutableStateFlow<AuthenticationMethodModel>(DEFAULT_AUTHENTICATION_METHOD)
@@ -67,10 +69,12 @@
         _isPinEnhancedPrivacyEnabled.asStateFlow()
 
     private var failedAttemptCount = 0
-    private var throttlingEndTimestamp = 0L
+    private var lockoutEndTimestamp = 0L
     private var credentialOverride: List<Any>? = null
     private var securityMode: SecurityMode = DEFAULT_AUTHENTICATION_METHOD.toSecurityMode()
 
+    var lockoutStartedReportCount = 0
+
     override suspend fun getAuthenticationMethod(): AuthenticationMethodModel {
         return authenticationMethod.value
     }
@@ -89,6 +93,10 @@
         authenticationChallengeResult.emit(isSuccessful)
     }
 
+    override suspend fun reportLockoutStarted(durationMs: Int) {
+        lockoutStartedReportCount++
+    }
+
     override suspend fun getPinLength(): Int {
         return (credentialOverride ?: DEFAULT_PIN).size
     }
@@ -97,16 +105,19 @@
         return failedAttemptCount
     }
 
-    override suspend fun getThrottlingEndTimestamp(): Long {
-        return throttlingEndTimestamp
+    override suspend fun getLockoutEndTimestamp(): Long {
+        return lockoutEndTimestamp
     }
 
     fun setAutoConfirmFeatureEnabled(isEnabled: Boolean) {
         _isAutoConfirmFeatureEnabled.value = isEnabled
     }
 
-    override suspend fun setThrottleDuration(durationMs: Int) {
-        throttlingEndTimestamp = if (durationMs > 0) currentTime() + durationMs else 0
+    override suspend fun setLockoutDuration(durationMs: Int) {
+        lockoutEndTimestamp = if (durationMs > 0) currentTime() + durationMs else 0
+        if (durationMs > 0) {
+            hasLockoutOccurred.value = true
+        }
     }
 
     override suspend fun checkCredential(
@@ -125,17 +136,16 @@
                 else -> error("Unexpected credential type ${credential.type}!")
             }
 
-        return if (
-            isSuccessful || failedAttemptCount < MAX_FAILED_AUTH_TRIES_BEFORE_THROTTLING - 1
-        ) {
+        return if (isSuccessful || failedAttemptCount < MAX_FAILED_AUTH_TRIES_BEFORE_LOCKOUT - 1) {
+            hasLockoutOccurred.value = false
             AuthenticationResultModel(
                 isSuccessful = isSuccessful,
-                throttleDurationMs = 0,
+                lockoutDurationMs = 0,
             )
         } else {
             AuthenticationResultModel(
                 isSuccessful = false,
-                throttleDurationMs = THROTTLE_DURATION_MS,
+                lockoutDurationMs = LOCKOUT_DURATION_MS,
             )
         }
     }
@@ -165,8 +175,9 @@
                 AuthenticationPatternCoordinate(0, 1),
                 AuthenticationPatternCoordinate(0, 2),
             )
-        const val MAX_FAILED_AUTH_TRIES_BEFORE_THROTTLING = 5
-        const val THROTTLE_DURATION_MS = 30000
+        const val MAX_FAILED_AUTH_TRIES_BEFORE_LOCKOUT = 5
+        const val LOCKOUT_DURATION_SECONDS = 30
+        const val LOCKOUT_DURATION_MS = LOCKOUT_DURATION_SECONDS * 1000
         const val HINTING_PIN_LENGTH = 6
         val DEFAULT_PIN = buildList { repeat(HINTING_PIN_LENGTH) { add(it + 1) } }
 
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/data/FakeDeviceEntryDataLayerModule.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/data/FakeDeviceEntryDataLayerModule.kt
index d97cb56..8ff04a63 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/data/FakeDeviceEntryDataLayerModule.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/data/FakeDeviceEntryDataLayerModule.kt
@@ -16,7 +16,6 @@
 package com.android.systemui.deviceentry.data
 
 import com.android.systemui.biometrics.data.repository.FakeFingerprintPropertyRepositoryModule
-import com.android.systemui.deviceentry.data.repository.FakeDeviceEntryHapticsRepositoryModule
 import com.android.systemui.deviceentry.data.repository.FakeDeviceEntryRepositoryModule
 import com.android.systemui.keyguard.data.repository.FakeBiometricSettingsRepositoryModule
 import com.android.systemui.keyguard.data.repository.FakeDeviceEntryFaceAuthRepositoryModule
@@ -29,7 +28,6 @@
         [
             FakeBiometricSettingsRepositoryModule::class,
             FakeDeviceEntryRepositoryModule::class,
-            FakeDeviceEntryHapticsRepositoryModule::class,
             FakeDeviceEntryFaceAuthRepositoryModule::class,
             FakeDeviceEntryFingerprintAuthRepositoryModule::class,
             FakeFingerprintPropertyRepositoryModule::class,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/data/repository/FakeDeviceEntryHapticsRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/data/repository/FakeDeviceEntryHapticsRepository.kt
deleted file mode 100644
index b29b67d..0000000
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/data/repository/FakeDeviceEntryHapticsRepository.kt
+++ /dev/null
@@ -1,55 +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.deviceentry.data.repository
-
-import com.android.systemui.dagger.SysUISingleton
-import dagger.Binds
-import dagger.Module
-import javax.inject.Inject
-import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.MutableStateFlow
-import kotlinx.coroutines.flow.asStateFlow
-
-/** Fake implementation of [DeviceEntryHapticsRepository] */
-@SysUISingleton
-class FakeDeviceEntryHapticsRepository @Inject constructor() : DeviceEntryHapticsRepository {
-    private var _successHapticRequest: MutableStateFlow<Boolean> = MutableStateFlow(false)
-    override val successHapticRequest: Flow<Boolean> = _successHapticRequest.asStateFlow()
-
-    private var _errorHapticRequest: MutableStateFlow<Boolean> = MutableStateFlow(false)
-    override val errorHapticRequest: Flow<Boolean> = _errorHapticRequest.asStateFlow()
-
-    override fun requestSuccessHaptic() {
-        _successHapticRequest.value = true
-    }
-
-    override fun handleSuccessHaptic() {
-        _successHapticRequest.value = false
-    }
-
-    override fun requestErrorHaptic() {
-        _errorHapticRequest.value = true
-    }
-
-    override fun handleErrorHaptic() {
-        _errorHapticRequest.value = false
-    }
-}
-
-@Module
-interface FakeDeviceEntryHapticsRepositoryModule {
-    @Binds fun bindFake(fake: FakeDeviceEntryHapticsRepository): DeviceEntryHapticsRepository
-}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/data/repository/FakeDeviceEntryRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/data/repository/FakeDeviceEntryRepository.kt
index ba70d46..6436a38 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/data/repository/FakeDeviceEntryRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/data/repository/FakeDeviceEntryRepository.kt
@@ -16,16 +16,24 @@
 package com.android.systemui.deviceentry.data.repository
 
 import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.keyguard.shared.model.BiometricUnlockSource
 import dagger.Binds
 import dagger.Module
 import javax.inject.Inject
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableSharedFlow
 import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.asSharedFlow
 import kotlinx.coroutines.flow.asStateFlow
 
 /** Fake implementation of [DeviceEntryRepository] */
 @SysUISingleton
 class FakeDeviceEntryRepository @Inject constructor() : DeviceEntryRepository {
+    private val _enteringDeviceFromBiometricUnlock: MutableSharedFlow<BiometricUnlockSource> =
+        MutableSharedFlow()
+    override val enteringDeviceFromBiometricUnlock: Flow<BiometricUnlockSource> =
+        _enteringDeviceFromBiometricUnlock.asSharedFlow()
 
     private var isLockscreenEnabled = true
 
@@ -54,6 +62,10 @@
     fun setBypassEnabled(isBypassEnabled: Boolean) {
         _isBypassEnabled.value = isBypassEnabled
     }
+
+    suspend fun enteringDeviceFromBiometricUnlock(sourceType: BiometricUnlockSource) {
+        _enteringDeviceFromBiometricUnlock.emit(sourceType)
+    }
 }
 
 @Module
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryBiometricAuthInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryBiometricAuthInteractorKosmos.kt
new file mode 100644
index 0000000..1bd1056
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryBiometricAuthInteractorKosmos.kt
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+@file:OptIn(ExperimentalCoroutinesApi::class)
+
+package com.android.systemui.deviceentry.domain.interactor
+
+import com.android.systemui.keyguard.data.repository.biometricSettingsRepository
+import com.android.systemui.kosmos.Kosmos
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+
+val Kosmos.deviceEntryBiometricAuthInteractor by
+    Kosmos.Fixture {
+        DeviceEntryBiometricAuthInteractor(
+            biometricSettingsRepository = biometricSettingsRepository,
+            deviceEntryFaceAuthInteractor = deviceEntryFaceAuthInteractor,
+        )
+    }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/data/repository/DeviceEntryHapticsRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFaceAuthInteractorKosmos.kt
similarity index 61%
copy from packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/data/repository/DeviceEntryHapticsRepositoryKosmos.kt
copy to packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFaceAuthInteractorKosmos.kt
index 8bb07d9..d2dff78 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/data/repository/DeviceEntryHapticsRepositoryKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFaceAuthInteractorKosmos.kt
@@ -14,10 +14,17 @@
  * limitations under the License.
  */
 
-package com.android.systemui.deviceentry.data.repository
+@file:OptIn(ExperimentalCoroutinesApi::class)
 
+package com.android.systemui.deviceentry.domain.interactor
+
+import com.android.systemui.keyguard.data.repository.deviceEntryFaceAuthRepository
 import com.android.systemui.kosmos.Kosmos
+import kotlinx.coroutines.ExperimentalCoroutinesApi
 
-var Kosmos.deviceEntryHapticsRepository: DeviceEntryHapticsRepository by
-    Kosmos.Fixture { fakeDeviceEntryHapticsRepository }
-val Kosmos.fakeDeviceEntryHapticsRepository by Kosmos.Fixture { FakeDeviceEntryHapticsRepository() }
+val Kosmos.deviceEntryFaceAuthInteractor by
+    Kosmos.Fixture {
+        DeviceEntryFaceAuthInteractor(
+            repository = deviceEntryFaceAuthRepository,
+        )
+    }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFingerprintAuthInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFingerprintAuthInteractorKosmos.kt
new file mode 100644
index 0000000..66c6f86
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFingerprintAuthInteractorKosmos.kt
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+@file:OptIn(ExperimentalCoroutinesApi::class)
+
+package com.android.systemui.deviceentry.domain.interactor
+
+import com.android.systemui.keyguard.data.repository.deviceEntryFingerprintAuthRepository
+import com.android.systemui.kosmos.Kosmos
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+
+val Kosmos.deviceEntryFingerprintAuthInteractor by
+    Kosmos.Fixture {
+        DeviceEntryFingerprintAuthInteractor(
+            repository = deviceEntryFingerprintAuthRepository,
+        )
+    }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryHapticsInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryHapticsInteractorKosmos.kt
index de6cacb..6bf527d 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryHapticsInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryHapticsInteractorKosmos.kt
@@ -20,7 +20,6 @@
 
 import com.android.keyguard.logging.biometricUnlockLogger
 import com.android.systemui.biometrics.data.repository.fingerprintPropertyRepository
-import com.android.systemui.deviceentry.data.repository.fakeDeviceEntryHapticsRepository
 import com.android.systemui.keyevent.domain.interactor.keyEventInteractor
 import com.android.systemui.keyguard.data.repository.biometricSettingsRepository
 import com.android.systemui.kosmos.Kosmos
@@ -31,7 +30,9 @@
 val Kosmos.deviceEntryHapticsInteractor by
     Kosmos.Fixture {
         DeviceEntryHapticsInteractor(
-            repository = fakeDeviceEntryHapticsRepository,
+            deviceEntryInteractor = deviceEntryInteractor,
+            deviceEntryFingerprintAuthInteractor = deviceEntryFingerprintAuthInteractor,
+            deviceEntryBiometricAuthInteractor = deviceEntryBiometricAuthInteractor,
             fingerprintPropertyRepository = fingerprintPropertyRepository,
             biometricSettingsRepository = biometricSettingsRepository,
             keyEventInteractor = keyEventInteractor,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/flags/FeatureFlagsClassicKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/flags/FeatureFlagsClassicKosmos.kt
index e6b7f62..abadaf7 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/flags/FeatureFlagsClassicKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/flags/FeatureFlagsClassicKosmos.kt
@@ -16,6 +16,45 @@
 
 package com.android.systemui.flags
 
+import android.content.res.mainResources
 import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.util.mockito.mock
 
-val Kosmos.featureFlagsClassic by Kosmos.Fixture { FakeFeatureFlagsClassic() }
+/**
+ * Main fixture for supplying a [FeatureFlagsClassic]. Should be used by other fixtures. Unless
+ * overridden in the test, this by default uses [fakeFeatureFlagsClassic].
+ */
+var Kosmos.featureFlagsClassic: FeatureFlagsClassic by Kosmos.Fixture { fakeFeatureFlagsClassic }
+
+/**
+ * Fixture supplying a shared [FakeFeatureFlagsClassic] instance. Can be accessed in tests in order
+ * to override flag values.
+ */
+val Kosmos.fakeFeatureFlagsClassic by Kosmos.Fixture { FakeFeatureFlagsClassic() }
+
+/**
+ * Fixture supplying a real [FeatureFlagsClassicRelease] instance, for use by tests that want to
+ * reflect the current state of the device in release builds (example: screenshot tests).
+ *
+ * By default, this fixture is unused; tests should override [featureFlagsClassic] in order to
+ * utilize this fixture:
+ * ```kotlin
+ *   val kosmos = Kosmos()
+ *   kosmos.featureFlagsClassic = kosmos.featureFlagsClassicRelease
+ * ```
+ */
+val Kosmos.featureFlagsClassicRelease by
+    Kosmos.Fixture {
+        FeatureFlagsClassicRelease(
+            /* resources = */ mainResources,
+            /* systemProperties = */ systemPropertiesHelper,
+            /* serverFlagReader = */ serverFlagReader,
+            /* allFlags = */ FlagsCommonModule.providesAllFlags(),
+            /* restarter = */ restarter,
+        )
+    }
+
+val Kosmos.systemPropertiesHelper by Kosmos.Fixture { SystemPropertiesHelper() }
+var Kosmos.serverFlagReader: ServerFlagReader by Kosmos.Fixture { serverFlagReaderFake }
+val Kosmos.serverFlagReaderFake by Kosmos.Fixture { ServerFlagReaderFake() }
+var Kosmos.restarter: Restarter by Kosmos.Fixture { mock() }
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
index e289083..a1b6587 100644
--- 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
@@ -31,7 +31,6 @@
 
 @SysUISingleton
 class FakeDeviceEntryFaceAuthRepository @Inject constructor() : DeviceEntryFaceAuthRepository {
-
     override val isAuthenticated = MutableStateFlow(false)
     override val canRunFaceAuth = MutableStateFlow(false)
     private val _authenticationStatus = MutableStateFlow<FaceAuthenticationStatus?>(null)
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryIconViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryIconViewModelKosmos.kt
index 299262b..6557bcf 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryIconViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryIconViewModelKosmos.kt
@@ -16,7 +16,6 @@
 
 package com.android.systemui.keyguard.ui.viewmodel
 
-import com.android.systemui.deviceentry.domain.interactor.deviceEntryHapticsInteractor
 import com.android.systemui.deviceentry.domain.interactor.deviceEntryInteractor
 import com.android.systemui.deviceentry.domain.interactor.deviceEntryUdfpsInteractor
 import com.android.systemui.keyguard.domain.interactor.burnInInteractor
@@ -45,7 +44,6 @@
         shadeDependentFlows = shadeDependentFlows,
         sceneContainerFlags = sceneContainerFlags,
         keyguardViewController = { statusBarKeyguardViewManager },
-        deviceEntryHapticsInteractor = deviceEntryHapticsInteractor,
         deviceEntryInteractor = deviceEntryInteractor,
     )
 }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/log/LogAssert.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/log/LogAssert.kt
new file mode 100644
index 0000000..10f9346
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/log/LogAssert.kt
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.log
+
+import android.util.Log
+import android.util.Log.TerribleFailureHandler
+import junit.framework.Assert
+
+/**
+ * Assert that the given block makes a call to Log.wtf
+ *
+ * @return the details of the log
+ */
+fun assertLogsWtf(
+    message: String = "Expected Log.wtf to be called",
+    allowMultiple: Boolean = false,
+    loggingBlock: () -> Unit,
+): TerribleFailureLog {
+    var caught: TerribleFailureLog? = null
+    var count = 0
+    val newHandler = TerribleFailureHandler { tag, failure, system ->
+        if (caught == null) {
+            caught = TerribleFailureLog(tag, failure, system)
+        }
+        count++
+    }
+    val oldHandler = Log.setWtfHandler(newHandler)
+    try {
+        loggingBlock()
+    } finally {
+        Log.setWtfHandler(oldHandler)
+    }
+    Assert.assertNotNull(message, caught)
+    if (!allowMultiple && count != 1) {
+        Assert.fail("Unexpectedly caught Log.Wtf $count times; expected only 1.  First: $caught")
+    }
+    return caught!!
+}
+
+@JvmOverloads
+fun assertLogsWtf(
+    message: String = "Expected Log.wtf to be called",
+    allowMultiple: Boolean = false,
+    loggingRunnable: Runnable,
+): TerribleFailureLog =
+    assertLogsWtf(message = message, allowMultiple = allowMultiple) { loggingRunnable.run() }
+
+/** The data passed to [TerribleFailureHandler.onTerribleFailure] */
+data class TerribleFailureLog(
+    val tag: String,
+    val failure: Log.TerribleFailure,
+    val system: Boolean
+)
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/data/repository/FakeShadeRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/data/repository/FakeShadeRepository.kt
index 9c10848..f7005ab 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/data/repository/FakeShadeRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/data/repository/FakeShadeRepository.kt
@@ -18,21 +18,14 @@
 package com.android.systemui.shade.data.repository
 
 import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.shade.domain.model.ShadeModel
 import dagger.Binds
 import dagger.Module
 import javax.inject.Inject
-import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.MutableStateFlow
-import kotlinx.coroutines.flow.StateFlow
 
 /** Fake implementation of [ShadeRepository] */
 @SysUISingleton
 class FakeShadeRepository @Inject constructor() : ShadeRepository {
-
-    private val _shadeModel = MutableStateFlow(ShadeModel())
-    override val shadeModel: Flow<ShadeModel> = _shadeModel
-
     private val _qsExpansion = MutableStateFlow(0f)
     override val qsExpansion = _qsExpansion
 
@@ -114,10 +107,6 @@
         _legacyIsClosing.value = isClosing
     }
 
-    fun setShadeModel(model: ShadeModel) {
-        _shadeModel.value = model
-    }
-
     override fun setQsExpansion(qsExpansion: Float) {
         _qsExpansion.value = qsExpansion
     }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/data/repository/DeviceEntryHapticsRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/collection/NotifPipelineKosmos.kt
similarity index 69%
rename from packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/data/repository/DeviceEntryHapticsRepositoryKosmos.kt
rename to packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/collection/NotifPipelineKosmos.kt
index 8bb07d9..a48b270 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/data/repository/DeviceEntryHapticsRepositoryKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/collection/NotifPipelineKosmos.kt
@@ -14,10 +14,9 @@
  * limitations under the License.
  */
 
-package com.android.systemui.deviceentry.data.repository
+package com.android.systemui.statusbar.notification.collection
 
 import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.util.mockito.mock
 
-var Kosmos.deviceEntryHapticsRepository: DeviceEntryHapticsRepository by
-    Kosmos.Fixture { fakeDeviceEntryHapticsRepository }
-val Kosmos.fakeDeviceEntryHapticsRepository by Kosmos.Fixture { FakeDeviceEntryHapticsRepository() }
+var Kosmos.notifPipeline by Kosmos.Fixture { mock<NotifPipeline>() }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/data/repository/DeviceEntryHapticsRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/collection/notifcollection/CommonNotifCollectionKosmos.kt
similarity index 69%
copy from packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/data/repository/DeviceEntryHapticsRepositoryKosmos.kt
copy to packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/collection/notifcollection/CommonNotifCollectionKosmos.kt
index 8bb07d9..f00538e 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/data/repository/DeviceEntryHapticsRepositoryKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/collection/notifcollection/CommonNotifCollectionKosmos.kt
@@ -14,10 +14,9 @@
  * limitations under the License.
  */
 
-package com.android.systemui.deviceentry.data.repository
+package com.android.systemui.statusbar.notification.collection.notifcollection
 
 import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.statusbar.notification.collection.notifPipeline
 
-var Kosmos.deviceEntryHapticsRepository: DeviceEntryHapticsRepository by
-    Kosmos.Fixture { fakeDeviceEntryHapticsRepository }
-val Kosmos.fakeDeviceEntryHapticsRepository by Kosmos.Fixture { FakeDeviceEntryHapticsRepository() }
+var Kosmos.commonNotifCollection by Kosmos.Fixture { notifPipeline }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/data/repository/DeviceEntryHapticsRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/collection/provider/SectionStyleProviderKosmos.kt
similarity index 69%
copy from packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/data/repository/DeviceEntryHapticsRepositoryKosmos.kt
copy to packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/collection/provider/SectionStyleProviderKosmos.kt
index 8bb07d9..e7c4085 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/data/repository/DeviceEntryHapticsRepositoryKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/collection/provider/SectionStyleProviderKosmos.kt
@@ -14,10 +14,9 @@
  * limitations under the License.
  */
 
-package com.android.systemui.deviceentry.data.repository
+package com.android.systemui.statusbar.notification.collection.provider
 
 import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.util.mockito.mock
 
-var Kosmos.deviceEntryHapticsRepository: DeviceEntryHapticsRepository by
-    Kosmos.Fixture { fakeDeviceEntryHapticsRepository }
-val Kosmos.fakeDeviceEntryHapticsRepository by Kosmos.Fixture { FakeDeviceEntryHapticsRepository() }
+var Kosmos.sectionStyleProvider: SectionStyleProvider by Kosmos.Fixture { mock() }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/data/repository/DeviceEntryHapticsRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/domain/interactor/RenderNotificationListInteractorKosmos.kt
similarity index 60%
copy from packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/data/repository/DeviceEntryHapticsRepositoryKosmos.kt
copy to packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/domain/interactor/RenderNotificationListInteractorKosmos.kt
index 8bb07d9..f7acae9 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/data/repository/DeviceEntryHapticsRepositoryKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/domain/interactor/RenderNotificationListInteractorKosmos.kt
@@ -14,10 +14,13 @@
  * limitations under the License.
  */
 
-package com.android.systemui.deviceentry.data.repository
+package com.android.systemui.statusbar.notification.domain.interactor
 
 import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.statusbar.notification.collection.provider.sectionStyleProvider
+import com.android.systemui.statusbar.notification.data.repository.activeNotificationListRepository
 
-var Kosmos.deviceEntryHapticsRepository: DeviceEntryHapticsRepository by
-    Kosmos.Fixture { fakeDeviceEntryHapticsRepository }
-val Kosmos.fakeDeviceEntryHapticsRepository by Kosmos.Fixture { FakeDeviceEntryHapticsRepository() }
+val Kosmos.renderNotificationListInteractor by
+    Kosmos.Fixture {
+        RenderNotificationListInteractor(activeNotificationListRepository, sectionStyleProvider)
+    }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/data/repository/DeviceEntryHapticsRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/icon/IconBuilderKosmos.kt
similarity index 69%
copy from packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/data/repository/DeviceEntryHapticsRepositoryKosmos.kt
copy to packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/icon/IconBuilderKosmos.kt
index 8bb07d9..4535652 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/data/repository/DeviceEntryHapticsRepositoryKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/icon/IconBuilderKosmos.kt
@@ -14,10 +14,9 @@
  * limitations under the License.
  */
 
-package com.android.systemui.deviceentry.data.repository
+package com.android.systemui.statusbar.notification.icon
 
+import android.content.applicationContext
 import com.android.systemui.kosmos.Kosmos
 
-var Kosmos.deviceEntryHapticsRepository: DeviceEntryHapticsRepository by
-    Kosmos.Fixture { fakeDeviceEntryHapticsRepository }
-val Kosmos.fakeDeviceEntryHapticsRepository by Kosmos.Fixture { FakeDeviceEntryHapticsRepository() }
+val Kosmos.iconBuilder by Kosmos.Fixture { IconBuilder(applicationContext) }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/data/repository/DeviceEntryHapticsRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/icon/IconManagerKosmos.kt
similarity index 68%
copy from packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/data/repository/DeviceEntryHapticsRepositoryKosmos.kt
copy to packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/icon/IconManagerKosmos.kt
index 8bb07d9..d3a8e0c 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/data/repository/DeviceEntryHapticsRepositoryKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/icon/IconManagerKosmos.kt
@@ -14,10 +14,11 @@
  * limitations under the License.
  */
 
-package com.android.systemui.deviceentry.data.repository
+package com.android.systemui.statusbar.notification.icon
 
+import android.content.pm.launcherApps
 import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.statusbar.notification.collection.notifcollection.commonNotifCollection
 
-var Kosmos.deviceEntryHapticsRepository: DeviceEntryHapticsRepository by
-    Kosmos.Fixture { fakeDeviceEntryHapticsRepository }
-val Kosmos.fakeDeviceEntryHapticsRepository by Kosmos.Fixture { FakeDeviceEntryHapticsRepository() }
+val Kosmos.iconManager by
+    Kosmos.Fixture { IconManager(commonNotifCollection, launcherApps, iconBuilder) }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationListViewBinderKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationListViewBinderKosmos.kt
index 75e5aeaf..ca5b401 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationListViewBinderKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationListViewBinderKosmos.kt
@@ -26,18 +26,18 @@
 import com.android.systemui.statusbar.notification.icon.ui.viewbinder.statusBarIconViewBindingFailureTracker
 import com.android.systemui.statusbar.notification.stack.ui.viewmodel.notificationListViewModel
 import com.android.systemui.statusbar.phone.notificationIconAreaController
-import com.android.systemui.statusbar.policy.configurationController
+import com.android.systemui.statusbar.ui.systemBarUtilsState
 
 val Kosmos.notificationListViewBinder by Fixture {
     NotificationListViewBinder(
         viewModel = notificationListViewModel,
         backgroundDispatcher = testDispatcher,
         configuration = configurationState,
-        configurationController = configurationController,
         falsingManager = falsingManager,
         iconAreaController = notificationIconAreaController,
         iconViewBindingFailureTracker = statusBarIconViewBindingFailureTracker,
         metricsLogger = metricsLogger,
         shelfIconViewStore = shelfNotificationIconViewStore,
+        systemBarUtilsState = systemBarUtilsState,
     )
 }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/data/repository/DeviceEntryHapticsRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/ui/FakeSystemBarUtilsProxy.kt
similarity index 64%
copy from packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/data/repository/DeviceEntryHapticsRepositoryKosmos.kt
copy to packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/ui/FakeSystemBarUtilsProxy.kt
index 8bb07d9..d38baba 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/data/repository/DeviceEntryHapticsRepositoryKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/ui/FakeSystemBarUtilsProxy.kt
@@ -14,10 +14,8 @@
  * limitations under the License.
  */
 
-package com.android.systemui.deviceentry.data.repository
+package com.android.systemui.statusbar.ui
 
-import com.android.systemui.kosmos.Kosmos
-
-var Kosmos.deviceEntryHapticsRepository: DeviceEntryHapticsRepository by
-    Kosmos.Fixture { fakeDeviceEntryHapticsRepository }
-val Kosmos.fakeDeviceEntryHapticsRepository by Kosmos.Fixture { FakeDeviceEntryHapticsRepository() }
+class FakeSystemBarUtilsProxy(private var statusBarHeight: Int) : SystemBarUtilsProxy {
+    override fun getStatusBarHeight(): Int = statusBarHeight
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/ui/SystemBarUtilsProxyKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/ui/SystemBarUtilsProxyKosmos.kt
new file mode 100644
index 0000000..f24037d
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/ui/SystemBarUtilsProxyKosmos.kt
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.ui
+
+import android.content.applicationContext
+import android.content.res.mainResources
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.Kosmos.Fixture
+import com.android.systemui.res.R
+
+/**
+ * Main fixture for supplying a [SystemBarUtilsProxy]. Should be used by other fixtures. Unless
+ * overridden in the test, this by default uses [fakeSystemBarUtilsProxy].
+ */
+var Kosmos.systemBarUtilsProxy: SystemBarUtilsProxy by Fixture { fakeSystemBarUtilsProxy }
+
+/**
+ * Fixture supplying a real [SystemBarUtilsProxyImpl] instance, for use by tests that want to use
+ * the real device logic to determine system bar properties. Note this this real instance does *not*
+ * support Robolectric tests; by opting in, you are explicitly opting-out of using Robolectric.
+ *
+ * By default, this fixture is unused; tests should override [systemBarUtilsProxy] in order to
+ * utilize this fixture:
+ * ```kotlin
+ *   val kosmos = Kosmos()
+ *   kosmos.systemBarUtilsProxy = kosmos.systemBarUtilsProxyImpl
+ * ```
+ */
+val Kosmos.systemBarUtilsProxyImpl by Fixture { SystemBarUtilsProxyImpl(applicationContext) }
+
+/**
+ * Fixture supplying a shared [FakeSystemBarUtilsProxy] instance. Can be accessed or overridden in
+ * tests in order to provide custom results.
+ */
+var Kosmos.fakeSystemBarUtilsProxy by Fixture {
+    FakeSystemBarUtilsProxy(mainResources.getDimensionPixelSize(R.dimen.status_bar_height))
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/data/repository/DeviceEntryHapticsRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/ui/SystemBarUtilsStateKosmos.kt
similarity index 69%
copy from packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/data/repository/DeviceEntryHapticsRepositoryKosmos.kt
copy to packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/ui/SystemBarUtilsStateKosmos.kt
index 8bb07d9..e208add 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/data/repository/DeviceEntryHapticsRepositoryKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/ui/SystemBarUtilsStateKosmos.kt
@@ -14,10 +14,10 @@
  * limitations under the License.
  */
 
-package com.android.systemui.deviceentry.data.repository
+package com.android.systemui.statusbar.ui
 
 import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.statusbar.policy.configurationController
 
-var Kosmos.deviceEntryHapticsRepository: DeviceEntryHapticsRepository by
-    Kosmos.Fixture { fakeDeviceEntryHapticsRepository }
-val Kosmos.fakeDeviceEntryHapticsRepository by Kosmos.Fixture { FakeDeviceEntryHapticsRepository() }
+val Kosmos.systemBarUtilsState by
+    Kosmos.Fixture { SystemBarUtilsState(configurationController, systemBarUtilsProxy) }
diff --git a/ravenwood/framework-minus-apex-ravenwood-policies.txt b/ravenwood/framework-minus-apex-ravenwood-policies.txt
index c9069e5..f5e4af5 100644
--- a/ravenwood/framework-minus-apex-ravenwood-policies.txt
+++ b/ravenwood/framework-minus-apex-ravenwood-policies.txt
@@ -3,6 +3,9 @@
 # Keep all AIDL interfaces
 class :aidl stubclass
 
+# Keep all feature flag implementations
+class :feature_flags stubclass
+
 # Collections
 class android.util.ArrayMap stubclass
 class android.util.ArraySet stubclass
diff --git a/ravenwood/ravenwood-annotation-allowed-classes.txt b/ravenwood/ravenwood-annotation-allowed-classes.txt
index 831fce1..2ba5f6b 100644
--- a/ravenwood/ravenwood-annotation-allowed-classes.txt
+++ b/ravenwood/ravenwood-annotation-allowed-classes.txt
@@ -1,6 +1,8 @@
 # Only classes listed here can use the Ravenwood annotations.
 
 com.android.internal.util.ArrayUtils
+com.android.internal.os.LongArrayMultiStateCounter
+com.android.internal.os.LongArrayMultiStateCounter$LongArrayContainer
 
 android.util.AtomicFile
 android.util.DataUnit
@@ -22,6 +24,7 @@
 android.util.TimeUtils
 android.util.Xml
 
+android.os.BatteryConsumer
 android.os.Binder
 android.os.Binder$IdentitySupplier
 android.os.FileUtils
@@ -88,6 +91,8 @@
 android.graphics.Rect
 android.graphics.RectF
 
+android.content.ContentProvider
+
 com.android.server.LocalServices
 
 com.android.internal.os.SomeArgs
diff --git a/ravenwood/test-authors.md b/ravenwood/test-authors.md
index 5adef53..de05777 100644
--- a/ravenwood/test-authors.md
+++ b/ravenwood/test-authors.md
@@ -71,10 +71,10 @@
 Once you’ve defined your test, you can use typical commands to execute it locally:
 
 ```
-$ atest MyTestsRavenwood
+$ atest --host MyTestsRavenwood
 ```
 
-> **Note:** There's a known bug where `atest` currently requires a connected device to run Ravenwood tests, but that device isn't used for testing.
+> **Note:** There's a known bug where `atest` currently requires a connected device to run Ravenwood tests, but that device isn't used for testing. Using the `--host` argument above is a way to bypass this requirement until bug #312525698 is fixed.
 
 You can also run your new tests automatically via `TEST_MAPPING` rules like this:
 
@@ -89,6 +89,27 @@
 }
 ```
 
+## Strategies for feature flags
+
+Ravenwood supports writing tests against logic that uses feature flags through the existing `SetFlagsRule` infrastructure maintained by the feature flagging team:
+
+```
+import android.platform.test.flag.junit.SetFlagsRule;
+
+@RunWith(AndroidJUnit4.class)
+public class MyCodeTest {
+    @Rule
+    public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
+
+    @Test
+    public void testEnabled() {
+        mSetFlagsRule.enableFlags(Flags.FLAG_MY_FLAG);
+        // verify test logic that depends on flag being enabled
+    }
+```
+
+This naturally composes together well with any `RavenwoodRule` that your test may need.
+
 ## Strategies for migration/bivalent tests
 
 Ravenwood aims to support tests that are written in a “bivalent” way, where the same test code can run on both a real Android device and under a Ravenwood environment.
diff --git a/services/accessibility/Android.bp b/services/accessibility/Android.bp
index e9bb763..b8cf13b 100644
--- a/services/accessibility/Android.bp
+++ b/services/accessibility/Android.bp
@@ -32,6 +32,22 @@
     ],
 }
 
+java_library_static {
+    name: "AccessibilityGestureUtils",
+    srcs: [
+        "java/**/gestures/GestureMatcher.java",
+        "java/**/gestures/GestureManifold.java",
+        "java/**/gestures/MultiFingerMultiTap.java",
+        "java/**/gestures/TouchState.java",
+    ],
+    static_libs: [
+        "services.accessibility",
+    ],
+    libs: [
+        "androidx.annotation_annotation",
+    ],
+}
+
 aconfig_declarations {
     name: "com_android_server_accessibility_flags",
     package: "com.android.server.accessibility",
diff --git a/services/accessibility/java/com/android/server/accessibility/gestures/GestureManifold.java b/services/accessibility/java/com/android/server/accessibility/gestures/GestureManifold.java
index 903a071..e54f0c1 100644
--- a/services/accessibility/java/com/android/server/accessibility/gestures/GestureManifold.java
+++ b/services/accessibility/java/com/android/server/accessibility/gestures/GestureManifold.java
@@ -82,7 +82,7 @@
  * detector. Gesture matchers are tied to a single gesture. It calls listener callback functions
  * when a gesture starts or completes.
  */
-class GestureManifold implements GestureMatcher.StateChangeListener {
+public class GestureManifold implements GestureMatcher.StateChangeListener {
 
     private static final String LOG_TAG = "GestureManifold";
 
@@ -111,7 +111,7 @@
     // Shared state information.
     private TouchState mState;
 
-    GestureManifold(Context context, Listener listener, TouchState state, Handler handler) {
+    public GestureManifold(Context context, Listener listener, TouchState state, Handler handler) {
         mContext = context;
         mHandler = handler;
         mListener = listener;
@@ -222,7 +222,7 @@
      * @return True if the event has been appropriately handled by the gesture manifold and related
      *     callback functions, false if it should be handled further by the calling function.
      */
-    boolean onMotionEvent(MotionEvent event, MotionEvent rawEvent, int policyFlags) {
+    public boolean onMotionEvent(MotionEvent event, MotionEvent rawEvent, int policyFlags) {
         if (mState.isClear()) {
             if (event.getActionMasked() == MotionEvent.ACTION_DOWN) {
                 // Validity safeguard: if touch state is clear, then matchers should always be clear
diff --git a/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandler.java b/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandler.java
index f55ecb0..0a2a780 100644
--- a/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandler.java
+++ b/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandler.java
@@ -981,17 +981,22 @@
                                 transitionToDelegatingStateAndClear();
                             }
                             transitToSinglePanningStateAndClear();
-                        } else {
+                        } else if (!mIsTwoFingerCountReached) {
+                            // If it is a two-finger gesture, do not transition to the
+                            // delegating state to ensure the reachability of
+                            // the two-finger triple tap (triggerable with ACTION_UP)
                             transitionToDelegatingStateAndClear();
                         }
                     } else if (isActivated() && pointerDownValid(mSecondPointerDownLocation)
                             && distanceClosestPointerToPoint(
-                            mSecondPointerDownLocation, /* move */ event) > mSwipeMinDistance
-                            // If mCompleteTapCount is not zero, it means that it is a multi tap
-                            // gesture. So, we should not transit to the PanningScalingState.
-                            && mCompletedTapCount == 0) {
+                            mSecondPointerDownLocation, /* move */ event) > mSwipeMinDistance) {
                         // Second pointer is swiping, so transit to PanningScalingState
-                        transitToPanningScalingStateAndClear();
+                        // Delay an ACTION_MOVE for tap timeout to ensure it is not trigger from
+                        // multi finger multi tap
+                        storePointerDownLocation(mSecondPointerDownLocation, event);
+                        mHandler.sendEmptyMessageDelayed(
+                                MESSAGE_TRANSITION_TO_PANNINGSCALING_STATE,
+                                ViewConfiguration.getTapTimeout());
                     }
                 }
                 break;
diff --git a/services/companion/java/com/android/server/companion/virtual/InputController.java b/services/companion/java/com/android/server/companion/virtual/InputController.java
index c111ec3..1f89e57b 100644
--- a/services/companion/java/com/android/server/companion/virtual/InputController.java
+++ b/services/companion/java/com/android/server/companion/virtual/InputController.java
@@ -253,13 +253,8 @@
         mInputManagerInternal.setDisplayEligibilityForPointerCapture(displayId, isEligible);
     }
 
-    void setLocalIme(int displayId) {
-        // WM throws a SecurityException if the display is untrusted.
-        if ((mDisplayManagerInternal.getDisplayInfo(displayId).flags & Display.FLAG_TRUSTED)
-                == Display.FLAG_TRUSTED) {
-            mWindowManager.setDisplayImePolicy(displayId,
-                    WindowManager.DISPLAY_IME_POLICY_LOCAL);
-        }
+    void setDisplayImePolicy(int displayId, @WindowManager.DisplayImePolicy int policy) {
+        mWindowManager.setDisplayImePolicy(displayId, policy);
     }
 
     @GuardedBy("mLock")
diff --git a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
index 13c7924..58aa2c3 100644
--- a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
+++ b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
@@ -899,6 +899,24 @@
 
     @Override // Binder call
     @EnforcePermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
+    public void setDisplayImePolicy(int displayId, @WindowManager.DisplayImePolicy int policy) {
+        super.setDisplayImePolicy_enforcePermission();
+        synchronized (mVirtualDeviceLock) {
+            if (!mVirtualDisplays.contains(displayId)) {
+                throw new SecurityException("Display ID " + displayId
+                        + " not found for this virtual device");
+            }
+        }
+        final long ident = Binder.clearCallingIdentity();
+        try {
+            mInputController.setDisplayImePolicy(displayId, policy);
+        } finally {
+            Binder.restoreCallingIdentity(ident);
+        }
+    }
+
+    @Override // Binder call
+    @EnforcePermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
     @Nullable
     public List<VirtualSensor> getVirtualSensorList() {
         super.getVirtualSensorList_enforcePermission();
@@ -1095,7 +1113,12 @@
             mInputController.setPointerAcceleration(1f, displayId);
             mInputController.setDisplayEligibilityForPointerCapture(/* isEligible= */ false,
                     displayId);
-            mInputController.setLocalIme(displayId);
+            // WM throws a SecurityException if the display is untrusted.
+            if ((mDisplayManagerInternal.getDisplayInfo(displayId).flags & Display.FLAG_TRUSTED)
+                    == Display.FLAG_TRUSTED) {
+                mInputController.setDisplayImePolicy(displayId,
+                        WindowManager.DISPLAY_IME_POLICY_LOCAL);
+            }
         } finally {
             Binder.restoreCallingIdentity(token);
         }
diff --git a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java
index 9b78ed4..923728f 100644
--- a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java
+++ b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java
@@ -859,6 +859,11 @@
         }
 
         @Override
+        public boolean isValidVirtualDeviceId(int deviceId) {
+            return mImpl.isValidVirtualDeviceId(deviceId);
+        }
+
+        @Override
         public @Nullable String getPersistentIdForDevice(int deviceId) {
             if (deviceId == Context.DEVICE_ID_DEFAULT) {
                 return VirtualDeviceManager.PERSISTENT_DEVICE_ID_DEFAULT;
diff --git a/services/core/Android.bp b/services/core/Android.bp
index b4cf34e..20a3b9a 100644
--- a/services/core/Android.bp
+++ b/services/core/Android.bp
@@ -154,7 +154,6 @@
 
     static_libs: [
         "android.frameworks.location.altitude-V1-java", // AIDL
-        "android.frameworks.vibrator-V1-java", // AIDL
         "android.hardware.authsecret-V1.0-java",
         "android.hardware.authsecret-V1-java",
         "android.hardware.boot-V1.0-java", // HIDL
diff --git a/services/core/java/com/android/server/BatteryService.java b/services/core/java/com/android/server/BatteryService.java
index b9a3c0c4..33726d1 100644
--- a/services/core/java/com/android/server/BatteryService.java
+++ b/services/core/java/com/android/server/BatteryService.java
@@ -17,6 +17,7 @@
 package com.android.server;
 
 import static android.os.Flags.stateOfHealthPublic;
+import static android.os.Flags.batteryServiceSupportCurrentAdbCommand;
 
 import static com.android.internal.logging.nano.MetricsProto.MetricsEvent;
 import static com.android.server.health.Utils.copyV1Battery;
@@ -927,9 +928,12 @@
         pw.println("Battery service (battery) commands:");
         pw.println("  help");
         pw.println("    Print this help text.");
-        pw.println("  get [-f] [ac|usb|wireless|dock|status|level|temp|present|counter|invalid]");
-        pw.println("  set [-f] "
-                + "[ac|usb|wireless|dock|status|level|temp|present|counter|invalid] <value>");
+        String getSetOptions = "ac|usb|wireless|dock|status|level|temp|present|counter|invalid";
+        if (batteryServiceSupportCurrentAdbCommand()) {
+            getSetOptions += "|current_now|current_average";
+        }
+        pw.println("  get [-f] [" + getSetOptions + "]");
+        pw.println("  set [-f] [" + getSetOptions + "] <value>");
         pw.println("    Force a battery property value, freezing battery state.");
         pw.println("    -f: force a battery change broadcast be sent, prints new sequence.");
         pw.println("  unplug [-f]");
@@ -1001,6 +1005,16 @@
                     case "counter":
                         pw.println(mHealthInfo.batteryChargeCounterUah);
                         break;
+                    case "current_now":
+                        if (batteryServiceSupportCurrentAdbCommand()) {
+                            pw.println(mHealthInfo.batteryCurrentMicroamps);
+                        }
+                        break;
+                    case "current_average":
+                        if (batteryServiceSupportCurrentAdbCommand()) {
+                            pw.println(mHealthInfo.batteryCurrentAverageMicroamps);
+                        }
+                        break;
                     case "temp":
                         pw.println(mHealthInfo.batteryTemperatureTenthsCelsius);
                         break;
@@ -1058,6 +1072,16 @@
                         case "counter":
                             mHealthInfo.batteryChargeCounterUah = Integer.parseInt(value);
                             break;
+                        case "current_now":
+                            if (batteryServiceSupportCurrentAdbCommand()) {
+                                mHealthInfo.batteryCurrentMicroamps = Integer.parseInt(value);
+                            }
+                            break;
+                        case "current_average":
+                            if (batteryServiceSupportCurrentAdbCommand()) {
+                                mHealthInfo.batteryCurrentAverageMicroamps =
+                                        Integer.parseInt(value);
+                            }
                         case "temp":
                             mHealthInfo.batteryTemperatureTenthsCelsius = Integer.parseInt(value);
                             break;
diff --git a/services/core/java/com/android/server/Watchdog.java b/services/core/java/com/android/server/Watchdog.java
index 4bb9f4f..c436c72 100644
--- a/services/core/java/com/android/server/Watchdog.java
+++ b/services/core/java/com/android/server/Watchdog.java
@@ -127,6 +127,7 @@
         "/system/bin/mediaserver",
         "/system/bin/netd",
         "/system/bin/sdcard",
+        "/system/bin/servicemanager",
         "/system/bin/surfaceflinger",
         "/system/bin/vold",
         "media.extractor", // system/bin/mediaextractor
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 3e533a6..ac173f3 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -11972,6 +11972,7 @@
         boolean dumpSwapPss;
         boolean dumpProto;
         boolean mDumpPrivateDirty;
+        boolean mDumpAllocatorStats;
     }
 
     @NeverCompile // Avoid size overhead of debugging code.
@@ -11991,6 +11992,7 @@
         opts.dumpSwapPss = false;
         opts.dumpProto = asProto;
         opts.mDumpPrivateDirty = false;
+        opts.mDumpAllocatorStats = false;
 
         int opti = 0;
         while (opti < args.length) {
@@ -12027,7 +12029,8 @@
                 opts.isCheckinRequest = true;
             } else if ("--proto".equals(opt)) {
                 opts.dumpProto = true;
-
+            } else if ("--logstats".equals(opt)) {
+                opts.mDumpAllocatorStats = true;
             } else if ("-h".equals(opt)) {
                 pw.println("meminfo dump options: [-a] [-d] [-c] [-s] [--oom] [process]");
                 pw.println("  -a: include all available information for each process.");
@@ -12238,7 +12241,8 @@
                             try {
                                 thread.dumpMemInfo(tp.getWriteFd(),
                                         mi, opts.isCheckinRequest, opts.dumpFullDetails,
-                                        opts.dumpDalvik, opts.dumpSummaryOnly, opts.dumpUnreachable, innerArgs);
+                                        opts.dumpDalvik, opts.dumpSummaryOnly, opts.dumpUnreachable,
+                                        opts.mDumpAllocatorStats, innerArgs);
                                 tp.go(fd, opts.dumpUnreachable ? 30000 : 5000);
                             } finally {
                                 tp.kill();
@@ -17510,6 +17514,8 @@
      *         other {@code ActivityManager#USER_OP_*} codes for failure.
      *
      */
+    // TODO(b/302662311): Add javadoc changes corresponding to the user property that allows
+    // delayed locking behavior once the private space flag is finalized.
     @Override
     public int stopUserWithDelayedLocking(final int userId, boolean force,
             final IStopUserCallback callback) {
diff --git a/services/core/java/com/android/server/am/UserController.java b/services/core/java/com/android/server/am/UserController.java
index 47a99fe..a6b532c 100644
--- a/services/core/java/com/android/server/am/UserController.java
+++ b/services/core/java/com/android/server/am/UserController.java
@@ -356,6 +356,8 @@
      * Once total number of unlocked users reach mMaxRunningUsers, least recently used user
      * will be locked.
      */
+    // TODO(b/302662311): Add javadoc changes corresponding to the user property that allows
+    // delayed locking behavior once the private space flag is finalized.
     @GuardedBy("mLock")
     private boolean mDelayUserDataLocking;
 
@@ -365,11 +367,12 @@
     private volatile boolean mAllowUserUnlocking;
 
     /**
-     * Keep track of last active users for mDelayUserDataLocking.
-     * The latest stopped user is placed in front while the least recently stopped user in back.
+     * Keep track of last active users for delayUserDataLocking.
+     * The most recently stopped user with delayed locking is placed in front, while the least
+     * recently stopped user in back.
      */
     @GuardedBy("mLock")
-    private final ArrayList<Integer> mLastActiveUsers = new ArrayList<>();
+    private final ArrayList<Integer> mLastActiveUsersForDelayedLocking = new ArrayList<>();
 
     /**
      * Map of userId to {@link UserCompletedEventType} event flags, indicating which as-yet-
@@ -1011,20 +1014,21 @@
         Slogf.i(TAG, "stopSingleUserLU userId=" + userId);
         final UserState uss = mStartedUsers.get(userId);
         if (uss == null) {  // User is not started
-            // If mDelayUserDataLocking is set and allowDelayedLocking is not set, we need to lock
-            // the requested user as the client wants to stop and lock the user. On the other hand,
-            // having keyEvictedCallback set will lead into locking user if mDelayUserDataLocking
-            // is set as that means client wants to lock the user immediately.
-            // If mDelayUserDataLocking is not set, the user was already locked when it was stopped
-            // and no further action is necessary.
-            if (mDelayUserDataLocking) {
+            // If canDelayDataLockingForUser() is true and allowDelayedLocking is false, we need
+            // to lock the requested user as the client wants to stop and lock the user. On the
+            // other hand, having keyEvictedCallback set will lead into locking user if
+            // canDelayDataLockingForUser() is true as that means client wants to lock the user
+            // immediately.
+            // If canDelayDataLockingForUser() is false, the user was already locked when it was
+            // stopped and no further action is necessary.
+            if (canDelayDataLockingForUser(userId)) {
                 if (allowDelayedLocking && keyEvictedCallback != null) {
                     Slogf.wtf(TAG, "allowDelayedLocking set with KeyEvictedCallback, ignore it"
                             + " and lock user:" + userId, new RuntimeException());
                     allowDelayedLocking = false;
                 }
                 if (!allowDelayedLocking) {
-                    if (mLastActiveUsers.remove(Integer.valueOf(userId))) {
+                    if (mLastActiveUsersForDelayedLocking.remove(Integer.valueOf(userId))) {
                         // should lock the user, user is already gone
                         final ArrayList<KeyEvictedCallback> keyEvictedCallbacks;
                         if (keyEvictedCallback != null) {
@@ -1354,14 +1358,21 @@
     @GuardedBy("mLock")
     private int updateUserToLockLU(@UserIdInt int userId, boolean allowDelayedLocking) {
         int userIdToLock = userId;
-        if (mDelayUserDataLocking && allowDelayedLocking && !getUserInfo(userId).isEphemeral()
+        // TODO: Decouple the delayed locking flows from mMaxRunningUsers or rename the property to
+        // state maximum running unlocked users specifically
+        if (canDelayDataLockingForUser(userIdToLock) && allowDelayedLocking
+                && !getUserInfo(userId).isEphemeral()
                 && !hasUserRestriction(UserManager.DISALLOW_RUN_IN_BACKGROUND, userId)) {
-            mLastActiveUsers.remove((Integer) userId); // arg should be object, not index
-            mLastActiveUsers.add(0, userId);
-            int totalUnlockedUsers = mStartedUsers.size() + mLastActiveUsers.size();
+            // arg should be object, not index
+            mLastActiveUsersForDelayedLocking.remove((Integer) userId);
+            mLastActiveUsersForDelayedLocking.add(0, userId);
+            int totalUnlockedUsers = mStartedUsers.size()
+                    + mLastActiveUsersForDelayedLocking.size();
             if (totalUnlockedUsers > mMaxRunningUsers) { // should lock a user
-                userIdToLock = mLastActiveUsers.get(mLastActiveUsers.size() - 1);
-                mLastActiveUsers.remove(mLastActiveUsers.size() - 1);
+                userIdToLock = mLastActiveUsersForDelayedLocking.get(
+                        mLastActiveUsersForDelayedLocking.size() - 1);
+                mLastActiveUsersForDelayedLocking
+                        .remove(mLastActiveUsersForDelayedLocking.size() - 1);
                 Slogf.i(TAG, "finishUserStopped, stopping user:" + userId
                         + " lock user:" + userIdToLock);
             } else {
@@ -1374,6 +1385,24 @@
     }
 
     /**
+     * Returns whether the user can have its CE storage left unlocked, even when it is stopped,
+     * either due to a global device configuration or an individual user's property.
+     */
+    private boolean canDelayDataLockingForUser(@UserIdInt int userIdToLock) {
+        if (allowBiometricUnlockForPrivateProfile()) {
+            final UserProperties userProperties = getUserProperties(userIdToLock);
+            return (mDelayUserDataLocking || (userProperties != null
+                    && userProperties.getAllowStoppingUserWithDelayedLocking()));
+        }
+        return mDelayUserDataLocking;
+    }
+
+    private boolean allowBiometricUnlockForPrivateProfile() {
+        return android.os.Flags.allowPrivateProfile()
+                && android.multiuser.Flags.enableBiometricsToUnlockPrivateSpace();
+    }
+
+    /**
      * Determines the list of users that should be stopped together with the specified
      * {@code userId}. The returned list includes {@code userId}.
      */
@@ -3161,7 +3190,7 @@
             pw.println("  mCurrentProfileIds:" + Arrays.toString(mCurrentProfileIds));
             pw.println("  mCurrentUserId:" + mCurrentUserId);
             pw.println("  mTargetUserId:" + mTargetUserId);
-            pw.println("  mLastActiveUsers:" + mLastActiveUsers);
+            pw.println("  mLastActiveUsersForDelayedLocking:" + mLastActiveUsersForDelayedLocking);
             pw.println("  mDelayUserDataLocking:" + mDelayUserDataLocking);
             pw.println("  mAllowUserUnlocking:" + mAllowUserUnlocking);
             pw.println("  shouldStopUserOnSwitch():" + shouldStopUserOnSwitch());
diff --git a/services/core/java/com/android/server/appop/DiscreteRegistry.java b/services/core/java/com/android/server/appop/DiscreteRegistry.java
index e91b7e8..b1a12f7 100644
--- a/services/core/java/com/android/server/appop/DiscreteRegistry.java
+++ b/services/core/java/com/android/server/appop/DiscreteRegistry.java
@@ -37,6 +37,7 @@
 import static android.app.AppOpsManager.OP_RECEIVE_AMBIENT_TRIGGER_AUDIO;
 import static android.app.AppOpsManager.OP_RECEIVE_SANDBOX_TRIGGER_AUDIO;
 import static android.app.AppOpsManager.OP_RECORD_AUDIO;
+import static android.app.AppOpsManager.OP_RESERVED_FOR_TESTING;
 import static android.app.AppOpsManager.flagsToString;
 import static android.app.AppOpsManager.getUidStateName;
 
@@ -136,7 +137,7 @@
     private static final String DEFAULT_DISCRETE_OPS = OP_FINE_LOCATION + "," + OP_COARSE_LOCATION
             + "," + OP_CAMERA + "," + OP_RECORD_AUDIO + "," + OP_PHONE_CALL_MICROPHONE + ","
             + OP_PHONE_CALL_CAMERA + "," + OP_RECEIVE_AMBIENT_TRIGGER_AUDIO + ","
-            + OP_RECEIVE_SANDBOX_TRIGGER_AUDIO;
+            + OP_RECEIVE_SANDBOX_TRIGGER_AUDIO + "," + OP_RESERVED_FOR_TESTING;
     private static final long DEFAULT_DISCRETE_HISTORY_CUTOFF = Duration.ofDays(7).toMillis();
     private static final long MAXIMUM_DISCRETE_HISTORY_CUTOFF = Duration.ofDays(30).toMillis();
     private static final long DEFAULT_DISCRETE_HISTORY_QUANTIZATION =
diff --git a/services/core/java/com/android/server/audio/AdiDeviceState.java b/services/core/java/com/android/server/audio/AdiDeviceState.java
index ffdab7d..9ae43a0 100644
--- a/services/core/java/com/android/server/audio/AdiDeviceState.java
+++ b/services/core/java/com/android/server/audio/AdiDeviceState.java
@@ -235,9 +235,10 @@
      * {@link AdiDeviceState#toPersistableString()}.
      */
     public static int getPeristedMaxSize() {
-        return 36;  /* (mDeviceType)2 + (mDeviceAddress)17 + (mInternalDeviceType)9 + (mSAEnabled)1
+        return 39;  /* (mDeviceType)2 + (mDeviceAddress)17 + (mInternalDeviceType)9 + (mSAEnabled)1
                            + (mHasHeadTracker)1 + (mHasHeadTrackerEnabled)1
-                           + (SETTINGS_FIELD_SEPARATOR)5 */
+                           + (mAudioDeviceCategory)1 + (SETTINGS_FIELD_SEPARATOR)6
+                           + (SETTING_DEVICE_SEPARATOR)1 */
     }
 
     @Nullable
diff --git a/services/core/java/com/android/server/audio/AudioDeviceBroker.java b/services/core/java/com/android/server/audio/AudioDeviceBroker.java
index 9cfcb16..8091753 100644
--- a/services/core/java/com/android/server/audio/AudioDeviceBroker.java
+++ b/services/core/java/com/android/server/audio/AudioDeviceBroker.java
@@ -2037,9 +2037,8 @@
                     } break;
 
                 case MSG_L_UPDATED_ADI_DEVICE_STATE:
-                    synchronized (mDeviceStateLock) {
-                        mAudioService.onUpdatedAdiDeviceState((AdiDeviceState) msg.obj);
-                    } break;
+                    mAudioService.onUpdatedAdiDeviceState((AdiDeviceState) msg.obj);
+                    break;
 
                 default:
                     Log.wtf(TAG, "Invalid message " + msg.what);
@@ -2687,11 +2686,15 @@
             return;
         }
         final SettingsAdapter settingsAdapter = mAudioService.getSettings();
-        boolean res = settingsAdapter.putSecureStringForUser(mAudioService.getContentResolver(),
-                Settings.Secure.AUDIO_DEVICE_INVENTORY,
-                deviceSettings, UserHandle.USER_CURRENT);
-        if (!res) {
-            Log.e(TAG, "error saving AdiDeviceState: " + deviceSettings);
+        try {
+            boolean res = settingsAdapter.putSecureStringForUser(mAudioService.getContentResolver(),
+                    Settings.Secure.AUDIO_DEVICE_INVENTORY,
+                    deviceSettings, UserHandle.USER_CURRENT);
+            if (!res) {
+                Log.e(TAG, "error saving AdiDeviceState: " + deviceSettings);
+            }
+        } catch (IllegalArgumentException e) {
+            Log.e(TAG, "error saving AdiDeviceState: " + deviceSettings, e);
         }
     }
 
diff --git a/services/core/java/com/android/server/audio/AudioDeviceInventory.java b/services/core/java/com/android/server/audio/AudioDeviceInventory.java
index e54bf64..34cfdfa 100644
--- a/services/core/java/com/android/server/audio/AudioDeviceInventory.java
+++ b/services/core/java/com/android/server/audio/AudioDeviceInventory.java
@@ -30,7 +30,6 @@
 import static android.media.AudioSystem.isBluetoothScoOutDevice;
 import static android.media.audio.Flags.automaticBtDeviceType;
 
-
 import static com.android.internal.annotations.VisibleForTesting.Visibility.PACKAGE;
 
 import android.annotation.NonNull;
@@ -81,7 +80,6 @@
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collection;
-import java.util.HashMap;
 import java.util.HashSet;
 import java.util.Iterator;
 import java.util.LinkedHashMap;
@@ -104,6 +102,14 @@
     private static final String SETTING_DEVICE_SEPARATOR_CHAR = "|";
     private static final String SETTING_DEVICE_SEPARATOR = "\\|";
 
+    /** Max String length that can be persisted within the Settings. */
+    // LINT.IfChange(settings_max_length_per_string)
+    private static final int MAX_SETTINGS_LENGTH_PER_STRING = 32768;
+    // LINT.ThenChange(/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java)
+
+    private static final int MAX_DEVICE_INVENTORY_ENTRIES =
+            MAX_SETTINGS_LENGTH_PER_STRING / AdiDeviceState.getPeristedMaxSize();
+
     // lock to synchronize all access to mConnectedDevices and mApmConnectedDevices
     private final Object mDevicesLock = new Object();
 
@@ -113,7 +119,8 @@
     private final Object mDeviceInventoryLock = new Object();
 
     @GuardedBy("mDeviceInventoryLock")
-    private final HashMap<Pair<Integer, String>, AdiDeviceState> mDeviceInventory = new HashMap<>();
+    private final LinkedHashMap<Pair<Integer, String>, AdiDeviceState> mDeviceInventory =
+            new LinkedHashMap<>();
 
     Collection<AdiDeviceState> getImmutableDeviceInventory() {
         final List<AdiDeviceState> newList;
@@ -136,6 +143,7 @@
                 oldState.setSAEnabled(newState.isSAEnabled());
                 return oldState;
             });
+            checkDeviceInventorySize_l();
         }
         mDeviceBroker.postSynchronizeAdiDevicesInInventory(deviceState);
     }
@@ -173,6 +181,8 @@
             ads.setAudioDeviceCategory(category);
 
             mDeviceInventory.put(ads.getDeviceId(), ads);
+            checkDeviceInventorySize_l();
+
             mDeviceBroker.postUpdatedAdiDeviceState(ads);
             mDeviceBroker.postPersistAudioDeviceSettings();
         }
@@ -200,6 +210,7 @@
                         }
                         return oldState;
                     });
+            checkDeviceInventorySize_l();
         }
         if (updatedCategory.get()) {
             mDeviceBroker.postUpdatedAdiDeviceState(deviceState);
@@ -272,6 +283,18 @@
         }
     }
 
+    @GuardedBy("mDeviceInventoryLock")
+    private void checkDeviceInventorySize_l() {
+        if (mDeviceInventory.size() > MAX_DEVICE_INVENTORY_ENTRIES) {
+            // remove the first element
+            Iterator<Entry<Pair<Integer, String>, AdiDeviceState>> iterator =
+                    mDeviceInventory.entrySet().iterator();
+            if (iterator.hasNext()) {
+                iterator.remove();
+            }
+        }
+    }
+
     @GuardedBy({"mDevicesLock", "mDeviceInventoryLock"})
     private boolean synchronizeBleDeviceInInventory(AdiDeviceState updatedDevice) {
         for (DeviceInfo di : mConnectedDevices.values()) {
@@ -1501,7 +1524,7 @@
             } else {
                 status = addOp.deviceRoleAction(useCase, role, devices);
                 if (status == AudioSystem.SUCCESS) {
-                    rolesMap.put(key, devices);
+                    rolesMap.put(key, new ArrayList(devices));
                 }
             }
             return status;
@@ -2806,7 +2829,7 @@
             deviceCatalogSize = mDeviceInventory.size();
 
             final StringBuilder settingsBuilder = new StringBuilder(
-                            deviceCatalogSize * AdiDeviceState.getPeristedMaxSize());
+                    deviceCatalogSize * AdiDeviceState.getPeristedMaxSize());
 
             Iterator<AdiDeviceState> iterator = mDeviceInventory.values().iterator();
             if (iterator.hasNext()) {
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index f7b7aaa..44cb136 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -13779,6 +13779,11 @@
         return mDeviceBroker.getDeviceAddresses(device);
     }
 
+    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
+    MusicFxHelper getMusicFxHelper() {
+        return mMusicFxHelper;
+    }
+
     //======================
     // misc
     //======================
diff --git a/services/core/java/com/android/server/audio/MusicFxHelper.java b/services/core/java/com/android/server/audio/MusicFxHelper.java
index 5f4e4c3..85b3b49 100644
--- a/services/core/java/com/android/server/audio/MusicFxHelper.java
+++ b/services/core/java/com/android/server/audio/MusicFxHelper.java
@@ -17,9 +17,11 @@
 package com.android.server.audio;
 
 import static android.content.pm.PackageManager.MATCH_ANY_USER;
+
 import static com.android.server.audio.AudioService.MUSICFX_HELPER_MSG_START;
 
 import android.annotation.NonNull;
+import android.annotation.RequiresPermission;
 import android.app.ActivityManager;
 import android.app.IUidObserver;
 import android.app.UidObserver;
@@ -48,7 +50,6 @@
 
 /**
  * MusicFx management.
- * .
  */
 public class MusicFxHelper {
     private static final String TAG = "AS.MusicFxHelper";
@@ -60,14 +61,113 @@
     // Synchronization UidSessionMap access between UidObserver and AudioServiceBroadcastReceiver.
     private final Object mClientUidMapLock = new Object();
 
+    private final String mPackageName = this.getClass().getPackage().getName();
+
+    private final String mMusicFxPackageName = "com.android.musicfx";
+
+    /*package*/ static final int MSG_EFFECT_CLIENT_GONE = MUSICFX_HELPER_MSG_START + 1;
+
     // The binder token identifying the UidObserver registration.
     private IBinder mUidObserverToken = null;
 
+    // Package name and list of open audio sessions for this package
+    private static class PackageSessions {
+        String mPackageName;
+        List<Integer> mSessions;
+    }
+
+    /*
+     * Override of SparseArray class to add bind/unbind and UID observer in the put/remove methods.
+     *
+     * put:
+     *  - the first key/value set put into MySparseArray will trigger a procState bump (bindService)
+     *  - if no valid observer token exist, will call registerUidObserver for put
+     *  - for each new uid put into array, it will be added to uid observer list
+     *
+     * remove:
+     *  - for each uid removed from array, it will be removed from uid observer list as well
+     *  - if it's the last uid in array, no more MusicFx procState bump (unbindService), uid
+     *    observer will also be removed, and observer token reset to null
+     */
+    private class MySparseArray extends SparseArray<PackageSessions> {
+        private final String mMusicFxPackageName = "com.android.musicfx";
+
+        @RequiresPermission(anyOf = {
+                android.Manifest.permission.INTERACT_ACROSS_USERS_FULL,
+                android.Manifest.permission.INTERACT_ACROSS_USERS,
+                android.Manifest.permission.INTERACT_ACROSS_PROFILES
+        })
+        @Override
+        public void put(int uid, PackageSessions pkgSessions) {
+            if (size() == 0) {
+                int procState = ActivityManager.PROCESS_STATE_NONEXISTENT;
+                try {
+                    procState = ActivityManager.getService().getPackageProcessState(
+                            mMusicFxPackageName, mPackageName);
+                } catch (RemoteException e) {
+                    Log.e(TAG, "RemoteException with getPackageProcessState: " + e);
+                }
+                if (procState > ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND) {
+                    Intent bindIntent = new Intent().setClassName(mMusicFxPackageName,
+                            "com.android.musicfx.KeepAliveService");
+                    mContext.bindServiceAsUser(
+                            bindIntent, mMusicFxBindConnection, Context.BIND_AUTO_CREATE,
+                            UserHandle.of(getCurrentUserId()));
+                    Log.i(TAG, "bindService to " + mMusicFxPackageName);
+                }
+
+                Log.i(TAG, mMusicFxPackageName + " procState " + procState);
+            }
+            try {
+                if (mUidObserverToken == null) {
+                    mUidObserverToken = ActivityManager.getService().registerUidObserverForUids(
+                            mEffectUidObserver, ActivityManager.UID_OBSERVER_GONE,
+                            ActivityManager.PROCESS_STATE_UNKNOWN, mPackageName,
+                            new int[]{uid});
+                    Log.i(TAG, "registered to observer with UID " + uid);
+                } else if (get(uid) == null) { // addUidToObserver if this is a new UID
+                    ActivityManager.getService().addUidToObserver(mUidObserverToken, mPackageName,
+                            uid);
+                    Log.i(TAG, " UID " + uid + " add to observer");
+                }
+            } catch (RemoteException e) {
+                Log.e(TAG, "RemoteException with UID observer add/register: " + e);
+            }
+
+            super.put(uid, pkgSessions);
+        }
+
+        @Override
+        public void remove(int uid) {
+            if (get(uid) != null) {
+                try {
+                    ActivityManager.getService().removeUidFromObserver(mUidObserverToken,
+                            mPackageName, uid);
+                } catch (RemoteException e) {
+                    Log.e(TAG, "RemoteException with removeUidFromObserver: " + e);
+                }
+            }
+
+            super.remove(uid);
+
+            // stop foreground service delegate and unregister UID observers with the last UID
+            if (size() == 0) {
+                try {
+                    ActivityManager.getService().unregisterUidObserver(mEffectUidObserver);
+                } catch (RemoteException e) {
+                    Log.e(TAG, "RemoteException with unregisterUidObserver: " + e);
+                }
+                mUidObserverToken = null;
+                mContext.unbindService(mMusicFxBindConnection);
+                Log.i(TAG, "last session closed, unregister UID observer, and unbind "
+                        + mMusicFxPackageName);
+            }
+        }
+    }
+
     // Hashmap of UID and list of open sessions for this UID.
     @GuardedBy("mClientUidMapLock")
-    private SparseArray<List<Integer>> mClientUidSessionMap = new SparseArray<>();
-
-    /*package*/ static final int MSG_EFFECT_CLIENT_GONE = MUSICFX_HELPER_MSG_START + 1;
+    private MySparseArray mClientUidSessionMap = new MySparseArray();
 
     // UID observer for effect MusicFx clients
     private final IUidObserver mEffectUidObserver = new UidObserver() {
@@ -102,23 +202,27 @@
      * Handle the broadcast {@link #ACTION_OPEN_AUDIO_EFFECT_CONTROL_SESSION} and
      * {@link #ACTION_CLOSE_AUDIO_EFFECT_CONTROL_SESSION} intents.
      *
+     * Only intents without target application package {@link android.content.Intent#getPackage}
+     * will be handled by the MusicFxHelper, all intents handled and forwarded by MusicFxHelper
+     * will have the target application package.
+     *
      * If the intent is {@link #ACTION_OPEN_AUDIO_EFFECT_CONTROL_SESSION}:
-     *  - If the MusicFx process is not running, call bindService with AUTO_CREATE to create.
-     *  - If this is the first audio session in MusicFx, call set foreground service delegate.
+     *  - If the MusicFx process is not running, call bindServiceAsUser with AUTO_CREATE to create.
+     *  - If this is the first audio session of MusicFx, call set foreground service delegate.
      *  - If this is the first audio session for a given UID, add the UID into observer.
      *
-     * If the intent is {@link #ACTION_CLOSE_AUDIO_EFFECT_CONTROL_SESSION}:
-     *  - MusicFx will not be foreground delegated anymore.
-     *  - The KeepAliveService of MusicFx will be unbound.
-     *  - The UidObserver will be removed.
+     * If the intent is {@link #ACTION_CLOSE_AUDIO_EFFECT_CONTROL_SESSION}
+     *  - The KeepAliveService of MusicFx will be unbound, and MusicFx will not be foreground
+     *    delegated anymore if the last session of the last package was closed.
+     *  - The Uid Observer will be removed when the last session of a package was closed.
      */
+    @RequiresPermission(allOf = {android.Manifest.permission.INTERACT_ACROSS_USERS})
     public void handleAudioEffectBroadcast(Context context, Intent intent) {
         String target = intent.getPackage();
         if (target != null) {
             Log.w(TAG, "effect broadcast already targeted to " + target);
             return;
         }
-        intent.addFlags(Intent.FLAG_INCLUDE_STOPPED_PACKAGES);
         final PackageManager pm = context.getPackageManager();
         // TODO this should target a user-selected panel
         List<ResolveInfo> ril = pm.queryBroadcastReceivers(intent, 0 /* flags */);
@@ -126,14 +230,14 @@
             ResolveInfo ri = ril.get(0);
             final String senderPackageName = intent.getStringExtra(AudioEffect.EXTRA_PACKAGE_NAME);
             try {
-                final int senderUid = pm.getPackageUidAsUser(senderPackageName,
-                        PackageManager.PackageInfoFlags.of(MATCH_ANY_USER), getCurrentUserId());
                 if (ri != null && ri.activityInfo != null && ri.activityInfo.packageName != null) {
+                    final int senderUid = pm.getPackageUidAsUser(senderPackageName,
+                            PackageManager.PackageInfoFlags.of(MATCH_ANY_USER), getCurrentUserId());
+                    intent.addFlags(Intent.FLAG_INCLUDE_STOPPED_PACKAGES);
                     intent.setPackage(ri.activityInfo.packageName);
-                    synchronized (mClientUidMapLock) {
-                        setMusicFxServiceWithObserver(context, intent, senderUid);
+                    if (setMusicFxServiceWithObserver(intent, senderUid, senderPackageName)) {
+                        context.sendBroadcastAsUser(intent, UserHandle.ALL);
                     }
-                    context.sendBroadcastAsUser(intent, UserHandle.ALL);
                     return;
                 }
             } catch (PackageManager.NameNotFoundException e) {
@@ -144,127 +248,105 @@
         Log.w(TAG, "couldn't find receiver package for effect intent");
     }
 
-    /**
-     * Handle the UidObserver onUidGone callback of MusicFx clients.
-     * All open audio sessions of this UID will be closed.
-     * If this is the last UID for MusicFx:
-     *  - MusicFx will not be foreground delegated anymore.
-     *  - The KeepAliveService of MusicFx will be unbound.
-     *  - The UidObserver will be removed.
-     */
-    public void handleEffectClientUidGone(int uid) {
-        synchronized (mClientUidMapLock) {
-            Log.w(TAG, " inside handle MSG_EFFECT_CLIENT_GONE");
-            // Once the uid is no longer running, close all remain audio session(s) for this UID
-            if (mClientUidSessionMap.get(Integer.valueOf(uid)) != null) {
-                final List<Integer> sessions =
-                        new ArrayList(mClientUidSessionMap.get(Integer.valueOf(uid)));
-                Log.i(TAG, "UID " + uid + " gone, closing " + sessions.size() + " sessions");
-                for (Integer session : sessions) {
-                    Intent intent = new Intent(
-                            AudioEffect.ACTION_CLOSE_AUDIO_EFFECT_CONTROL_SESSION);
-                    intent.putExtra(AudioEffect.EXTRA_AUDIO_SESSION, session);
-                    setMusicFxServiceWithObserver(mContext, intent, uid);
-                    Log.i(TAG, "Close session " + session + " of UID " + uid);
-                }
-                mClientUidSessionMap.remove(Integer.valueOf(uid));
+    @RequiresPermission(anyOf = {
+            android.Manifest.permission.INTERACT_ACROSS_USERS_FULL,
+            android.Manifest.permission.INTERACT_ACROSS_USERS,
+            android.Manifest.permission.INTERACT_ACROSS_PROFILES
+    })
+    @GuardedBy("mClientUidMapLock")
+    private boolean handleAudioEffectSessionOpen(
+            int senderUid, String senderPackageName, int sessionId) {
+        Log.d(TAG, senderPackageName + " UID " + senderUid + " open MusicFx session " + sessionId);
+
+        PackageSessions pkgSessions = mClientUidSessionMap.get(Integer.valueOf(senderUid));
+        if (pkgSessions != null && pkgSessions.mSessions != null) {
+            if (pkgSessions.mSessions.contains(sessionId)) {
+                Log.e(TAG, "Audio session " + sessionId + " already open for UID: "
+                        + senderUid + ", package: " + senderPackageName + ", abort");
+                return false;
             }
+            if (pkgSessions.mPackageName != senderPackageName) {
+                Log.w(TAG, "Inconsistency package names for UID open: " + senderUid + " prev: "
+                        + pkgSessions.mPackageName + ", now: " + senderPackageName);
+                return false;
+            }
+        } else {
+            // first session for this UID, create a new Package/Sessions pair
+            pkgSessions = new PackageSessions();
+            pkgSessions.mSessions = new ArrayList();
+            pkgSessions.mPackageName = senderPackageName;
         }
+
+        pkgSessions.mSessions.add(Integer.valueOf(sessionId));
+        mClientUidSessionMap.put(Integer.valueOf(senderUid), pkgSessions);
+        return true;
     }
 
+    @RequiresPermission(anyOf = {
+            android.Manifest.permission.INTERACT_ACROSS_USERS_FULL,
+            android.Manifest.permission.INTERACT_ACROSS_USERS,
+            android.Manifest.permission.INTERACT_ACROSS_PROFILES
+    })
     @GuardedBy("mClientUidMapLock")
-    private void setMusicFxServiceWithObserver(Context context, Intent intent, int senderUid) {
-        PackageManager pm = context.getPackageManager();
-        try {
-            final int audioSession = intent.getIntExtra(AudioEffect.EXTRA_AUDIO_SESSION,
-                    AudioManager.AUDIO_SESSION_ID_GENERATE);
-            if (AudioManager.AUDIO_SESSION_ID_GENERATE == audioSession) {
-                Log.e(TAG, "Intent missing audio session: " + audioSession);
-                return;
+    private boolean handleAudioEffectSessionClose(
+            int senderUid, String senderPackageName, int sessionId) {
+        Log.d(TAG, senderPackageName + " UID " + senderUid + " close MusicFx session " + sessionId);
+
+        PackageSessions pkgSessions = mClientUidSessionMap.get(Integer.valueOf(senderUid));
+        if (pkgSessions == null) {
+            Log.e(TAG, senderPackageName + " UID " + senderUid + " does not exist in map, abort");
+            return false;
+        }
+        if (pkgSessions.mPackageName != senderPackageName) {
+            Log.w(TAG, "Inconsistency package names for UID " + senderUid + " close, prev: "
+                    + pkgSessions.mPackageName + ", now: " + senderPackageName);
+            return false;
+        }
+
+        if (pkgSessions.mSessions != null && pkgSessions.mSessions.size() != 0) {
+            if (!pkgSessions.mSessions.contains(sessionId)) {
+                Log.e(TAG, senderPackageName + " UID " + senderUid + " session " + sessionId
+                        + " does not exist in map, abort");
+                return false;
             }
 
-            // only apply to com.android.musicfx and KeepAliveService for now
-            final String musicFxPackageName = "com.android.musicfx";
-            final String musicFxKeepAliveService = "com.android.musicfx.KeepAliveService";
-            final int musicFxUid = pm.getPackageUidAsUser(musicFxPackageName,
-                    PackageManager.PackageInfoFlags.of(MATCH_ANY_USER), getCurrentUserId());
+            pkgSessions.mSessions.remove(Integer.valueOf(sessionId));
+        }
 
+        if (pkgSessions.mSessions == null || pkgSessions.mSessions.size() == 0) {
+            // remove UID from map as well as the UID observer with the last session close
+            mClientUidSessionMap.remove(Integer.valueOf(senderUid));
+        } else {
+            mClientUidSessionMap.put(Integer.valueOf(senderUid), pkgSessions);
+        }
+
+        return true;
+    }
+
+    /**
+     * @return true if the intent is validated and handled successfully, false with any error
+     * (invalid sender/intent for example).
+     */
+    @RequiresPermission(anyOf = {
+            android.Manifest.permission.INTERACT_ACROSS_USERS_FULL,
+            android.Manifest.permission.INTERACT_ACROSS_USERS,
+            android.Manifest.permission.INTERACT_ACROSS_PROFILES
+    })
+    private boolean setMusicFxServiceWithObserver(
+            Intent intent, int senderUid, String packageName) {
+        final int session = intent.getIntExtra(AudioEffect.EXTRA_AUDIO_SESSION,
+                AudioManager.AUDIO_SESSION_ID_GENERATE);
+        if (AudioManager.AUDIO_SESSION_ID_GENERATE == session) {
+            Log.e(TAG, packageName + " intent have no invalid audio session");
+            return false;
+        }
+
+        synchronized (mClientUidMapLock) {
             if (intent.getAction().equals(AudioEffect.ACTION_OPEN_AUDIO_EFFECT_CONTROL_SESSION)) {
-                List<Integer> sessions = new ArrayList<>();
-                Log.d(TAG, "UID " + senderUid + ", open MusicFx session " + audioSession);
-                // start foreground service delegate and register UID observer with the first
-                // session of first UID open
-                if (0 == mClientUidSessionMap.size()) {
-                    final int procState = ActivityManager.getService().getPackageProcessState(
-                            musicFxPackageName, this.getClass().getPackage().getName());
-                    // if musicfx process not in binding state, call bindService with AUTO_CREATE
-                    if (procState > ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND) {
-                        Intent bindIntent = new Intent().setClassName(musicFxPackageName,
-                                musicFxKeepAliveService);
-                        context.bindServiceAsUser(
-                                bindIntent, mMusicFxBindConnection, Context.BIND_AUTO_CREATE,
-                                UserHandle.of(getCurrentUserId()));
-                        Log.i(TAG, "bindService to " + musicFxPackageName);
-                    }
-
-                    Log.i(TAG, "Package " + musicFxPackageName + " uid " + musicFxUid
-                            + " procState " + procState);
-                } else if (mClientUidSessionMap.get(Integer.valueOf(senderUid)) != null) {
-                    sessions = mClientUidSessionMap.get(Integer.valueOf(senderUid));
-                    if (sessions.contains(audioSession)) {
-                        Log.e(TAG, "Audio session " + audioSession + " already exist for UID "
-                                + senderUid + ", abort");
-                        return;
-                    }
-                }
-                // first session of this UID
-                if (sessions.size() == 0) {
-                    // call registerUidObserverForUids with the first UID and first session
-                    if (mClientUidSessionMap.size() == 0 || mUidObserverToken == null) {
-                        mUidObserverToken = ActivityManager.getService().registerUidObserverForUids(
-                                mEffectUidObserver, ActivityManager.UID_OBSERVER_GONE,
-                                ActivityManager.PROCESS_STATE_UNKNOWN, null, new int[]{senderUid});
-                        Log.i(TAG, "UID " + senderUid + " registered to observer");
-                    } else {
-                        // add UID to observer for each new UID
-                        ActivityManager.getService().addUidToObserver(mUidObserverToken, TAG,
-                                senderUid);
-                        Log.i(TAG, "UID " + senderUid + " addeded to observer");
-                    }
-                }
-
-                sessions.add(Integer.valueOf(audioSession));
-                mClientUidSessionMap.put(Integer.valueOf(senderUid), sessions);
+                return handleAudioEffectSessionOpen(senderUid, packageName, session);
             } else {
-                if (mClientUidSessionMap.get(senderUid) != null) {
-                    Log.d(TAG, "UID " + senderUid + ", close MusicFx session " + audioSession);
-                    List<Integer> sessions = mClientUidSessionMap.get(Integer.valueOf(senderUid));
-                    sessions.remove(Integer.valueOf(audioSession));
-                    if (0 == sessions.size()) {
-                        mClientUidSessionMap.remove(Integer.valueOf(senderUid));
-                    } else {
-                        mClientUidSessionMap.put(Integer.valueOf(senderUid), sessions);
-                    }
-
-                    // stop foreground service delegate and unregister UID observer with the
-                    // last session of last UID close
-                    if (0 == mClientUidSessionMap.size()) {
-                        ActivityManager.getService().unregisterUidObserver(mEffectUidObserver);
-                        mClientUidSessionMap.clear();
-                        context.unbindService(mMusicFxBindConnection);
-                        Log.i(TAG, " remove all sessions, unregister UID observer, and unbind "
-                                + musicFxPackageName);
-                    }
-                } else {
-                    // if the audio session already closed, print an error
-                    Log.e(TAG, "UID " + senderUid + " close audio session " + audioSession
-                            + " which does not exist");
-                }
+                return handleAudioEffectSessionClose(senderUid, packageName, session);
             }
-        } catch (PackageManager.NameNotFoundException e) {
-            Log.e(TAG, "Not able to find UID from package: " + e);
-        } catch (RemoteException e) {
-            Log.e(TAG, "RemoteException " + e + " with handling intent");
         }
     }
 
@@ -281,6 +363,39 @@
         return UserHandle.USER_SYSTEM;
     }
 
+
+    /**
+     * Handle the UidObserver onUidGone callback of MusicFx clients.
+     * Send close intent for all open audio sessions of this UID. The mClientUidSessionMap will be
+     * updated with the handling of close intent in setMusicFxServiceWithObserver.
+     */
+    @RequiresPermission(allOf = {android.Manifest.permission.INTERACT_ACROSS_USERS})
+    private void handleEffectClientUidGone(int uid) {
+        synchronized (mClientUidMapLock) {
+            Log.d(TAG, "handle MSG_EFFECT_CLIENT_GONE uid: " + uid + " mapSize: "
+                    + mClientUidSessionMap.size());
+            // Once the uid is no longer running, close all remain audio session(s) for this UID
+            final PackageSessions pkgSessions = mClientUidSessionMap.get(Integer.valueOf(uid));
+            if (pkgSessions != null) {
+                Log.i(TAG, "UID " + uid + " gone, closing all sessions");
+
+                // send close intent for each open session of the gone UID
+                for (Integer sessionId : pkgSessions.mSessions) {
+                    Intent closeIntent =
+                            new Intent(AudioEffect.ACTION_CLOSE_AUDIO_EFFECT_CONTROL_SESSION);
+                    closeIntent.putExtra(AudioEffect.EXTRA_PACKAGE_NAME, pkgSessions.mPackageName);
+                    closeIntent.putExtra(AudioEffect.EXTRA_AUDIO_SESSION, sessionId);
+                    closeIntent.addFlags(Intent.FLAG_INCLUDE_STOPPED_PACKAGES);
+                    // set broadcast target
+                    closeIntent.setPackage(mMusicFxPackageName);
+                    mContext.sendBroadcastAsUser(closeIntent, UserHandle.ALL);
+                }
+                mClientUidSessionMap.remove(Integer.valueOf(uid));
+            }
+        }
+    }
+
+    @RequiresPermission(allOf = {android.Manifest.permission.INTERACT_ACROSS_USERS})
     /*package*/ void handleMessage(Message msg) {
         switch (msg.what) {
             case MSG_EFFECT_CLIENT_GONE:
@@ -292,5 +407,4 @@
                 break;
         }
     }
-
 }
diff --git a/services/core/java/com/android/server/companion/virtual/VirtualDeviceManagerInternal.java b/services/core/java/com/android/server/companion/virtual/VirtualDeviceManagerInternal.java
index cecde55..823788f 100644
--- a/services/core/java/com/android/server/companion/virtual/VirtualDeviceManagerInternal.java
+++ b/services/core/java/com/android/server/companion/virtual/VirtualDeviceManagerInternal.java
@@ -21,6 +21,7 @@
 import android.companion.virtual.IVirtualDevice;
 import android.companion.virtual.VirtualDevice;
 import android.companion.virtual.sensor.VirtualSensor;
+import android.content.Context;
 import android.os.LocaleList;
 import android.util.ArraySet;
 
@@ -149,6 +150,14 @@
     public abstract @NonNull ArraySet<Integer> getDisplayIdsForDevice(int deviceId);
 
     /**
+     * Checks whether the passed {@code deviceId} is a valid virtual device ID or not.
+     *
+     * <p>{@link Context#DEVICE_ID_DEFAULT} is not valid as it is the ID of the default
+     * device which is not a virtual device.</p>
+     */
+    public abstract boolean isValidVirtualDeviceId(int deviceId);
+
+    /**
      * Returns the ID of the device which owns the display with the given ID.
      *
      * <p>In case the virtual display ID is invalid or doesn't belong to a virtual device, then
diff --git a/services/core/java/com/android/server/content/SyncManager.java b/services/core/java/com/android/server/content/SyncManager.java
index 3b7d80c..1e5e147 100644
--- a/services/core/java/com/android/server/content/SyncManager.java
+++ b/services/core/java/com/android/server/content/SyncManager.java
@@ -1292,7 +1292,11 @@
      */
     private boolean isPackageStopped(String packageName, int userId) {
         if (android.content.pm.Flags.stayStopped()) {
-            return mPackageManagerInternal.isPackageStopped(packageName, userId);
+            try {
+                return mPackageManagerInternal.isPackageStopped(packageName, userId);
+            } catch (IllegalArgumentException e) {
+                Log.d(TAG, "Couldn't determine stopped state for unknown package: " + packageName);
+            }
         }
         return false;
     }
diff --git a/services/core/java/com/android/server/inputmethod/HandwritingModeController.java b/services/core/java/com/android/server/inputmethod/HandwritingModeController.java
index dcb86a7..66807ae 100644
--- a/services/core/java/com/android/server/inputmethod/HandwritingModeController.java
+++ b/services/core/java/com/android/server/inputmethod/HandwritingModeController.java
@@ -23,7 +23,9 @@
 import android.annotation.RequiresPermission;
 import android.annotation.UiThread;
 import android.content.ComponentName;
+import android.content.Context;
 import android.content.pm.PackageManagerInternal;
+import android.hardware.input.InputManager;
 import android.hardware.input.InputManagerGlobal;
 import android.os.Handler;
 import android.os.IBinder;
@@ -64,6 +66,7 @@
     private static final int LONG_EVENT_BUFFER_SIZE = EVENT_BUFFER_SIZE * 20;
     private static final long HANDWRITING_DELEGATION_IDLE_TIMEOUT_MS = 3000;
 
+    private final Context mContext;
     // This must be the looper for the UiThread.
     private final Looper mLooper;
     private final InputManagerInternal mInputManagerInternal;
@@ -87,7 +90,9 @@
     private int mCurrentRequestId;
 
     @AnyThread
-    HandwritingModeController(Looper uiThreadLooper, Runnable inkWindowInitRunnable) {
+    HandwritingModeController(Context context, Looper uiThreadLooper,
+            Runnable inkWindowInitRunnable) {
+        mContext = context;
         mLooper = uiThreadLooper;
         mCurrentDisplayId = Display.INVALID_DISPLAY;
         mInputManagerInternal = LocalServices.getService(InputManagerInternal.class);
@@ -285,7 +290,14 @@
         mHandwritingSurface.startIntercepting(imePid, imeUid);
 
         // Unset the pointer icon for the stylus in case the app had set it.
-        InputManagerGlobal.getInstance().setPointerIconType(PointerIcon.TYPE_NOT_SPECIFIED);
+        if (com.android.input.flags.Flags.enablePointerChoreographer()) {
+            Objects.requireNonNull(mContext.getSystemService(InputManager.class)).setPointerIcon(
+                    PointerIcon.getSystemIcon(mContext, PointerIcon.TYPE_NOT_SPECIFIED),
+                    downEvent.getDisplayId(), downEvent.getDeviceId(), downEvent.getPointerId(0),
+                    mHandwritingSurface.getInputChannel().getToken());
+        } else {
+            InputManagerGlobal.getInstance().setPointerIconType(PointerIcon.TYPE_NOT_SPECIFIED);
+        }
 
         return new HandwritingSession(mCurrentRequestId, mHandwritingSurface.getInputChannel(),
                 mHandwritingBuffer);
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
index 09c388f..2d145c2 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
@@ -1713,7 +1713,7 @@
                 com.android.internal.R.bool.config_preventImeStartupUnlessTextEditor);
         mNonPreemptibleInputMethods = mRes.getStringArray(
                 com.android.internal.R.array.config_nonPreemptibleInputMethods);
-        mHwController = new HandwritingModeController(thread.getLooper(),
+        mHwController = new HandwritingModeController(mContext, thread.getLooper(),
                 new InkWindowInitializer());
         registerDeviceListenerAndCheckStylusSupport();
     }
@@ -5626,7 +5626,7 @@
         @Override
         public void setVirtualDeviceInputMethodForAllUsers(int deviceId, @Nullable String imeId) {
             // TODO(b/287269288): validate that id belongs to a valid virtual device instead.
-            Preconditions.checkArgument(deviceId == Context.DEVICE_ID_DEFAULT,
+            Preconditions.checkArgument(deviceId != Context.DEVICE_ID_DEFAULT,
                     "DeviceId " + deviceId + " does not belong to a virtual device.");
             synchronized (ImfLock.class) {
                 if (imeId == null) {
diff --git a/services/core/java/com/android/server/location/contexthub/IContextHubWrapper.java b/services/core/java/com/android/server/location/contexthub/IContextHubWrapper.java
index d359280..bab3cbe 100644
--- a/services/core/java/com/android/server/location/contexthub/IContextHubWrapper.java
+++ b/services/core/java/com/android/server/location/contexthub/IContextHubWrapper.java
@@ -424,6 +424,7 @@
             // 9a17008d-6bf1-445a-9011-6d21bd985b6c
             private static final byte[] UUID = {-102, 23, 0, -115, 107, -15, 68, 90,
                                                 -112, 17, 109, 33, -67, -104, 91, 108};
+            private static final String NAME = "ContextHubService";
 
             ContextHubAidlCallback(int contextHubId, ICallback callback) {
                 mContextHubId = contextHubId;
@@ -466,10 +467,14 @@
                 // TODO(271471342): Implement
             }
 
-            public byte[] getUuid() throws RemoteException {
+            public byte[] getUuid() {
                 return UUID;
             }
 
+            public String getName() {
+                return NAME;
+            }
+
             @Override
             public String getInterfaceHash() {
                 return android.hardware.contexthub.IContextHubCallback.HASH;
diff --git a/services/core/java/com/android/server/locksettings/LockSettingsService.java b/services/core/java/com/android/server/locksettings/LockSettingsService.java
index 49095ce..42c2548 100644
--- a/services/core/java/com/android/server/locksettings/LockSettingsService.java
+++ b/services/core/java/com/android/server/locksettings/LockSettingsService.java
@@ -1432,7 +1432,7 @@
     }
 
     private void unlockKeystore(int userId, SyntheticPassword sp) {
-        Authorization.onLockScreenEvent(false, userId, sp.deriveKeyStorePassword(), null);
+        Authorization.onDeviceUnlocked(userId, sp.deriveKeyStorePassword());
     }
 
     @VisibleForTesting /** Note: this method is overridden in unit tests */
diff --git a/services/core/java/com/android/server/media/AudioPoliciesBluetoothRouteController.java b/services/core/java/com/android/server/media/AudioPoliciesBluetoothRouteController.java
index a00999d..7cf3983 100644
--- a/services/core/java/com/android/server/media/AudioPoliciesBluetoothRouteController.java
+++ b/services/core/java/com/android/server/media/AudioPoliciesBluetoothRouteController.java
@@ -39,6 +39,7 @@
 
 import com.android.internal.R;
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.media.flags.Flags;
 
 import java.util.ArrayList;
 import java.util.HashMap;
@@ -219,7 +220,10 @@
         BluetoothRouteInfo
                 newBtRoute = new BluetoothRouteInfo();
         newBtRoute.mBtDevice = device;
-        String deviceName = device.getName();
+        String deviceName =
+                Flags.enableUseOfBluetoothDeviceGetAliasForMr2infoGetName()
+                        ? device.getAlias()
+                        : device.getName();
         if (TextUtils.isEmpty(deviceName)) {
             deviceName = mContext.getResources().getText(R.string.unknownName).toString();
         }
diff --git a/services/core/java/com/android/server/media/LegacyBluetoothRouteController.java b/services/core/java/com/android/server/media/LegacyBluetoothRouteController.java
index 041fceaf..ede2d27 100644
--- a/services/core/java/com/android/server/media/LegacyBluetoothRouteController.java
+++ b/services/core/java/com/android/server/media/LegacyBluetoothRouteController.java
@@ -43,6 +43,7 @@
 import android.util.SparseIntArray;
 
 import com.android.internal.R;
+import com.android.media.flags.Flags;
 
 import java.util.ArrayList;
 import java.util.HashMap;
@@ -283,7 +284,10 @@
         newBtRoute.mBtDevice = device;
 
         String routeId = device.getAddress();
-        String deviceName = device.getName();
+        String deviceName =
+                Flags.enableUseOfBluetoothDeviceGetAliasForMr2infoGetName()
+                        ? device.getAlias()
+                        : device.getName();
         if (TextUtils.isEmpty(deviceName)) {
             deviceName = mContext.getResources().getText(R.string.unknownName).toString();
         }
diff --git a/services/core/java/com/android/server/pm/AppsFilterImpl.java b/services/core/java/com/android/server/pm/AppsFilterImpl.java
index bdcec3a..82622d9 100644
--- a/services/core/java/com/android/server/pm/AppsFilterImpl.java
+++ b/services/core/java/com/android/server/pm/AppsFilterImpl.java
@@ -54,6 +54,7 @@
 import android.util.ArraySet;
 import android.util.Slog;
 import android.util.SparseBooleanArray;
+import android.util.SparseSetArray;
 
 import com.android.internal.R;
 import com.android.internal.annotations.GuardedBy;
@@ -1030,14 +1031,18 @@
     private void recomputeComponentVisibility(
             ArrayMap<String, ? extends PackageStateInternal> existingSettings) {
         final WatchedArraySet<String> protectedBroadcasts;
+        final WatchedArraySet<Integer> forceQueryable;
         synchronized (mProtectedBroadcastsLock) {
             protectedBroadcasts = mProtectedBroadcasts.snapshot();
         }
+        synchronized (mForceQueryableLock) {
+            forceQueryable = mForceQueryable.snapshot();
+        }
         final ParallelComputeComponentVisibility computer = new ParallelComputeComponentVisibility(
-                existingSettings, mForceQueryable, protectedBroadcasts);
+                existingSettings, forceQueryable, protectedBroadcasts);
+        SparseSetArray<Integer> queriesViaComponent = computer.execute();
         synchronized (mQueriesViaComponentLock) {
-            mQueriesViaComponent.clear();
-            computer.execute(mQueriesViaComponent);
+            mQueriesViaComponent.copyFrom(queriesViaComponent);
         }
 
         mQueriesViaComponentRequireRecompute.set(false);
diff --git a/services/core/java/com/android/server/pm/AppsFilterUtils.java b/services/core/java/com/android/server/pm/AppsFilterUtils.java
index f3f64c5..200734b 100644
--- a/services/core/java/com/android/server/pm/AppsFilterUtils.java
+++ b/services/core/java/com/android/server/pm/AppsFilterUtils.java
@@ -26,6 +26,7 @@
 import android.util.ArrayMap;
 import android.util.ArraySet;
 import android.util.Pair;
+import android.util.SparseSetArray;
 
 import com.android.internal.pm.pkg.component.ParsedComponent;
 import com.android.internal.pm.pkg.component.ParsedIntentInfo;
@@ -37,7 +38,6 @@
 import com.android.server.pm.pkg.PackageState;
 import com.android.server.pm.pkg.PackageStateInternal;
 import com.android.server.utils.WatchedArraySet;
-import com.android.server.utils.WatchedSparseSetArray;
 
 import java.util.ArrayList;
 import java.util.List;
@@ -213,7 +213,9 @@
         /**
          * Computes component visibility of all packages in parallel from a thread pool.
          */
-        void execute(@NonNull WatchedSparseSetArray<Integer> outQueriesViaComponent) {
+        @NonNull
+        SparseSetArray<Integer> execute() {
+            final SparseSetArray<Integer> queriesViaComponent = new SparseSetArray<>();
             final ExecutorService pool = ConcurrentUtils.newFixedThreadPool(
                     MAX_THREADS, ParallelComputeComponentVisibility.class.getSimpleName(),
                     THREAD_PRIORITY_DEFAULT);
@@ -239,7 +241,7 @@
                     try {
                         final ArraySet<Integer> visibleList = future.get();
                         if (visibleList.size() != 0) {
-                            outQueriesViaComponent.addAll(appId, visibleList);
+                            queriesViaComponent.addAll(appId, visibleList);
                         }
                     } catch (InterruptedException | ExecutionException e) {
                         throw new IllegalStateException(e);
@@ -248,6 +250,7 @@
             } finally {
                 pool.shutdownNow();
             }
+            return queriesViaComponent;
         }
 
         /**
diff --git a/services/core/java/com/android/server/pm/Computer.java b/services/core/java/com/android/server/pm/Computer.java
index 92d469c..c36c8ca 100644
--- a/services/core/java/com/android/server/pm/Computer.java
+++ b/services/core/java/com/android/server/pm/Computer.java
@@ -405,9 +405,19 @@
     boolean isInstallDisabledForPackage(@NonNull String packageName, int uid,
             @UserIdInt int userId);
 
-    @Nullable
-    List<VersionedPackage> getPackagesUsingSharedLibrary(@NonNull SharedLibraryInfo libInfo,
-            @PackageManager.PackageInfoFlagsBits long flags, int callingUid, @UserIdInt int userId);
+    /**
+     * Returns a Pair that contains a list of packages that depend on the target library and the
+     * package library dependency information. The List&lt;VersionedPackage&gt; indicates a list of
+     * packages that depend on the target library, it may be null if no package depends on
+     * the target library. The List&lt;Boolean&gt; indicates whether each VersionedPackage in
+     * the List&lt;VersionedPackage&gt; optionally depends on the target library, where true means
+     * optional and false means required. It may be null if no package depends on
+     * the target library or without dependency information, e.g. uses-static-library.
+     */
+    @NonNull
+    Pair<List<VersionedPackage>, List<Boolean>> getPackagesUsingSharedLibrary(
+            @NonNull SharedLibraryInfo libInfo, @PackageManager.PackageInfoFlagsBits long flags,
+            int callingUid, @UserIdInt int userId);
 
     @Nullable
     ParceledListSlice<SharedLibraryInfo> getDeclaredSharedLibraries(
diff --git a/services/core/java/com/android/server/pm/ComputerEngine.java b/services/core/java/com/android/server/pm/ComputerEngine.java
index 139c7c0..2ae1005 100644
--- a/services/core/java/com/android/server/pm/ComputerEngine.java
+++ b/services/core/java/com/android/server/pm/ComputerEngine.java
@@ -895,6 +895,9 @@
             @PackageManager.ResolveInfoFlagsBits long flags, int filterCallingUid, int userId) {
         ParsedActivity a = mComponentResolver.getActivity(component);
 
+        // Allow to match activities of quarantined packages.
+        flags |= PackageManager.MATCH_QUARANTINED_COMPONENTS;
+
         if (DEBUG_PACKAGE_INFO) Log.v(TAG, "getActivityInfo " + component + ": " + a);
 
         AndroidPackage pkg = a == null ? null : mPackages.get(a.getPackageName());
@@ -3862,11 +3865,13 @@
                     Binder.restoreCallingIdentity(identity);
                 }
 
+                var usingSharedLibraryPair =
+                        getPackagesUsingSharedLibrary(libInfo, flags, callingUid, userId);
                 SharedLibraryInfo resLibInfo = new SharedLibraryInfo(libInfo.getPath(),
                         libInfo.getPackageName(), libInfo.getAllCodePaths(),
                         libInfo.getName(), libInfo.getLongVersion(),
                         libInfo.getType(), declaringPackage,
-                        getPackagesUsingSharedLibrary(libInfo, flags, callingUid, userId),
+                        usingSharedLibraryPair.first,
                         (libInfo.getDependencies() == null
                                 ? null
                                 : new ArrayList<>(libInfo.getDependencies())),
@@ -3935,13 +3940,15 @@
         return false;
     }
 
+
     @Override
-    public List<VersionedPackage> getPackagesUsingSharedLibrary(@NonNull SharedLibraryInfo libInfo,
-            @PackageManager.PackageInfoFlagsBits long flags, int callingUid,
-            @UserIdInt int userId) {
+    public Pair<List<VersionedPackage>, List<Boolean>> getPackagesUsingSharedLibrary(
+            @NonNull SharedLibraryInfo libInfo, @PackageManager.PackageInfoFlagsBits long flags,
+            int callingUid, @UserIdInt int userId) {
         List<VersionedPackage> versionedPackages = null;
         final ArrayMap<String, ? extends PackageStateInternal> packageStates = getPackageStates();
         final int packageCount = packageStates.size();
+        List<Boolean> usesLibsOptional = null;
         for (int i = 0; i < packageCount; i++) {
             PackageStateInternal ps = packageStates.valueAt(i);
             if (ps == null) {
@@ -3958,12 +3965,15 @@
                         libInfo.isStatic() ? ps.getUsesStaticLibraries() : ps.getUsesSdkLibraries();
                 final long[] libsVersions = libInfo.isStatic() ? ps.getUsesStaticLibrariesVersions()
                         : ps.getUsesSdkLibrariesVersionsMajor();
+                final boolean[] libsOptional = libInfo.isSdk()
+                        ? ps.getUsesSdkLibrariesOptional() : null;
 
                 final int libIdx = ArrayUtils.indexOf(libs, libName);
                 if (libIdx < 0) {
                     continue;
                 }
                 if (libsVersions[libIdx] != libInfo.getLongVersion()) {
+                    // Not expected StaticLib/SdkLib version
                     continue;
                 }
                 if (shouldFilterApplication(ps, callingUid, userId)) {
@@ -3972,6 +3982,9 @@
                 if (versionedPackages == null) {
                     versionedPackages = new ArrayList<>();
                 }
+                if (usesLibsOptional == null) {
+                    usesLibsOptional = new ArrayList<>();
+                }
                 // If the dependent is a static shared lib, use the public package name
                 String dependentPackageName = ps.getPackageName();
                 if (ps.getPkg() != null && ps.getPkg().isStaticSharedLibrary()) {
@@ -3979,6 +3992,7 @@
                 }
                 versionedPackages.add(new VersionedPackage(dependentPackageName,
                         ps.getVersionCode()));
+                usesLibsOptional.add(libsOptional != null && libsOptional[libIdx]);
             } else if (ps.getPkg() != null) {
                 if (ArrayUtils.contains(ps.getPkg().getUsesLibraries(), libName)
                         || ArrayUtils.contains(ps.getPkg().getUsesOptionalLibraries(), libName)) {
@@ -3994,7 +4008,7 @@
             }
         }
 
-        return versionedPackages;
+        return new Pair<>(versionedPackages, usesLibsOptional);
     }
 
     @Nullable
@@ -4053,13 +4067,14 @@
                     Binder.restoreCallingIdentity(identity);
                 }
 
+                var usingSharedLibraryPair =
+                        getPackagesUsingSharedLibrary(libraryInfo, flags, callingUid, userId);
                 SharedLibraryInfo resultLibraryInfo = new SharedLibraryInfo(
                         libraryInfo.getPath(), libraryInfo.getPackageName(),
                         libraryInfo.getAllCodePaths(), libraryInfo.getName(),
                         libraryInfo.getLongVersion(), libraryInfo.getType(),
                         libraryInfo.getDeclaringPackage(),
-                        getPackagesUsingSharedLibrary(
-                                libraryInfo, flags, callingUid, userId),
+                        usingSharedLibraryPair.first,
                         libraryInfo.getDependencies() == null
                                 ? null : new ArrayList<>(libraryInfo.getDependencies()),
                         libraryInfo.isNative());
diff --git a/services/core/java/com/android/server/pm/DeletePackageHelper.java b/services/core/java/com/android/server/pm/DeletePackageHelper.java
index 80e6c83..93836266 100644
--- a/services/core/java/com/android/server/pm/DeletePackageHelper.java
+++ b/services/core/java/com/android/server/pm/DeletePackageHelper.java
@@ -18,7 +18,6 @@
 
 import static android.Manifest.permission.CONTROL_KEYGUARD;
 import static android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS;
-import static android.content.pm.Flags.sdkLibIndependence;
 import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DEFAULT;
 import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_ENABLED;
 import static android.content.pm.PackageManager.DELETE_KEEP_DATA;
@@ -40,6 +39,7 @@
 import android.app.ApplicationPackageManager;
 import android.content.Intent;
 import android.content.pm.ApplicationInfo;
+import android.content.pm.Flags;
 import android.content.pm.IPackageDeleteObserver2;
 import android.content.pm.PackageInstaller;
 import android.content.pm.PackageManager;
@@ -72,8 +72,6 @@
 
 import dalvik.system.VMRuntime;
 
-import java.util.List;
-
 /**
  * Deletes a package. Uninstall if installed, or at least deletes the base directory if it's called
  * from a failed installation. Fixes user state after deletion.
@@ -181,16 +179,36 @@
                 }
 
                 if (libraryInfo != null) {
+                    boolean flagSdkLibIndependence = Flags.sdkLibIndependence();
                     for (int currUserId : allUsers) {
                         if (removeUser != UserHandle.USER_ALL && removeUser != currUserId) {
                             continue;
                         }
-                        List<VersionedPackage> libClientPackages =
-                                computer.getPackagesUsingSharedLibrary(libraryInfo,
-                                        MATCH_KNOWN_PACKAGES, Process.SYSTEM_UID, currUserId);
-                        boolean allowSdkLibIndependence =
-                                (pkg.getSdkLibraryName() != null) && sdkLibIndependence();
-                        if (!ArrayUtils.isEmpty(libClientPackages) && !allowSdkLibIndependence) {
+                        var libClientPackagesPair = computer.getPackagesUsingSharedLibrary(
+                                libraryInfo, MATCH_KNOWN_PACKAGES, Process.SYSTEM_UID, currUserId);
+                        var libClientPackages = libClientPackagesPair.first;
+                        var libClientOptional = libClientPackagesPair.second;
+                        // We by default don't allow removing a package if the host lib is still be
+                        // used by other client packages
+                        boolean allowLibIndependence = false;
+                        // Only when the sdkLibIndependence flag is enabled we will respect the
+                        // "optional" attr in uses-sdk-library. Only allow to remove sdk-lib host
+                        // package if no required clients depend on it
+                        if ((pkg.getSdkLibraryName() != null)
+                                && !ArrayUtils.isEmpty(libClientPackages)
+                                && !ArrayUtils.isEmpty(libClientOptional)
+                                && (libClientPackages.size() == libClientOptional.size())
+                                && flagSdkLibIndependence) {
+                            allowLibIndependence = true;
+                            for (int i = 0; i < libClientPackages.size(); i++) {
+                                boolean usesSdkLibOptional = libClientOptional.get(i);
+                                if (!usesSdkLibOptional) {
+                                    allowLibIndependence = false;
+                                    break;
+                                }
+                            }
+                        }
+                        if (!ArrayUtils.isEmpty(libClientPackages) && !allowLibIndependence) {
                             Slog.w(TAG, "Not removing package " + pkg.getManifestPackageName()
                                     + " hosting lib " + libraryInfo.getName() + " version "
                                     + libraryInfo.getLongVersion() + " used by " + libClientPackages
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index f27c462..2880f84 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -7113,9 +7113,9 @@
                         if (info == null) {
                             continue;
                         }
-                        final List<VersionedPackage> dependents =
-                                computer.getPackagesUsingSharedLibrary(info, 0, Process.SYSTEM_UID,
-                                        userId);
+                        var usingSharedLibraryPair = computer.getPackagesUsingSharedLibrary(info, 0,
+                                Process.SYSTEM_UID, userId);
+                        final List<VersionedPackage> dependents = usingSharedLibraryPair.first;
                         if (dependents == null) {
                             continue;
                         }
diff --git a/services/core/java/com/android/server/pm/PackageSetting.java b/services/core/java/com/android/server/pm/PackageSetting.java
index 174df44..7d0a1f6 100644
--- a/services/core/java/com/android/server/pm/PackageSetting.java
+++ b/services/core/java/com/android/server/pm/PackageSetting.java
@@ -123,11 +123,15 @@
     @Nullable
     private Map<String, Set<String>> mimeGroups;
 
+    // TODO(b/314036181): encapsulate all these fields for usesSdk, instead of having three
+    //  separate arrays.
     @Nullable
     private String[] usesSdkLibraries;
 
     @Nullable
     private long[] usesSdkLibrariesVersionsMajor;
+    @Nullable
+    private boolean[] usesSdkLibrariesOptional;
 
     @Nullable
     private String[] usesStaticLibraries;
@@ -701,6 +705,9 @@
         usesSdkLibrariesVersionsMajor = other.usesSdkLibrariesVersionsMajor != null
                 ? Arrays.copyOf(other.usesSdkLibrariesVersionsMajor,
                 other.usesSdkLibrariesVersionsMajor.length) : null;
+        usesSdkLibrariesOptional = other.usesSdkLibrariesOptional != null
+                ? Arrays.copyOf(other.usesSdkLibrariesOptional,
+                other.usesSdkLibrariesOptional.length) : null;
 
         usesStaticLibraries = other.usesStaticLibraries != null
                 ? Arrays.copyOf(other.usesStaticLibraries,
@@ -1344,6 +1351,12 @@
 
     @NonNull
     @Override
+    public boolean[] getUsesSdkLibrariesOptional() {
+        return usesSdkLibrariesOptional == null ? EmptyArray.BOOLEAN : usesSdkLibrariesOptional;
+    }
+
+    @NonNull
+    @Override
     public String[] getUsesStaticLibraries() {
         return usesStaticLibraries == null ? EmptyArray.STRING : usesStaticLibraries;
     }
@@ -1444,6 +1457,12 @@
         return this;
     }
 
+    public PackageSetting setUsesSdkLibrariesOptional(boolean[] usesSdkLibrariesOptional) {
+        this.usesSdkLibrariesOptional = usesSdkLibrariesOptional;
+        onChanged();
+        return this;
+    }
+
     public PackageSetting setUsesStaticLibraries(String[] usesStaticLibraries) {
         this.usesStaticLibraries = usesStaticLibraries;
         onChanged();
diff --git a/services/core/java/com/android/server/pm/ScanPackageUtils.java b/services/core/java/com/android/server/pm/ScanPackageUtils.java
index 53b84e6..31a63e0 100644
--- a/services/core/java/com/android/server/pm/ScanPackageUtils.java
+++ b/services/core/java/com/android/server/pm/ScanPackageUtils.java
@@ -218,7 +218,8 @@
                     parsedPackage.getLongVersionCode(), pkgFlags, pkgPrivateFlags, user,
                     true /*allowInstall*/, instantApp, virtualPreload, isStoppedSystemApp,
                     UserManagerService.getInstance(), usesSdkLibraries,
-                    parsedPackage.getUsesSdkLibrariesVersionsMajor(), usesStaticLibraries,
+                    parsedPackage.getUsesSdkLibrariesVersionsMajor(),
+                    parsedPackage.getUsesSdkLibrariesOptional(), usesStaticLibraries,
                     parsedPackage.getUsesStaticLibrariesVersions(), parsedPackage.getMimeGroups(),
                     newDomainSetId,
                     parsedPackage.getTargetSdkVersion(), parsedPackage.getRestrictUpdateHash());
@@ -240,6 +241,7 @@
                     PackageInfoUtils.appInfoPrivateFlags(parsedPackage, pkgSetting),
                     UserManagerService.getInstance(),
                     usesSdkLibraries, parsedPackage.getUsesSdkLibrariesVersionsMajor(),
+                    parsedPackage.getUsesSdkLibrariesOptional(),
                     usesStaticLibraries, parsedPackage.getUsesStaticLibrariesVersions(),
                     parsedPackage.getMimeGroups(), newDomainSetId,
                     parsedPackage.getTargetSdkVersion(), parsedPackage.getRestrictUpdateHash());
diff --git a/services/core/java/com/android/server/pm/Settings.java b/services/core/java/com/android/server/pm/Settings.java
index 2cbf714..75d88da 100644
--- a/services/core/java/com/android/server/pm/Settings.java
+++ b/services/core/java/com/android/server/pm/Settings.java
@@ -106,6 +106,7 @@
 import com.android.server.backup.PreferredActivityBackupHelper;
 import com.android.server.pm.Installer.InstallerException;
 import com.android.server.pm.parsing.PackageInfoUtils;
+import com.android.server.pm.parsing.pkg.PackageImpl;
 import com.android.server.pm.permission.LegacyPermissionDataProvider;
 import com.android.server.pm.permission.LegacyPermissionSettings;
 import com.android.server.pm.permission.LegacyPermissionState;
@@ -339,6 +340,8 @@
     private static final String ATTR_DISTRACTION_FLAGS = "distraction_flags";
     private static final String ATTR_SUSPENDED = "suspended";
     private static final String ATTR_SUSPENDING_PACKAGE = "suspending-package";
+
+    private static final String ATTR_OPTIONAL = "optional";
     /**
      * @deprecated Legacy attribute, kept only for upgrading from P builds.
      */
@@ -942,6 +945,7 @@
             ret.setLongVersionCode(p.getVersionCode());
             ret.setUsesSdkLibraries(p.getUsesSdkLibraries());
             ret.setUsesSdkLibrariesVersionsMajor(p.getUsesSdkLibrariesVersionsMajor());
+            ret.setUsesSdkLibrariesOptional(p.getUsesSdkLibrariesOptional());
             ret.setUsesStaticLibraries(p.getUsesStaticLibraries());
             ret.setUsesStaticLibrariesVersions(p.getUsesStaticLibrariesVersions());
             ret.setMimeGroups(p.getMimeGroups());
@@ -1061,9 +1065,9 @@
             UserHandle installUser, boolean allowInstall, boolean instantApp,
             boolean virtualPreload, boolean isStoppedSystemApp, UserManagerService userManager,
             String[] usesSdkLibraries, long[] usesSdkLibrariesVersions,
-            String[] usesStaticLibraries, long[] usesStaticLibrariesVersions,
-            Set<String> mimeGroupNames, @NonNull UUID domainSetId,
-            int targetSdkVersion, byte[] restrictUpdatedHash) {
+            boolean[] usesSdkLibrariesOptional, String[] usesStaticLibraries,
+            long[] usesStaticLibrariesVersions, Set<String> mimeGroupNames,
+            @NonNull UUID domainSetId, int targetSdkVersion, byte[] restrictUpdatedHash) {
         final PackageSetting pkgSetting;
         if (originalPkg != null) {
             if (PackageManagerService.DEBUG_UPGRADE) Log.v(PackageManagerService.TAG, "Package "
@@ -1079,6 +1083,7 @@
                     .setLongVersionCode(versionCode)
                     .setUsesSdkLibraries(usesSdkLibraries)
                     .setUsesSdkLibrariesVersionsMajor(usesSdkLibrariesVersions)
+                    .setUsesSdkLibrariesOptional(usesSdkLibrariesOptional)
                     .setUsesStaticLibraries(usesStaticLibraries)
                     .setUsesStaticLibrariesVersions(usesStaticLibrariesVersions)
                     // Update new package state.
@@ -1096,6 +1101,7 @@
                     pkgPrivateFlags, domainSetId)
                     .setUsesSdkLibraries(usesSdkLibraries)
                     .setUsesSdkLibrariesVersionsMajor(usesSdkLibrariesVersions)
+                    .setUsesSdkLibrariesOptional(usesSdkLibrariesOptional)
                     .setUsesStaticLibraries(usesStaticLibraries)
                     .setUsesStaticLibrariesVersions(usesStaticLibrariesVersions)
                     .setLegacyNativeLibraryPath(legacyNativeLibraryPath)
@@ -1218,6 +1224,7 @@
             @Nullable String primaryCpuAbi, @Nullable String secondaryCpuAbi, int pkgFlags,
             int pkgPrivateFlags, @NonNull UserManagerService userManager,
             @Nullable String[] usesSdkLibraries, @Nullable long[] usesSdkLibrariesVersions,
+            @Nullable boolean[] usesSdkLibrariesOptional,
             @Nullable String[] usesStaticLibraries, @Nullable long[] usesStaticLibrariesVersions,
             @Nullable Set<String> mimeGroupNames, @NonNull UUID domainSetId,
             int targetSdkVersion, byte[] restrictUpdatedHash)
@@ -1277,12 +1284,17 @@
                 .setRestrictUpdateHash(restrictUpdatedHash);
         // Update SDK library dependencies if needed.
         if (usesSdkLibraries != null && usesSdkLibrariesVersions != null
-                && usesSdkLibraries.length == usesSdkLibrariesVersions.length) {
+                && usesSdkLibrariesOptional != null
+                && usesSdkLibraries.length == usesSdkLibrariesVersions.length
+                && usesSdkLibraries.length == usesSdkLibrariesOptional.length) {
             pkgSetting.setUsesSdkLibraries(usesSdkLibraries)
-                    .setUsesSdkLibrariesVersionsMajor(usesSdkLibrariesVersions);
+                    .setUsesSdkLibrariesVersionsMajor(usesSdkLibrariesVersions)
+                    .setUsesSdkLibrariesOptional(usesSdkLibrariesOptional);
         } else {
             pkgSetting.setUsesSdkLibraries(null)
-                    .setUsesSdkLibrariesVersionsMajor(null);
+                    .setUsesSdkLibrariesVersionsMajor(null)
+                    .setUsesSdkLibrariesOptional(null);
+
         }
 
         // Update static shared library dependencies if needed.
@@ -2537,12 +2549,15 @@
             throws IOException, XmlPullParserException {
         String libName = parser.getAttributeValue(null, ATTR_NAME);
         long libVersion = parser.getAttributeLong(null, ATTR_VERSION, -1);
+        boolean optional = parser.getAttributeBoolean(null, ATTR_OPTIONAL, true);
 
         if (libName != null && libVersion >= 0) {
             outPs.setUsesSdkLibraries(ArrayUtils.appendElement(String.class,
                     outPs.getUsesSdkLibraries(), libName));
             outPs.setUsesSdkLibrariesVersionsMajor(ArrayUtils.appendLong(
                     outPs.getUsesSdkLibrariesVersionsMajor(), libVersion));
+            outPs.setUsesSdkLibrariesOptional(PackageImpl.appendBoolean(
+                    outPs.getUsesSdkLibrariesOptional(), optional));
         }
 
         XmlUtils.skipCurrentTag(parser);
@@ -2564,7 +2579,7 @@
     }
 
     void writeUsesSdkLibLPw(TypedXmlSerializer serializer, String[] usesSdkLibraries,
-            long[] usesSdkLibraryVersions) throws IOException {
+            long[] usesSdkLibraryVersions, boolean[] usesSdkLibrariesOptional) throws IOException {
         if (ArrayUtils.isEmpty(usesSdkLibraries) || ArrayUtils.isEmpty(usesSdkLibraryVersions)
                 || usesSdkLibraries.length != usesSdkLibraryVersions.length) {
             return;
@@ -2573,9 +2588,11 @@
         for (int i = 0; i < libCount; i++) {
             final String libName = usesSdkLibraries[i];
             final long libVersion = usesSdkLibraryVersions[i];
+            boolean libOptional = usesSdkLibrariesOptional[i];
             serializer.startTag(null, TAG_USES_SDK_LIB);
             serializer.attribute(null, ATTR_NAME, libName);
             serializer.attributeLong(null, ATTR_VERSION, libVersion);
+            serializer.attributeBoolean(null, ATTR_OPTIONAL, libOptional);
             serializer.endTag(null, TAG_USES_SDK_LIB);
         }
     }
@@ -3106,7 +3123,8 @@
         }
 
         writeUsesSdkLibLPw(serializer, pkg.getUsesSdkLibraries(),
-                pkg.getUsesSdkLibrariesVersionsMajor());
+                pkg.getUsesSdkLibrariesVersionsMajor(), pkg.getUsesSdkLibrariesOptional());
+
 
         writeUsesStaticLibLPw(serializer, pkg.getUsesStaticLibraries(),
                 pkg.getUsesStaticLibrariesVersions());
@@ -3206,7 +3224,7 @@
         }
 
         writeUsesSdkLibLPw(serializer, pkg.getUsesSdkLibraries(),
-                pkg.getUsesSdkLibrariesVersionsMajor());
+                pkg.getUsesSdkLibrariesVersionsMajor(), pkg.getUsesSdkLibrariesOptional());
 
         writeUsesStaticLibLPw(serializer, pkg.getUsesStaticLibraries(),
                 pkg.getUsesStaticLibrariesVersions());
@@ -5091,12 +5109,14 @@
 
             List<String> usesSdkLibraries = pkg.getUsesSdkLibraries();
             long[] usesSdkLibrariesVersionsMajor = pkg.getUsesSdkLibrariesVersionsMajor();
+            boolean[] usesSdkLibrariesOptional = pkg.getUsesSdkLibrariesOptional();
             if (usesSdkLibraries.size() > 0) {
                 pw.print(prefix); pw.println("  usesSdkLibraries:");
                 for (int i = 0, size = usesSdkLibraries.size(); i < size; ++i) {
                     pw.print(prefix); pw.print("    ");
                     pw.print(usesSdkLibraries.get(i)); pw.print(" version:");
-                    pw.println(usesSdkLibrariesVersionsMajor[i]);
+                    pw.println(usesSdkLibrariesVersionsMajor[i]); pw.print(" optional:");
+                    pw.println(usesSdkLibrariesOptional[i]);
                 }
             }
 
diff --git a/services/core/java/com/android/server/pm/SharedLibrariesImpl.java b/services/core/java/com/android/server/pm/SharedLibrariesImpl.java
index 9384c13..94495bf 100644
--- a/services/core/java/com/android/server/pm/SharedLibrariesImpl.java
+++ b/services/core/java/com/android/server/pm/SharedLibrariesImpl.java
@@ -16,7 +16,6 @@
 
 package com.android.server.pm;
 
-import static android.content.pm.Flags.sdkLibIndependence;
 import static android.content.pm.PackageManager.INSTALL_FAILED_MISSING_SHARED_LIBRARY;
 import static android.content.pm.PackageManager.INSTALL_FAILED_SHARED_LIBRARY_BAD_CERTIFICATE_DIGEST;
 
@@ -28,6 +27,7 @@
 import android.annotation.Nullable;
 import android.compat.annotation.ChangeId;
 import android.compat.annotation.EnabledAfter;
+import android.content.pm.Flags;
 import android.content.pm.PackageManager;
 import android.content.pm.SharedLibraryInfo;
 import android.content.pm.Signature;
@@ -82,6 +82,8 @@
 public final class SharedLibrariesImpl implements SharedLibrariesRead, Watchable, Snappable {
     private static final boolean DEBUG_SHARED_LIBRARIES = false;
 
+    private static final String LIBRARY_TYPE_SDK = "sdk";
+
     /**
      * Apps targeting Android S and above need to declare dependencies to the public native
      * shared libraries that are defined by the device maker using {@code uses-native-library} tag
@@ -798,8 +800,9 @@
 
             // Remove the shared library overlays from its dependent packages.
             for (int currentUserId : mPm.mUserManager.getUserIds()) {
-                final List<VersionedPackage> dependents = snapshot.getPackagesUsingSharedLibrary(
-                        libraryInfo, 0, Process.SYSTEM_UID, currentUserId);
+                var usingSharedLibraryPair = snapshot.getPackagesUsingSharedLibrary(libraryInfo, 0,
+                        Process.SYSTEM_UID, currentUserId);
+                final List<VersionedPackage> dependents = usingSharedLibraryPair.first;
                 if (dependents == null) {
                     continue;
                 }
@@ -921,42 +924,43 @@
         // duplicates.
         ArrayList<SharedLibraryInfo> usesLibraryInfos = null;
         if (!pkg.getUsesLibraries().isEmpty()) {
-            usesLibraryInfos = collectSharedLibraryInfos(pkg.getUsesLibraries(), null, null,
+            usesLibraryInfos = collectSharedLibraryInfos(pkg.getUsesLibraries(), null, null, null,
                     pkg.getPackageName(), "shared", true, pkg.getTargetSdkVersion(), null,
                     availablePackages, newLibraries);
         }
         if (!pkg.getUsesStaticLibraries().isEmpty()) {
             usesLibraryInfos = collectSharedLibraryInfos(pkg.getUsesStaticLibraries(),
                     pkg.getUsesStaticLibrariesVersions(), pkg.getUsesStaticLibrariesCertDigests(),
-                    pkg.getPackageName(), "static shared", true, pkg.getTargetSdkVersion(),
-                    usesLibraryInfos, availablePackages, newLibraries);
+                    null, pkg.getPackageName(), "static shared", true,
+                    pkg.getTargetSdkVersion(), usesLibraryInfos, availablePackages, newLibraries);
         }
         if (!pkg.getUsesOptionalLibraries().isEmpty()) {
             usesLibraryInfos = collectSharedLibraryInfos(pkg.getUsesOptionalLibraries(), null, null,
-                    pkg.getPackageName(), "shared", false, pkg.getTargetSdkVersion(),
+                    null, pkg.getPackageName(), "shared", false, pkg.getTargetSdkVersion(),
                     usesLibraryInfos, availablePackages, newLibraries);
         }
         if (platformCompat.isChangeEnabledInternal(ENFORCE_NATIVE_SHARED_LIBRARY_DEPENDENCIES,
                 pkg.getPackageName(), pkg.getTargetSdkVersion())) {
             if (!pkg.getUsesNativeLibraries().isEmpty()) {
                 usesLibraryInfos = collectSharedLibraryInfos(pkg.getUsesNativeLibraries(), null,
-                        null, pkg.getPackageName(), "native shared", true,
+                        null, null, pkg.getPackageName(), "native shared", true,
                         pkg.getTargetSdkVersion(), usesLibraryInfos, availablePackages,
                         newLibraries);
             }
             if (!pkg.getUsesOptionalNativeLibraries().isEmpty()) {
                 usesLibraryInfos = collectSharedLibraryInfos(pkg.getUsesOptionalNativeLibraries(),
-                        null, null, pkg.getPackageName(), "native shared", false,
+                        null, null, null, pkg.getPackageName(), "native shared", false,
                         pkg.getTargetSdkVersion(), usesLibraryInfos, availablePackages,
                         newLibraries);
             }
         }
         if (!pkg.getUsesSdkLibraries().isEmpty()) {
             // Allow installation even if sdk-library dependency doesn't exist
-            boolean required = !sdkLibIndependence();
+            boolean required = !Flags.sdkLibIndependence();
             usesLibraryInfos = collectSharedLibraryInfos(pkg.getUsesSdkLibraries(),
                     pkg.getUsesSdkLibrariesVersionsMajor(), pkg.getUsesSdkLibrariesCertDigests(),
-                    pkg.getPackageName(), "sdk", required, pkg.getTargetSdkVersion(),
+                    pkg.getUsesSdkLibrariesOptional(),
+                    pkg.getPackageName(), LIBRARY_TYPE_SDK, required, pkg.getTargetSdkVersion(),
                     usesLibraryInfos, availablePackages, newLibraries);
         }
         return usesLibraryInfos;
@@ -965,6 +969,7 @@
     private ArrayList<SharedLibraryInfo> collectSharedLibraryInfos(
             @NonNull List<String> requestedLibraries,
             @Nullable long[] requiredVersions, @Nullable String[][] requiredCertDigests,
+            @Nullable boolean[] libsOptional,
             @NonNull String packageName, @NonNull String libraryType, boolean required,
             int targetSdk, @Nullable ArrayList<SharedLibraryInfo> outUsedLibraries,
             @NonNull final Map<String, AndroidPackage> availablePackages,
@@ -981,7 +986,10 @@
                         libName, libVersion, mSharedLibraries, newLibraries);
             }
             if (libraryInfo == null) {
-                if (required) {
+                // Only allow app be installed if the app specifies the sdk-library dependency is
+                // optional
+                if (required || (LIBRARY_TYPE_SDK.equals(libraryType) && (libsOptional != null
+                        && !libsOptional[i]))) {
                     throw new PackageManagerException(INSTALL_FAILED_MISSING_SHARED_LIBRARY,
                             "Package " + packageName + " requires unavailable " + libraryType
                                     + " library " + libName + "; failing!");
diff --git a/services/core/java/com/android/server/pm/SuspendPackageHelper.java b/services/core/java/com/android/server/pm/SuspendPackageHelper.java
index 71f6c0d..fe8c12c 100644
--- a/services/core/java/com/android/server/pm/SuspendPackageHelper.java
+++ b/services/core/java/com/android/server/pm/SuspendPackageHelper.java
@@ -33,7 +33,6 @@
 import android.os.Process;
 import android.os.UserHandle;
 import android.os.UserManager;
-import android.provider.DeviceConfig;
 import android.util.ArrayMap;
 import android.util.ArraySet;
 import android.util.IntArray;
@@ -504,10 +503,6 @@
             final String requiredPermissionControllerPackage =
                     getKnownPackageName(snapshot, KnownPackages.PACKAGE_PERMISSION_CONTROLLER,
                             userId);
-            final AppOpsManager appOpsManager = mInjector.getSystemService(AppOpsManager.class);
-            final boolean isSystemExemptFlagEnabled = DeviceConfig.getBoolean(
-                    DeviceConfig.NAMESPACE_PACKAGE_MANAGER_SERVICE,
-                    SYSTEM_EXEMPT_FROM_SUSPENSION, /* defaultValue= */ true);
             for (int i = 0; i < packageNames.length; i++) {
                 canSuspend[i] = false;
                 final String packageName = packageNames[i];
@@ -581,9 +576,7 @@
                                 + pkg.getStaticSharedLibraryName());
                         continue;
                     }
-                    if (isSystemExemptFlagEnabled && appOpsManager.checkOpNoThrow(
-                            AppOpsManager.OP_SYSTEM_EXEMPT_FROM_SUSPENSION, uid, packageName)
-                            == AppOpsManager.MODE_ALLOWED) {
+                    if (exemptFromSuspensionByAppOp(uid, packageName)) {
                         Slog.w(TAG, "Cannot suspend package \"" + packageName
                                 + "\": has OP_SYSTEM_EXEMPT_FROM_SUSPENSION set");
                         continue;
@@ -601,6 +594,13 @@
         return canSuspend;
     }
 
+    private boolean exemptFromSuspensionByAppOp(int uid, String packageName) {
+        final AppOpsManager appOpsManager = mInjector.getSystemService(AppOpsManager.class);
+        return appOpsManager.checkOpNoThrow(
+                AppOpsManager.OP_SYSTEM_EXEMPT_FROM_SUSPENSION, uid, packageName)
+                        == AppOpsManager.MODE_ALLOWED;
+    }
+
     /**
      * Suspends packages on behalf of an admin.
      *
diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java
index b53a21c..a7b52f4 100644
--- a/services/core/java/com/android/server/pm/UserManagerService.java
+++ b/services/core/java/com/android/server/pm/UserManagerService.java
@@ -1519,7 +1519,7 @@
 
         try {
             if (enableQuietMode) {
-                ActivityManager.getService().stopUser(userId, /* force= */ true, null);
+                stopUserForQuietMode(userId);
                 LocalServices.getService(ActivityManagerInternal.class)
                         .killForegroundAppsForUser(userId);
             } else {
@@ -1547,6 +1547,18 @@
         }
     }
 
+    private void stopUserForQuietMode(int userId) throws RemoteException {
+        if (android.os.Flags.allowPrivateProfile()
+                && android.multiuser.Flags.enableBiometricsToUnlockPrivateSpace()) {
+            // Allow delayed locking since some profile types want to be able to unlock again via
+            // biometrics.
+            ActivityManager.getService()
+                    .stopUserWithDelayedLocking(userId, /* force= */ true, null);
+            return;
+        }
+        ActivityManager.getService().stopUser(userId, /* force= */ true, null);
+    }
+
     private void logQuietModeEnabled(@UserIdInt int userId, boolean enableQuietMode,
             @Nullable String callingPackage) {
         Slogf.i(LOG_TAG,
diff --git a/services/core/java/com/android/server/pm/UserTypeFactory.java b/services/core/java/com/android/server/pm/UserTypeFactory.java
index 4ef8cb7..7386301 100644
--- a/services/core/java/com/android/server/pm/UserTypeFactory.java
+++ b/services/core/java/com/android/server/pm/UserTypeFactory.java
@@ -160,7 +160,9 @@
                                 UserProperties.SHOW_IN_SHARING_SURFACES_WITH_PARENT)
                         .setMediaSharedWithParent(true)
                         .setCredentialShareableWithParent(true)
-                        .setDeleteAppWithParent(true));
+                        .setDeleteAppWithParent(true)
+                        .setCrossProfileContentSharingStrategy(UserProperties
+                                .CROSS_PROFILE_CONTENT_SHARING_DELEGATE_FROM_PARENT));
     }
 
     /**
@@ -309,6 +311,7 @@
                         .setStartWithParent(true)
                         .setCredentialShareableWithParent(true)
                         .setAuthAlwaysRequiredToDisableQuietMode(true)
+                        .setAllowStoppingUserWithDelayedLocking(true)
                         .setMediaSharedWithParent(false)
                         .setShowInLauncher(UserProperties.SHOW_IN_LAUNCHER_SEPARATE)
                         .setShowInSettings(UserProperties.SHOW_IN_SETTINGS_SEPARATE)
@@ -318,7 +321,9 @@
                                 UserProperties.SHOW_IN_SHARING_SURFACES_SEPARATE)
                         .setCrossProfileIntentFilterAccessControl(
                                 UserProperties.CROSS_PROFILE_INTENT_FILTER_ACCESS_LEVEL_SYSTEM)
-                        .setInheritDevicePolicy(UserProperties.INHERIT_DEVICE_POLICY_FROM_PARENT));
+                        .setInheritDevicePolicy(UserProperties.INHERIT_DEVICE_POLICY_FROM_PARENT)
+                        .setCrossProfileContentSharingStrategy(
+                                UserProperties.CROSS_PROFILE_CONTENT_SHARING_DELEGATE_FROM_PARENT));
     }
 
     /**
diff --git a/services/core/java/com/android/server/pm/parsing/PackageInfoUtils.java b/services/core/java/com/android/server/pm/parsing/PackageInfoUtils.java
index d642018..b23dbee 100644
--- a/services/core/java/com/android/server/pm/parsing/PackageInfoUtils.java
+++ b/services/core/java/com/android/server/pm/parsing/PackageInfoUtils.java
@@ -266,17 +266,20 @@
         if ((flags & PackageManager.GET_ACTIVITIES) != 0) {
             final int N = pkg.getActivities().size();
             if (N > 0) {
+                // Allow to match activities of quarantined packages.
+                long aflags = flags | PackageManager.MATCH_QUARANTINED_COMPONENTS;
+
                 int num = 0;
                 final ActivityInfo[] res = new ActivityInfo[N];
                 for (int i = 0; i < N; i++) {
                     final ParsedActivity a = pkg.getActivities().get(i);
                     if (ComponentParseUtils.isMatch(state, pkgSetting.isSystem(), pkg.isEnabled(), a,
-                            flags)) {
+                            aflags)) {
                         if (PackageManager.APP_DETAILS_ACTIVITY_CLASS_NAME.equals(
                                 a.getName())) {
                             continue;
                         }
-                        res[num++] = generateActivityInfo(pkg, a, flags, state,
+                        res[num++] = generateActivityInfo(pkg, a, aflags, state,
                                 applicationInfo, userId, pkgSetting);
                     }
                 }
diff --git a/services/core/java/com/android/server/pm/parsing/pkg/PackageImpl.java b/services/core/java/com/android/server/pm/parsing/pkg/PackageImpl.java
index 85d95ea..da58d47 100644
--- a/services/core/java/com/android/server/pm/parsing/pkg/PackageImpl.java
+++ b/services/core/java/com/android/server/pm/parsing/pkg/PackageImpl.java
@@ -267,6 +267,8 @@
     @Nullable
     private String[][] usesSdkLibrariesCertDigests;
     @Nullable
+    private boolean[] usesSdkLibrariesOptional;
+    @Nullable
     @DataClass.ParcelWith(ForInternedString.class)
     private String sharedUserId;
     private int sharedUserLabel;
@@ -718,16 +720,33 @@
 
     @Override
     public PackageImpl addUsesSdkLibrary(String libraryName, long versionMajor,
-            String[] certSha256Digests) {
+            String[] certSha256Digests, boolean usesSdkLibrariesOptional) {
         this.usesSdkLibraries = CollectionUtils.add(this.usesSdkLibraries,
                 TextUtils.safeIntern(libraryName));
         this.usesSdkLibrariesVersionsMajor = ArrayUtils.appendLong(
                 this.usesSdkLibrariesVersionsMajor, versionMajor, true);
         this.usesSdkLibrariesCertDigests = ArrayUtils.appendElement(String[].class,
                 this.usesSdkLibrariesCertDigests, certSha256Digests, true);
+        this.usesSdkLibrariesOptional = appendBoolean(this.usesSdkLibrariesOptional,
+                usesSdkLibrariesOptional);
         return this;
     }
 
+    /**
+     * Adds value to given array if not already present, providing set-like
+     * behavior.
+     */
+    public static boolean[] appendBoolean(@Nullable boolean[] cur, boolean val) {
+        if (cur == null) {
+            return new boolean[] { val };
+        }
+        final int N = cur.length;
+        boolean[] ret = new boolean[N + 1];
+        System.arraycopy(cur, 0, ret, 0, N);
+        ret[N] = val;
+        return ret;
+    }
+
     @Override
     public PackageImpl addUsesStaticLibrary(String libraryName, long version,
             String[] certSha256Digests) {
@@ -1468,6 +1487,12 @@
     @Override
     public long[] getUsesSdkLibrariesVersionsMajor() { return usesSdkLibrariesVersionsMajor; }
 
+    @Nullable
+    @Override
+    public boolean[] getUsesSdkLibrariesOptional() {
+        return usesSdkLibrariesOptional;
+    }
+
     @NonNull
     @Override
     public List<String> getUsesStaticLibraries() {
@@ -3126,6 +3151,7 @@
                 dest.writeStringArray(this.usesSdkLibrariesCertDigests[index]);
             }
         }
+        dest.writeBooleanArray(this.usesSdkLibrariesOptional);
 
         sForInternedString.parcel(this.sharedUserId, dest, flags);
         dest.writeInt(this.sharedUserLabel);
@@ -3278,6 +3304,7 @@
                 }
             }
         }
+        this.usesSdkLibrariesOptional = in.createBooleanArray();
 
         this.sharedUserId = sForInternedString.unparcel(in);
         this.sharedUserLabel = in.readInt();
diff --git a/services/core/java/com/android/server/pm/pkg/PackageState.java b/services/core/java/com/android/server/pm/pkg/PackageState.java
index 10b59c7..a7ae4eb 100644
--- a/services/core/java/com/android/server/pm/pkg/PackageState.java
+++ b/services/core/java/com/android/server/pm/pkg/PackageState.java
@@ -322,6 +322,14 @@
     long[] getUsesSdkLibrariesVersionsMajor();
 
     /**
+     * @see R.styleable#AndroidManifestUsesSdkLibrary_optional
+     * @hide
+     */
+    @Immutable.Ignore
+    @NonNull
+    boolean[] getUsesSdkLibrariesOptional();
+
+    /**
      * @see R.styleable#AndroidManifestUsesStaticLibrary
      * @hide
      */
diff --git a/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackageUtils.java b/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackageUtils.java
index 722350a..aa0fb27 100644
--- a/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackageUtils.java
+++ b/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackageUtils.java
@@ -2598,6 +2598,8 @@
                     R.styleable.AndroidManifestUsesSdkLibrary_versionMajor, -1);
             String certSha256Digest = sa.getNonResourceString(R.styleable
                     .AndroidManifestUsesSdkLibrary_certDigest);
+            boolean optional =
+                    sa.getBoolean(R.styleable.AndroidManifestUsesSdkLibrary_optional, false);
 
             // Since an APK providing a static shared lib can only provide the lib - fail if
             // malformed
@@ -2641,7 +2643,8 @@
             System.arraycopy(additionalCertSha256Digests, 0, certSha256Digests,
                     1, additionalCertSha256Digests.length);
 
-            return input.success(pkg.addUsesSdkLibrary(lname, versionMajor, certSha256Digests));
+            return input.success(
+                    pkg.addUsesSdkLibrary(lname, versionMajor, certSha256Digests, optional));
         } finally {
             sa.recycle();
         }
diff --git a/services/core/java/com/android/server/power/ThermalManagerService.java b/services/core/java/com/android/server/power/ThermalManagerService.java
index 99653ae..24d7acd 100644
--- a/services/core/java/com/android/server/power/ThermalManagerService.java
+++ b/services/core/java/com/android/server/power/ThermalManagerService.java
@@ -759,6 +759,36 @@
                     case "NPU":
                         type = Temperature.TYPE_NPU;
                         break;
+                    case "TPU":
+                        type = Temperature.TYPE_TPU;
+                        break;
+                    case "DISPLAY":
+                        type = Temperature.TYPE_DISPLAY;
+                        break;
+                    case "MODEM":
+                        type = Temperature.TYPE_MODEM;
+                        break;
+                    case "SOC":
+                        type = Temperature.TYPE_SOC;
+                        break;
+                    case "WIFI":
+                        type = Temperature.TYPE_WIFI;
+                        break;
+                    case "CAMERA":
+                        type = Temperature.TYPE_CAMERA;
+                        break;
+                    case "FLASHLIGHT":
+                        type = Temperature.TYPE_FLASHLIGHT;
+                        break;
+                    case "SPEAKER":
+                        type = Temperature.TYPE_SPEAKER;
+                        break;
+                    case "AMBIENT":
+                        type = Temperature.TYPE_AMBIENT;
+                        break;
+                    case "POGO":
+                        type = Temperature.TYPE_POGO;
+                        break;
                     default:
                         pw.println("Invalid temperature type: " + typeName);
                         return -1;
diff --git a/services/core/java/com/android/server/power/stats/AggregatedPowerStatsConfig.java b/services/core/java/com/android/server/power/stats/AggregatedPowerStatsConfig.java
index 43fd15d..6fbbc0f 100644
--- a/services/core/java/com/android/server/power/stats/AggregatedPowerStatsConfig.java
+++ b/services/core/java/com/android/server/power/stats/AggregatedPowerStatsConfig.java
@@ -19,7 +19,6 @@
 import android.annotation.NonNull;
 import android.os.BatteryConsumer;
 
-import com.android.internal.os.MultiStateStats;
 import com.android.internal.os.PowerStats;
 
 import java.lang.annotation.Retention;
diff --git a/services/core/java/com/android/server/power/stats/AggregatedPowerStatsProcessor.java b/services/core/java/com/android/server/power/stats/AggregatedPowerStatsProcessor.java
index 5fd8ddf..7feb964 100644
--- a/services/core/java/com/android/server/power/stats/AggregatedPowerStatsProcessor.java
+++ b/services/core/java/com/android/server/power/stats/AggregatedPowerStatsProcessor.java
@@ -19,7 +19,6 @@
 import android.annotation.Nullable;
 import android.util.Log;
 
-import com.android.internal.os.MultiStateStats;
 import com.android.internal.os.PowerStats;
 
 import java.util.ArrayList;
diff --git a/services/core/java/com/android/server/power/stats/CpuPowerStatsCollector.java b/services/core/java/com/android/server/power/stats/CpuPowerStatsCollector.java
index 4442845..1af1271 100644
--- a/services/core/java/com/android/server/power/stats/CpuPowerStatsCollector.java
+++ b/services/core/java/com/android/server/power/stats/CpuPowerStatsCollector.java
@@ -78,7 +78,7 @@
     private final SparseArray<UidStats> mUidStats = new SparseArray<>();
     private boolean mIsPerUidTimeInStateSupported;
     private PowerStatsInternal mPowerStatsInternal;
-    private int[] mCpuEnergyConsumerIds;
+    private int[] mCpuEnergyConsumerIds = new int[0];
     private PowerStats.Descriptor mPowerStatsDescriptor;
     // Reusable instance
     private PowerStats mCpuPowerStats;
@@ -286,8 +286,6 @@
 
         if (mPowerStatsInternal != null) {
             readCpuEnergyConsumerIds();
-        } else {
-            mCpuEnergyConsumerIds = new int[0];
         }
 
         int cpuScalingStepCount = mCpuScalingPolicies.getScalingStepCount();
@@ -320,7 +318,6 @@
     private void readCpuEnergyConsumerIds() {
         EnergyConsumer[] energyConsumerInfo = mPowerStatsInternal.getEnergyConsumerInfo();
         if (energyConsumerInfo == null) {
-            mCpuEnergyConsumerIds = new int[0];
             return;
         }
 
diff --git a/core/java/com/android/internal/os/MultiStateStats.java b/services/core/java/com/android/server/power/stats/MultiStateStats.java
similarity index 99%
rename from core/java/com/android/internal/os/MultiStateStats.java
rename to services/core/java/com/android/server/power/stats/MultiStateStats.java
index ecfed53..9356950 100644
--- a/core/java/com/android/internal/os/MultiStateStats.java
+++ b/services/core/java/com/android/server/power/stats/MultiStateStats.java
@@ -14,11 +14,12 @@
  * limitations under the License.
  */
 
-package com.android.internal.os;
+package com.android.server.power.stats;
 
 import android.util.Slog;
 
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.os.LongArrayMultiStateCounter;
 import com.android.internal.util.Preconditions;
 import com.android.modules.utils.TypedXmlPullParser;
 import com.android.modules.utils.TypedXmlSerializer;
diff --git a/services/core/java/com/android/server/power/stats/PowerComponentAggregatedPowerStats.java b/services/core/java/com/android/server/power/stats/PowerComponentAggregatedPowerStats.java
index 0facb9c..1637022 100644
--- a/services/core/java/com/android/server/power/stats/PowerComponentAggregatedPowerStats.java
+++ b/services/core/java/com/android/server/power/stats/PowerComponentAggregatedPowerStats.java
@@ -21,7 +21,6 @@
 import android.util.IndentingPrintWriter;
 import android.util.SparseArray;
 
-import com.android.internal.os.MultiStateStats;
 import com.android.internal.os.PowerStats;
 import com.android.modules.utils.TypedXmlPullParser;
 import com.android.modules.utils.TypedXmlSerializer;
diff --git a/services/core/java/com/android/server/power/stats/PowerStatsExporter.java b/services/core/java/com/android/server/power/stats/PowerStatsExporter.java
index 1f6f113..c267b79 100644
--- a/services/core/java/com/android/server/power/stats/PowerStatsExporter.java
+++ b/services/core/java/com/android/server/power/stats/PowerStatsExporter.java
@@ -22,7 +22,6 @@
 import android.os.UidBatteryConsumer;
 import android.util.Slog;
 
-import com.android.internal.os.MultiStateStats;
 import com.android.internal.os.PowerStats;
 
 import java.util.ArrayList;
diff --git a/services/core/java/com/android/server/trust/TrustManagerService.java b/services/core/java/com/android/server/trust/TrustManagerService.java
index d172d3f..eac4fc0 100644
--- a/services/core/java/com/android/server/trust/TrustManagerService.java
+++ b/services/core/java/com/android/server/trust/TrustManagerService.java
@@ -865,21 +865,19 @@
             mDeviceLockedForUser.put(userId, locked);
         }
         if (changed) {
-            dispatchDeviceLocked(userId, locked);
-            Authorization.onLockScreenEvent(locked, userId, null,
-                    getBiometricSids(userId));
+            notifyTrustAgentsOfDeviceLockState(userId, locked);
+            notifyKeystoreOfDeviceLockState(userId, locked);
             // Also update the user's profiles who have unified challenge, since they
             // share the same unlocked state (see {@link #isDeviceLocked(int)})
             for (int profileHandle : mUserManager.getEnabledProfileIds(userId)) {
                 if (mLockPatternUtils.isManagedProfileWithUnifiedChallenge(profileHandle)) {
-                    Authorization.onLockScreenEvent(locked, profileHandle, null,
-                            getBiometricSids(profileHandle));
+                    notifyKeystoreOfDeviceLockState(profileHandle, locked);
                 }
             }
         }
     }
 
-    private void dispatchDeviceLocked(int userId, boolean isLocked) {
+    private void notifyTrustAgentsOfDeviceLockState(int userId, boolean isLocked) {
         for (int i = 0; i < mActiveAgents.size(); i++) {
             AgentInfo agent = mActiveAgents.valueAt(i);
             if (agent.userId == userId) {
@@ -892,6 +890,17 @@
         }
     }
 
+    private void notifyKeystoreOfDeviceLockState(int userId, boolean isLocked) {
+        if (isLocked) {
+            Authorization.onDeviceLocked(userId, getBiometricSids(userId));
+        } else {
+            // Notify Keystore that the device is now unlocked for the user.  Note that for unlocks
+            // with LSKF, this is redundant with the call from LockSettingsService which provides
+            // the password.  However, for unlocks with biometric or trust agent, this is required.
+            Authorization.onDeviceUnlocked(userId, /* password= */ null);
+        }
+    }
+
     private void dispatchEscrowTokenActivatedLocked(long handle, int userId) {
         for (int i = 0; i < mActiveAgents.size(); i++) {
             AgentInfo agent = mActiveAgents.valueAt(i);
@@ -1425,10 +1434,10 @@
         }
     }
 
-    private long[] getBiometricSids(int userId) {
+    private @NonNull long[] getBiometricSids(int userId) {
         BiometricManager biometricManager = mContext.getSystemService(BiometricManager.class);
         if (biometricManager == null) {
-            return null;
+            return new long[0];
         }
         return biometricManager.getAuthenticatorIds(userId);
     }
@@ -1680,8 +1689,7 @@
                         mDeviceLockedForUser.put(userId, locked);
                     }
 
-                    Authorization.onLockScreenEvent(locked, userId, null,
-                            getBiometricSids(userId));
+                    notifyKeystoreOfDeviceLockState(userId, locked);
 
                     if (locked) {
                         try {
diff --git a/services/core/java/com/android/server/tv/TvInputManagerService.java b/services/core/java/com/android/server/tv/TvInputManagerService.java
index e4c7fc1..4c4dafa 100644
--- a/services/core/java/com/android/server/tv/TvInputManagerService.java
+++ b/services/core/java/com/android/server/tv/TvInputManagerService.java
@@ -1851,14 +1851,14 @@
                             sessionState.currentChannel = channelUri;
                             notifyCurrentChannelInfosUpdatedLocked(userState);
                             if (!sessionState.isRecordingSession) {
-                                String actualInputId = getActualInputId(sessionState);
-                                if (!TextUtils.equals(mOnScreenInputId, actualInputId)) {
+                                String sessionActualInputId = getSessionActualInputId(sessionState);
+                                if (!TextUtils.equals(mOnScreenInputId, sessionActualInputId)) {
                                     logExternalInputEvent(
                                             FrameworkStatsLog
                                                     .EXTERNAL_TV_INPUT_EVENT__EVENT_TYPE__TUNED,
-                                            actualInputId, sessionState);
+                                            sessionState.inputId, sessionState);
                                 }
-                                mOnScreenInputId = actualInputId;
+                                mOnScreenInputId = sessionActualInputId;
                                 mOnScreenSessionState = sessionState;
                             }
                         }
@@ -2985,11 +2985,20 @@
     // e.g. if an HDMI port has a CEC device plugged in, the actual input id of the HDMI
     // session should be the input id of CEC device instead of the default HDMI input id.
     @GuardedBy("mLock")
-    private String getActualInputId(SessionState sessionState) {
+    private String getSessionActualInputId(SessionState sessionState) {
         UserState userState = getOrCreateUserStateLocked(sessionState.userId);
         TvInputState tvInputState = userState.inputMap.get(sessionState.inputId);
+        if (tvInputState == null) {
+            Slog.w(TAG, "No TvInputState for sessionState.inputId " + sessionState.inputId);
+            return sessionState.inputId;
+        }
         TvInputInfo tvInputInfo = tvInputState.info;
-        String actualInputId = sessionState.inputId;
+        if (tvInputInfo == null) {
+            Slog.w(TAG, "TvInputInfo is null for input id " + sessionState.inputId);
+            return sessionState.inputId;
+        }
+
+        String sessionActualInputId = sessionState.inputId;
         switch (tvInputInfo.getType()) {
             case TvInputInfo.TYPE_HDMI:
                 // TODO: find a better approach towards active CEC device in future
@@ -2997,13 +3006,13 @@
                         mTvInputHardwareManager.getHdmiParentInputMap();
                 if (hdmiParentInputMap.containsKey(sessionState.inputId)) {
                     List<String> parentInputList = hdmiParentInputMap.get(sessionState.inputId);
-                    actualInputId = parentInputList.get(0);
+                    sessionActualInputId = parentInputList.get(0);
                 }
                 break;
             default:
                 break;
         }
-        return actualInputId;
+        return sessionActualInputId;
     }
 
     @Nullable
@@ -3110,8 +3119,22 @@
     @GuardedBy("mLock")
     private void logExternalInputEvent(int eventType, String inputId, SessionState sessionState) {
         UserState userState = getOrCreateUserStateLocked(sessionState.userId);
-        TvInputState tvInputState = userState.inputMap.get(inputId);
+        // Try finding the actual input id in inputMap. If not found, find the input id as is.
+        String inputIdForLogging = getSessionActualInputId(sessionState);
+        TvInputState tvInputState = userState.inputMap.get(inputIdForLogging);
+        if (tvInputState == null) {
+            inputIdForLogging = inputId;
+            tvInputState = userState.inputMap.get(inputIdForLogging);
+        }
+        if (tvInputState == null) {
+            Slog.w(TAG, "Cannot find input state for input id " + inputIdForLogging);
+            return;
+        }
         TvInputInfo tvInputInfo = tvInputState.info;
+        if (tvInputInfo == null) {
+            Slog.w(TAG, "TvInputInfo is null for input id " + inputIdForLogging);
+            return;
+        }
         int inputState = tvInputState.state;
         int inputType = tvInputInfo.getType();
         String displayName = tvInputInfo.loadLabel(mContext).toString();
@@ -3647,14 +3670,14 @@
                         mSessionState.currentChannel = channelUri;
                         notifyCurrentChannelInfosUpdatedLocked(userState);
                         if (!mSessionState.isRecordingSession) {
-                            String actualInputId = getActualInputId(mSessionState);
-                            if (!TextUtils.equals(mOnScreenInputId, actualInputId)) {
+                            String sessionActualInputId = getSessionActualInputId(mSessionState);
+                            if (!TextUtils.equals(mOnScreenInputId, sessionActualInputId)) {
                                 logExternalInputEvent(
                                         FrameworkStatsLog
                                                 .EXTERNAL_TV_INPUT_EVENT__EVENT_TYPE__TUNED,
-                                        actualInputId, mSessionState);
+                                        mSessionState.inputId, mSessionState);
                             }
-                            mOnScreenInputId = actualInputId;
+                            mOnScreenInputId = sessionActualInputId;
                             mOnScreenSessionState = mSessionState;
                         }
                     }
diff --git a/services/core/java/com/android/server/uri/UriGrantsManagerService.java b/services/core/java/com/android/server/uri/UriGrantsManagerService.java
index 7862f58..e501b9d 100644
--- a/services/core/java/com/android/server/uri/UriGrantsManagerService.java
+++ b/services/core/java/com/android/server/uri/UriGrantsManagerService.java
@@ -146,17 +146,31 @@
             mGrantedUriPermissions = new SparseArray<>();
 
     private UriGrantsManagerService() {
-        this(SystemServiceManager.ensureSystemDir());
+        this(SystemServiceManager.ensureSystemDir(), "uri-grants");
     }
 
-    private UriGrantsManagerService(File systemDir) {
+    private UriGrantsManagerService(File systemDir, String commitTag) {
         mH = new H(IoThread.get().getLooper());
-        mGrantFile = new AtomicFile(new File(systemDir, "urigrants.xml"), "uri-grants");
+        final File file = new File(systemDir, "urigrants.xml");
+        mGrantFile = (commitTag != null) ? new AtomicFile(file, commitTag) : new AtomicFile(file);
     }
 
     @VisibleForTesting
     static UriGrantsManagerService createForTest(File systemDir) {
-        final UriGrantsManagerService service = new UriGrantsManagerService(systemDir);
+        final UriGrantsManagerService service = new UriGrantsManagerService(systemDir, null) {
+            @VisibleForTesting
+            protected int checkUidPermission(String permission, int uid) {
+                // Tests have no permission granted
+                return PackageManager.PERMISSION_DENIED;
+            }
+
+            @VisibleForTesting
+            protected int checkComponentPermission(String permission, int uid, int owningUid,
+                    boolean exported) {
+                // Tests have no permission granted
+                return PackageManager.PERMISSION_DENIED;
+            }
+        };
         service.mAmInternal = LocalServices.getService(ActivityManagerInternal.class);
         service.mPmInternal = LocalServices.getService(PackageManagerInternal.class);
         return service;
@@ -202,7 +216,8 @@
         }
     }
 
-    private int checkUidPermission(String permission, int uid) {
+    @VisibleForTesting
+    protected int checkUidPermission(String permission, int uid) {
         try {
             return AppGlobals.getPackageManager().checkUidPermission(permission, uid);
         } catch (RemoteException e) {
@@ -210,6 +225,12 @@
         }
     }
 
+    @VisibleForTesting
+    protected int checkComponentPermission(String permission, int uid, int owningUid,
+            boolean exported) {
+        return ActivityManager.checkComponentPermission(permission, uid, owningUid, exported);
+    }
+
     /**
      * Grant uri permissions to the specified app.
      *
@@ -916,7 +937,7 @@
             ProviderInfo pi, GrantUri grantUri, int uid, final int modeFlags) {
         if (DEBUG) Slog.v(TAG, "checkHoldingPermissions: uri=" + grantUri + " uid=" + uid);
         if (UserHandle.getUserId(uid) != grantUri.sourceUserId) {
-            if (ActivityManager.checkComponentPermission(INTERACT_ACROSS_USERS, uid, -1, true)
+            if (checkComponentPermission(INTERACT_ACROSS_USERS, uid, -1, true)
                     != PERMISSION_GRANTED) {
                 return false;
             }
@@ -1340,7 +1361,7 @@
         if (uid == Process.SYSTEM_UID || uid == Process.ROOT_UID) {
             return true;
         }
-        return ActivityManager.checkComponentPermission(
+        return checkComponentPermission(
                     android.Manifest.permission.INTERACT_ACROSS_USERS_FULL,
                     uid, /* owningUid = */-1, /* exported = */ true)
                     == PackageManager.PERMISSION_GRANTED;
diff --git a/services/core/java/com/android/server/utils/AnrTimer.java b/services/core/java/com/android/server/utils/AnrTimer.java
index 2b6dffb..7b5192c 100644
--- a/services/core/java/com/android/server/utils/AnrTimer.java
+++ b/services/core/java/com/android/server/utils/AnrTimer.java
@@ -16,37 +16,22 @@
 
 package com.android.server.utils;
 
-import static android.text.TextUtils.formatSimple;
-
-import android.annotation.IntDef;
 import android.annotation.NonNull;
-import android.annotation.Nullable;
 import android.os.Handler;
-import android.os.Looper;
 import android.os.Message;
-import android.os.Process;
 import android.os.SystemClock;
 import android.os.Trace;
-import android.text.TextUtils;
 import android.text.format.TimeMigrationUtils;
-import android.util.ArrayMap;
 import android.util.IndentingPrintWriter;
 import android.util.Log;
-import android.util.MathUtils;
-import android.util.SparseArray;
 
 import com.android.internal.annotations.GuardedBy;
-import com.android.internal.annotations.Keep;
 import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.os.ProcessCpuTracker;
 import com.android.internal.util.RingBuffer;
 
 import java.io.PrintWriter;
-import java.lang.ref.WeakReference;
-import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Objects;
-import java.util.concurrent.atomic.AtomicInteger;
 
 /**
  * This class managers AnrTimers.  An AnrTimer is a substitute for a delayed Message.  In legacy
@@ -102,12 +87,6 @@
     private static final long TRACE_TAG = Trace.TRACE_TAG_ACTIVITY_MANAGER;
 
     /**
-     * Enable tracing from the time a timer expires until it is accepted or discarded.  This is
-     * used to diagnose long latencies in the client.
-     */
-    private static final boolean ENABLE_TRACING = false;
-
-    /**
      * Return true if the feature is enabled.  By default, the value is take from the Flags class
      * but it can be changed for local testing.
      */
@@ -116,22 +95,13 @@
     }
 
     /**
-     * The status of an ANR timer.  TIMER_INVALID status is returned when an error is detected.
+     * This class allows test code to provide instance-specific overrides.
      */
-    private static final int TIMER_INVALID = 0;
-    private static final int TIMER_RUNNING = 1;
-    private static final int TIMER_EXPIRED = 2;
-
-    @IntDef(prefix = { "TIMER_" }, value = {
-                TIMER_INVALID, TIMER_RUNNING, TIMER_EXPIRED
-            })
-    private @interface TimerStatus {}
-
-    /**
-     * A static list of all known AnrTimer instances, used for dumping and testing.
-     */
-    @GuardedBy("sAnrTimerList")
-    private static final ArrayList<WeakReference<AnrTimer>> sAnrTimerList = new ArrayList<>();
+    static class Injector {
+        boolean anrTimerServiceEnabled() {
+            return AnrTimer.anrTimerServiceEnabled();
+        }
+    }
 
     /**
      * An error is defined by its issue, the operation that detected the error, the tag of the
@@ -161,6 +131,22 @@
             this.arg = arg;
             this.timestamp = SystemClock.elapsedRealtime();
         }
+
+        /**
+         * Dump a single error to the output stream.
+         */
+        private void dump(IndentingPrintWriter ipw, int seq) {
+            ipw.format("%2d: op:%s tag:%s issue:%s arg:%s\n", seq, operation, tag, issue, arg);
+
+            final long offset = System.currentTimeMillis() - SystemClock.elapsedRealtime();
+            final long etime = offset + timestamp;
+            ipw.println("    date:" + TimeMigrationUtils.formatMillisWithFixedFormat(etime));
+            ipw.increaseIndent();
+            for (int i = 0; i < stack.length; i++) {
+                ipw.println("    " + stack[i].toString());
+            }
+            ipw.decreaseIndent();
+        }
     }
 
     /**
@@ -171,132 +157,10 @@
     @GuardedBy("sErrors")
     private static final RingBuffer<Error> sErrors = new RingBuffer<>(Error.class, 20);
 
-    /**
-     * A record of a single anr timer.  The pid and uid are retained for reference but they do not
-     * participate in the equality tests. A {@link Timer} is bound to its parent {@link AnrTimer}
-     * through the owner field.  Access to timer fields is guarded by the mLock of the owner.
-     */
-    private static class Timer {
-        /** The AnrTimer that is managing this Timer. */
-        final AnrTimer owner;
-
-        /** The argument that uniquely identifies the Timer in the context of its current owner. */
-        final Object arg;
-        /** The pid of the process being tracked by this Timer. */
-        final int pid;
-        /** The uid of the process being tracked by this Timer as reported by the kernel. */
-        final int uid;
-        /** The original timeout. */
-        final long timeoutMs;
-
-        /** The status of the Timer.  */
-        @GuardedBy("owner.mLock")
-        @TimerStatus
-        int status;
-
-        /** The absolute time the timer was startd */
-        final long startedMs;
-
-        /** Fields used by the native timer service. */
-
-        /** The timer ID: used to exchange information with the native service. */
-        int timerId;
-
-        /** Fields used by the legacy timer service. */
-
-        /**
-         * The process's cpu delay time when the timer starts . It is meaningful only if
-         * extendable is true.  The cpu delay is cumulative, so the incremental delay that occurs
-         * during a timer is the delay at the end of the timer minus this value.  Units are in
-         * milliseconds.
-         */
-        @GuardedBy("owner.mLock")
-        long initialCpuDelayMs;
-
-        /** True if the timer has been extended. */
-        @GuardedBy("owner.mLock")
-        boolean extended;
-
-        /**
-         * Fetch a new Timer.  This is private.  Clients should get a new timer using the obtain()
-         * method.
-         */
-        private Timer(int pid, int uid, @Nullable Object arg, long timeoutMs,
-                @NonNull AnrTimer service) {
-            this.arg = arg;
-            this.pid = pid;
-            this.uid = uid;
-            this.timerId = 0;
-            this.timeoutMs = timeoutMs;
-            this.startedMs = now();
-            this.owner = service;
-            this.initialCpuDelayMs = 0;
-            this.extended = false;
-            this.status = TIMER_INVALID;
-        }
-
-        /** Get a timer.  This implementation constructs a new timer. */
-        static Timer obtain(int pid, int uid, @Nullable Object arg, long timeout,
-                @NonNull AnrTimer service) {
-            return new Timer(pid, uid, arg, timeout, service);
-        }
-
-        /** Release a timer. This implementation simply drops the timer. */
-        void release() {
-        }
-
-        /** Return the age of the timer. This is used for debugging. */
-        long age() {
-            return now() - startedMs;
-        }
-
-        /**
-         * The hash code is generated from the owner and the argument.  By definition, the
-         * combination must be unique for the lifetime of an in-use Timer.
-         */
-        @Override
-        public int hashCode() {
-            return Objects.hash(owner, arg);
-        }
-
-        /**
-         * The equality check compares the owner and the argument.  By definition, the combination
-         * must be unique for the lifetime of an in-use Timer.
-         */
-        @Override
-        public boolean equals(Object r) {
-            if (r instanceof Timer) {
-                Timer t = (Timer) r;
-                return Objects.equals(owner, t.owner) && Objects.equals(arg, t.arg);
-            }
-            return false;
-        }
-
-        @Override
-        public String toString() {
-            final int myStatus;
-            synchronized (owner.mLock) {
-                myStatus = status;
-            }
-            return "timerId=" + timerId + " pid=" + pid + " uid=" + uid
-                    + " " + statusString(myStatus) + " " + owner.mLabel;
-        }
-    }
-
     /** A lock for the AnrTimer instance. */
     private final Object mLock = new Object();
 
     /**
-     * The map from client argument to the associated timer.
-     */
-    @GuardedBy("mLock")
-    private final ArrayMap<V, Timer> mTimerMap = new ArrayMap<>();
-
-    /** The highwater mark of started, but not closed, timers. */
-    @GuardedBy("mLock")
-    private int mMaxStarted = 0;
-
-    /**
      * The total number of timers started.
      */
     @GuardedBy("mLock")
@@ -309,176 +173,6 @@
     private int mTotalErrors = 0;
 
     /**
-     * The total number of timers that have expired.
-     */
-    @GuardedBy("mLock")
-    private int mTotalExpired = 0;
-
-    /**
-     * A TimerService that generates a timeout event <n> milliseconds in the future.  See the
-     * class documentation for an explanation of the operations.
-     */
-    private abstract class TimerService {
-        /** Start a timer.  The timeout must be initialized. */
-        abstract boolean start(@NonNull Timer timer);
-
-        abstract void cancel(@NonNull Timer timer);
-
-        abstract void accept(@NonNull Timer timer);
-
-        abstract void discard(@NonNull Timer timer);
-    }
-
-    /**
-     * A class to assist testing.  All methods are null by default but can be overridden as
-     * necessary for a test.
-     */
-    @VisibleForTesting
-    static class Injector {
-        private final Handler mReferenceHandler;
-
-        Injector(@NonNull Handler handler) {
-            mReferenceHandler = handler;
-        }
-
-        /**
-         * Return a handler for the given Callback, based on the reference handler. The handler
-         * might be mocked, in which case it does not have a valid Looper.  In this case, use the
-         * main Looper.
-         */
-        @NonNull
-        Handler newHandler(@NonNull Handler.Callback callback) {
-            Looper looper = mReferenceHandler.getLooper();
-            if (looper == null) looper = Looper.getMainLooper();
-            return new Handler(looper, callback);
-        }
-
-        /**
-         * Return a CpuTracker. The default behavior is to create a new CpuTracker but this changes
-         * for unit tests.
-         **/
-        @NonNull
-        CpuTracker newTracker() {
-            return new CpuTracker();
-        }
-
-        /** Return true if the feature is enabled. */
-        boolean isFeatureEnabled() {
-            return anrTimerServiceEnabled();
-        }
-    }
-
-    /**
-     * A helper class to measure CPU delays.  Given a process ID, this class will return the
-     * cumulative CPU delay for the PID, since process inception.  This class is defined to assist
-     * testing.
-     */
-    @VisibleForTesting
-    static class CpuTracker {
-        /**
-         * The parameter to ProcessCpuTracker indicates that statistics should be collected on a
-         * single process and not on the collection of threads associated with that process.
-         */
-        private final ProcessCpuTracker mCpu = new ProcessCpuTracker(false);
-
-        /** A simple wrapper to fetch the delay.  This method can be overridden for testing. */
-        long delay(int pid) {
-            return mCpu.getCpuDelayTimeForPid(pid);
-        }
-    }
-
-    /**
-     * The "user-space" implementation of the timer service.  This service uses its own message
-     * handler to create timeouts.
-     */
-    private class HandlerTimerService extends TimerService {
-        /** The lock for this handler */
-        private final Object mLock = new Object();
-
-        /** The message handler for scheduling future events. */
-        private final Handler mHandler;
-
-        /** The interface to fetch process statistics that might extend an ANR timeout. */
-        private final CpuTracker mCpu;
-
-        /** Create a HandlerTimerService that directly uses the supplied handler and tracker. */
-        @VisibleForTesting
-        HandlerTimerService(@NonNull Injector injector) {
-            mHandler = injector.newHandler(this::expires);
-            mCpu = injector.newTracker();
-        }
-
-        /** Post a message with the specified timeout.  The timer is not modified. */
-        private void post(@NonNull Timer t, long timeoutMillis) {
-            final Message msg = mHandler.obtainMessage();
-            msg.obj = t;
-            mHandler.sendMessageDelayed(msg, timeoutMillis);
-        }
-
-        /**
-         * The local expiration handler first attempts to compute a timer extension.  If the timer
-         * should be extended, it is rescheduled in the future (granting more time to the
-         * associated process).  If the timer should not be extended then the timeout is delivered
-         * to the client.
-         *
-         * A process is extended to account for the time the process was swapped out and was not
-         * runnable through no fault of its own.  A timer can only be extended once and only if
-         * the AnrTimer permits extensions.  Finally, a timer will never be extended by more than
-         * the original timeout, so the total timeout will never be more than twice the originally
-         * configured timeout.
-         */
-        private boolean expires(Message msg) {
-            Timer t = (Timer) msg.obj;
-            synchronized (mLock) {
-                long extension = 0;
-                if (mExtend && !t.extended) {
-                    extension = mCpu.delay(t.pid) - t.initialCpuDelayMs;
-                    if (extension < 0) extension = 0;
-                    if (extension > t.timeoutMs) extension = t.timeoutMs;
-                    t.extended = true;
-                }
-                if (extension > 0) {
-                    post(t, extension);
-                } else {
-                    onExpiredLocked(t);
-                }
-            }
-            return true;
-        }
-
-        @GuardedBy("mLock")
-        @Override
-        boolean start(@NonNull Timer t) {
-            if (mExtend) {
-                t.initialCpuDelayMs = mCpu.delay(t.pid);
-            }
-            post(t, t.timeoutMs);
-            return true;
-        }
-
-        @Override
-        void cancel(@NonNull Timer t) {
-            mHandler.removeMessages(0, t);
-        }
-
-        @Override
-        void accept(@NonNull Timer t) {
-            // Nothing to do.
-        }
-
-        @Override
-        void discard(@NonNull Timer t) {
-            // Nothing to do.
-        }
-
-        /** The string identifies this subclass of AnrTimerService as being based on handlers. */
-        @Override
-        public String toString() {
-            return "handler";
-        }
-    }
-
-    /**
      * The handler for messages sent from this instance.
      */
     private final Handler mHandler;
@@ -499,18 +193,6 @@
     private final boolean mExtend;
 
     /**
-     * The timer service to use for this AnrTimer.
-     */
-    private final TimerService mTimerService;
-
-    /**
-     * Whether or not canceling a non-existent timer is an error.  Clients often cancel freely
-     * preemptively, without knowing if the timer was ever started.  Keeping this variable true
-     * means that such behavior is not an error.
-     */
-    private final boolean mLenientCancel = true;
-
-    /**
      * The top-level switch for the feature enabled or disabled.
      */
     private final FeatureSwitch mFeature;
@@ -528,40 +210,34 @@
      * AnrTimer may extend the individual timer rather than immediately delivering the timeout to
      * the client.  The extension policy is not part of the instance.
      *
-     * This method accepts an {@link #Injector} to tune behavior for testing.  This method should
-     * not be called directly by regular clients.
-     *
      * @param handler The handler to which the expiration message will be delivered.
      * @param what The "what" parameter for the expiration message.
      * @param label A name for this instance.
      * @param extend A flag to indicate if expired timers can be granted extensions.
-     * @param injector An {@link #Injector} to tune behavior for testing.
+     * @param injector An injector to provide overrides for testing.
      */
     @VisibleForTesting
     AnrTimer(@NonNull Handler handler, int what, @NonNull String label, boolean extend,
-            @NonNull Injector injector) {
+             @NonNull Injector injector) {
         mHandler = handler;
         mWhat = what;
         mLabel = label;
         mExtend = extend;
-        boolean enabled = injector.isFeatureEnabled();
-        if (!enabled) {
-            mFeature = new FeatureDisabled();
-            mTimerService = null;
-        } else {
-            mFeature = new FeatureEnabled();
-            mTimerService = new HandlerTimerService(injector);
-
-            synchronized (sAnrTimerList) {
-                sAnrTimerList.add(new WeakReference(this));
-            }
-        }
-        Log.i(TAG, formatSimple("created %s label: \"%s\"", mTimerService, label));
+        mFeature = new FeatureDisabled();
     }
 
     /**
-     * Create an AnrTimer instance with the default {@link #Injector}.  See {@link AnrTimer(Handler,
-     * int, String, boolean, Injector} for a functional description.
+     * Create one AnrTimer instance.  The instance is given a handler and a "what".  Individual
+     * timers are started with {@link #start}.  If a timer expires, then a {@link Message} is sent
+     * immediately to the handler with {@link Message.what} set to what and {@link Message.obj} set
+     * to the timer key.
+     *
+     * AnrTimer instances have a label, which must be unique.  The label is used for reporting and
+     * debug.
+     *
+     * If an individual timer expires internally, and the "extend" parameter is true, then the
+     * AnrTimer may extend the individual timer rather than immediately delivering the timeout to
+     * the client.  The extension policy is not part of the instance.
      *
      * @param handler The handler to which the expiration message will be delivered.
      * @param what The "what" parameter for the expiration message.
@@ -569,7 +245,7 @@
      * @param extend A flag to indicate if expired timers can be granted extensions.
      */
     public AnrTimer(@NonNull Handler handler, int what, @NonNull String label, boolean extend) {
-        this(handler, what, label, extend, new Injector(handler));
+        this(handler, what, label, extend, new Injector());
     }
 
     /**
@@ -596,105 +272,17 @@
     }
 
     /**
-     * Start a trace on the timer.  The trace is laid down in the AnrTimerTrack.
-     */
-    private void traceBegin(Timer t, String what) {
-        if (ENABLE_TRACING) {
-            final String label = formatSimple("%s(%d,%d,%s)", what, t.pid, t.uid, mLabel);
-            final int cookie = t.hashCode();
-            Trace.asyncTraceForTrackBegin(TRACE_TAG, TRACK, label, cookie);
-        }
-    }
-
-    /**
-     * End a trace on the timer.
-     */
-    private void traceEnd(Timer t) {
-        if (ENABLE_TRACING) {
-            final int cookie = t.hashCode();
-            Trace.asyncTraceForTrackEnd(TRACE_TAG, TRACK, cookie);
-        }
-    }
-
-    /**
-     * Return the string representation for a timer status.
-     */
-    private static String statusString(int s) {
-        switch (s) {
-            case TIMER_INVALID: return "invalid";
-            case TIMER_RUNNING: return "running";
-            case TIMER_EXPIRED: return "expired";
-        }
-        return formatSimple("unknown: %d", s);
-    }
-
-    /**
-     * Delete the timer associated with arg from the maps and return it.  Return null if the timer
-     * was not found.
-     */
-    @GuardedBy("mLock")
-    private Timer removeLocked(V arg) {
-        Timer timer = mTimerMap.remove(arg);
-        return timer;
-    }
-
-    /**
-     * Return the number of timers currently running.
-     */
-    @VisibleForTesting
-    static int sizeOfTimerList() {
-        synchronized (sAnrTimerList) {
-            int totalTimers = 0;
-            for (int i = 0; i < sAnrTimerList.size(); i++) {
-                AnrTimer client = sAnrTimerList.get(i).get();
-                if (client != null) totalTimers += client.mTimerMap.size();
-            }
-            return totalTimers;
-        }
-    }
-
-    /**
-     * Clear out all existing timers.  This will lead to unexpected behavior if used carelessly.
-     * It is available only for testing.  It returns the number of times that were actually
-     * erased.
-     */
-    @VisibleForTesting
-    static int resetTimerListForHermeticTest() {
-        synchronized (sAnrTimerList) {
-            int mapLen = 0;
-            for (int i = 0; i < sAnrTimerList.size(); i++) {
-                AnrTimer client = sAnrTimerList.get(i).get();
-                if (client != null) {
-                    mapLen += client.mTimerMap.size();
-                    client.mTimerMap.clear();
-                }
-            }
-            if (mapLen > 0) {
-                Log.w(TAG, formatSimple("erasing timer list: clearing %d timers", mapLen));
-            }
-            return mapLen;
-        }
-    }
-
-    /**
-     * Generate a log message for a timer.
-     */
-    private void report(@NonNull Timer timer, @NonNull String msg) {
-        Log.i(TAG, msg + " " + timer + " " + Objects.toString(timer.arg));
-    }
-
-    /**
      * The FeatureSwitch class provides a quick switch between feature-enabled behavior and
      * feature-disabled behavior.
      */
     private abstract class FeatureSwitch {
-        abstract boolean start(@NonNull V arg, int pid, int uid, long timeoutMs);
+        abstract void start(@NonNull V arg, int pid, int uid, long timeoutMs);
 
-        abstract boolean cancel(@NonNull V arg);
+        abstract void cancel(@NonNull V arg);
 
-        abstract boolean accept(@NonNull V arg);
+        abstract void accept(@NonNull V arg);
 
-        abstract boolean discard(@NonNull V arg);
+        abstract void discard(@NonNull V arg);
 
         abstract boolean enabled();
     }
@@ -706,29 +294,25 @@
     private class FeatureDisabled extends FeatureSwitch {
         /** Start a timer by sending a message to the client's handler. */
         @Override
-        boolean start(@NonNull V arg, int pid, int uid, long timeoutMs) {
+        void start(@NonNull V arg, int pid, int uid, long timeoutMs) {
             final Message msg = mHandler.obtainMessage(mWhat, arg);
             mHandler.sendMessageDelayed(msg, timeoutMs);
-            return true;
         }
 
         /** Cancel a timer by removing the message from the client's handler. */
         @Override
-        boolean cancel(@NonNull V arg) {
+        void cancel(@NonNull V arg) {
             mHandler.removeMessages(mWhat, arg);
-            return true;
         }
 
         /** accept() is a no-op when the feature is disabled. */
         @Override
-        boolean accept(@NonNull V arg) {
-            return true;
+        void accept(@NonNull V arg) {
         }
 
         /** discard() is a no-op when the feature is disabled. */
         @Override
-        boolean discard(@NonNull V arg) {
-            return true;
+        void discard(@NonNull V arg) {
         }
 
         /** The feature is not enabled. */
@@ -739,113 +323,6 @@
     }
 
     /**
-     * The FeatureEnabled class enables the AnrTimer logic.  It is used when the AnrTimer service
-     * is enabled via Flags.anrTimerServiceEnabled.
-     */
-    private class FeatureEnabled extends FeatureSwitch {
-
-        /**
-         * Start a timer.
-         */
-        @Override
-        boolean start(@NonNull V arg, int pid, int uid, long timeoutMs) {
-            final Timer timer = Timer.obtain(pid, uid, arg, timeoutMs, AnrTimer.this);
-            synchronized (mLock) {
-                Timer old = mTimerMap.get(arg);
-                // There is an existing timer.  If the timer was running, then cancel the running
-                // timer and restart it.  If the timer was expired record a protocol error and
-                // discard the expired timer.
-                if (old != null) {
-                    if (old.status == TIMER_EXPIRED) {
-                      restartedLocked(old.status, arg);
-                        discard(arg);
-                    } else {
-                        cancel(arg);
-                    }
-                }
-                if (mTimerService.start(timer)) {
-                    timer.status = TIMER_RUNNING;
-                    mTimerMap.put(arg, timer);
-                    mTotalStarted++;
-                    mMaxStarted = Math.max(mMaxStarted, mTimerMap.size());
-                    if (DEBUG) report(timer, "start");
-                    return true;
-                } else {
-                    Log.e(TAG, "AnrTimer.start failed");
-                    return false;
-                }
-            }
-        }
-
-        /**
-         * Cancel a timer.  Return false if the timer was not found.
-         */
-        @Override
-        boolean cancel(@NonNull V arg) {
-            synchronized (mLock) {
-                Timer timer = removeLocked(arg);
-                if (timer == null) {
-                    if (!mLenientCancel) notFoundLocked("cancel", arg);
-                    return false;
-                }
-                mTimerService.cancel(timer);
-                // There may be an expiration message in flight.  Cancel it.
-                mHandler.removeMessages(mWhat, arg);
-                if (DEBUG) report(timer, "cancel");
-                timer.release();
-                return true;
-            }
-        }
-
-        /**
-         * Accept a timer in the framework-level handler.  The timeout has been accepted and the
-         * timeout handler is executing.  Return false if the timer was not found.
-         */
-        @Override
-        boolean accept(@NonNull V arg) {
-            synchronized (mLock) {
-                Timer timer = removeLocked(arg);
-                if (timer == null) {
-                    notFoundLocked("accept", arg);
-                    return false;
-                }
-                mTimerService.accept(timer);
-                traceEnd(timer);
-                if (DEBUG) report(timer, "accept");
-                timer.release();
-                return true;
-            }
-        }
-
-        /**
-         * Discard a timer in the framework-level handler.  For whatever reason, the timer is no
-         * longer interesting.  No statistics are collected.  Return false if the time was not
-         * found.
-         */
-        @Override
-        boolean discard(@NonNull V arg) {
-            synchronized (mLock) {
-                Timer timer = removeLocked(arg);
-                if (timer == null) {
-                    notFoundLocked("discard", arg);
-                    return false;
-                }
-                mTimerService.discard(timer);
-                traceEnd(timer);
-                if (DEBUG) report(timer, "discard");
-                timer.release();
-                return true;
-            }
-        }
-
-        /** The feature is enabled. */
-        @Override
-        boolean enabled() {
-            return true;
-        }
-    }
-
-    /**
      * Start a timer associated with arg.  The same object must be used to cancel, accept, or
      * discard a timer later.  If a timer already exists with the same arg, then the existing timer
      * is canceled and a new timer is created.
@@ -854,32 +331,27 @@
      * @param pid The Linux process ID of the target being timed.
      * @param uid The Linux user ID of the target being timed.
      * @param timeoutMs The timer timeout, in milliseconds.
-     * @return true if the timer was successfully created.
      */
-    public boolean start(@NonNull V arg, int pid, int uid, long timeoutMs) {
-        return mFeature.start(arg, pid, uid, timeoutMs);
+    public void start(@NonNull V arg, int pid, int uid, long timeoutMs) {
+        mFeature.start(arg, pid, uid, timeoutMs);
     }
 
     /**
      * Cancel the running timer associated with arg.  The timer is forgotten.  If the timer has
      * expired, the call is treated as a discard.  No errors are reported if the timer does not
      * exist or if the timer has expired.
-     *
-     * @return true if the timer was found and was running.
      */
-    public boolean cancel(@NonNull V arg) {
-        return mFeature.cancel(arg);
+    public void cancel(@NonNull V arg) {
+        mFeature.cancel(arg);
     }
 
     /**
      * Accept the expired timer associated with arg.  This indicates that the caller considers the
      * timer expiration to be a true ANR.  (See {@link #discard} for an alternate response.)  It is
      * an error to accept a running timer, however the running timer will be canceled.
-     *
-     * @return true if the timer was found and was expired.
      */
-    public boolean accept(@NonNull V arg) {
-        return mFeature.accept(arg);
+    public void accept(@NonNull V arg) {
+        mFeature.accept(arg);
     }
 
     /**
@@ -889,24 +361,9 @@
      * such a process could be stopped at a breakpoint and its failure to respond would not be an
      * error.  It is an error to discard a running timer, however the running timer will be
      * canceled.
-     *
-     * @return true if the timer was found and was expired.
      */
-    public boolean discard(@NonNull V arg) {
-        return mFeature.discard(arg);
-    }
-
-    /**
-     * The notifier that a timer has fired.  The timer is not modified.
-     */
-    @GuardedBy("mLock")
-    private void onExpiredLocked(@NonNull Timer timer) {
-        if (DEBUG) report(timer, "expire");
-        traceBegin(timer, "expired");
-        mHandler.sendMessage(Message.obtain(mHandler, mWhat, timer.arg));
-        synchronized (mLock) {
-            mTotalExpired++;
-        }
+    public void discard(@NonNull V arg) {
+        mFeature.discard(arg);
     }
 
     /**
@@ -916,9 +373,7 @@
         synchronized (mLock) {
             pw.format("timer: %s\n", mLabel);
             pw.increaseIndent();
-            pw.format("started=%d maxStarted=%d running=%d expired=%d error=%d\n",
-                    mTotalStarted, mMaxStarted, mTimerMap.size(),
-                    mTotalExpired, mTotalErrors);
+            pw.format("started=%d errors=%d\n", mTotalStarted, mTotalErrors);
             pw.decreaseIndent();
         }
     }
@@ -931,10 +386,20 @@
     }
 
     /**
-     * The current time in milliseconds.
+     * Dump all errors to the output stream.
      */
-    private static long now() {
-        return SystemClock.uptimeMillis();
+    private static void dumpErrors(IndentingPrintWriter ipw) {
+        Error errors[];
+        synchronized (sErrors) {
+            if (sErrors.size() == 0) return;
+            errors = sErrors.toArray();
+        }
+        ipw.println("Errors");
+        ipw.increaseIndent();
+        for (int i = 0; i < errors.length; i++) {
+            if (errors[i] != null) errors[i].dump(ipw, i);
+        }
+        ipw.decreaseIndent();
     }
 
     /**
@@ -966,60 +431,12 @@
     }
 
     /**
-     * Log an error about a timer that is started when there is an existing timer.
-     */
-    @GuardedBy("mLock")
-    private void restartedLocked(@TimerStatus int status, Object arg) {
-        recordErrorLocked("start", status == TIMER_EXPIRED ? "autoDiscard" : "autoCancel", arg);
-    }
-
-    /**
-     * Dump a single error to the output stream.
-     */
-    private static void dump(IndentingPrintWriter ipw, int seq, Error err) {
-        ipw.format("%2d: op:%s tag:%s issue:%s arg:%s\n", seq, err.operation, err.tag,
-                err.issue, err.arg);
-
-        final long offset = System.currentTimeMillis() - SystemClock.elapsedRealtime();
-        final long etime = offset + err.timestamp;
-        ipw.println("    date:" + TimeMigrationUtils.formatMillisWithFixedFormat(etime));
-        ipw.increaseIndent();
-        for (int i = 0; i < err.stack.length; i++) {
-            ipw.println("    " + err.stack[i].toString());
-        }
-        ipw.decreaseIndent();
-    }
-
-    /**
-     * Dump all errors to the output stream.
-     */
-    private static void dumpErrors(IndentingPrintWriter ipw) {
-        Error errors[];
-        synchronized (sErrors) {
-            if (sErrors.size() == 0) return;
-            errors = sErrors.toArray();
-        }
-        ipw.println("Errors");
-        ipw.increaseIndent();
-        for (int i = 0; i < errors.length; i++) {
-            if (errors[i] != null) dump(ipw, i, errors[i]);
-        }
-        ipw.decreaseIndent();
-    }
-
-    /**
      * Dumpsys output.
      */
     public static void dump(@NonNull PrintWriter pw, boolean verbose) {
         final IndentingPrintWriter ipw = new IndentingPrintWriter(pw);
         ipw.println("AnrTimer statistics");
         ipw.increaseIndent();
-        synchronized (sAnrTimerList) {
-            for (int i = 0; i < sAnrTimerList.size(); i++) {
-                AnrTimer client = sAnrTimerList.get(i).get();
-                if (client != null) client.dump(ipw);
-            }
-        }
         if (verbose) dumpErrors(ipw);
         ipw.format("AnrTimerEnd\n");
         ipw.decreaseIndent();
diff --git a/services/core/java/com/android/server/utils/OWNERS b/services/core/java/com/android/server/utils/OWNERS
index be91611d..fbc0b56 100644
--- a/services/core/java/com/android/server/utils/OWNERS
+++ b/services/core/java/com/android/server/utils/OWNERS
@@ -10,3 +10,8 @@
 per-file Watcher.java = shombert@google.com
 per-file EventLogger.java = file:/platform/frameworks/av:/media/janitors/media_solutions_OWNERS
 per-file EventLogger.java = jmtrivi@google.com
+
+# Bug component : 158088 = per-file AnrTimer*.java
+per-file AnrTimer*.java = file:/PERFORMANCE_OWNERS
+
+per-file flags.aconfig = file:/PERFORMANCE_OWNERS
diff --git a/services/core/java/com/android/server/utils/WatchedSparseSetArray.java b/services/core/java/com/android/server/utils/WatchedSparseSetArray.java
index 0386e66..b8850af 100644
--- a/services/core/java/com/android/server/utils/WatchedSparseSetArray.java
+++ b/services/core/java/com/android/server/utils/WatchedSparseSetArray.java
@@ -142,6 +142,20 @@
         return (T) mStorage.valueAt(intIndex, valueIndex);
     }
 
+    /**
+     * Copy from another SparseSetArray.
+     */
+    public void copyFrom(@NonNull SparseSetArray<T> c) {
+        clear();
+        final int end = c.size();
+        for (int i = 0; i < end; i++) {
+            final int key = c.keyAt(i);
+            final ArraySet<T> set = c.get(key);
+            mStorage.addAll(key, set);
+        }
+        onChanged();
+    }
+
     @NonNull
     @Override
     public Object snapshot() {
diff --git a/services/core/java/com/android/server/vibrator/VibratorControlService.java b/services/core/java/com/android/server/vibrator/VibratorControlService.java
deleted file mode 100644
index 2eeb903..0000000
--- a/services/core/java/com/android/server/vibrator/VibratorControlService.java
+++ /dev/null
@@ -1,105 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.vibrator;
-
-import android.annotation.NonNull;
-import android.annotation.SuppressLint;
-import android.frameworks.vibrator.IVibratorControlService;
-import android.frameworks.vibrator.IVibratorController;
-import android.frameworks.vibrator.VibrationParam;
-import android.os.IBinder;
-import android.os.RemoteException;
-import android.util.Slog;
-
-import java.util.Objects;
-
-/**
- * Implementation of {@link IVibratorControlService} which allows the registration of
- * {@link IVibratorController} to set and receive vibration params.
- *
- * @hide
- */
-public final class VibratorControlService extends IVibratorControlService.Stub {
-    private static final String TAG = "VibratorControlService";
-
-    private final VibratorControllerHolder mVibratorControllerHolder;
-    private final Object mLock;
-
-    public VibratorControlService(VibratorControllerHolder vibratorControllerHolder, Object lock) {
-        mVibratorControllerHolder = vibratorControllerHolder;
-        mLock = lock;
-    }
-
-    @Override
-    public void registerVibratorController(IVibratorController controller)
-            throws RemoteException {
-        synchronized (mLock) {
-            mVibratorControllerHolder.setVibratorController(controller);
-        }
-    }
-
-    @Override
-    public void unregisterVibratorController(@NonNull IVibratorController controller)
-            throws RemoteException {
-        Objects.requireNonNull(controller);
-
-        synchronized (mLock) {
-            if (mVibratorControllerHolder.getVibratorController() == null) {
-                Slog.w(TAG, "Received request to unregister IVibratorController = "
-                        + controller + ", but no controller was previously registered. Request "
-                        + "Ignored.");
-                return;
-            }
-            if (!Objects.equals(mVibratorControllerHolder.getVibratorController().asBinder(),
-                    controller.asBinder())) {
-                Slog.wtf(TAG, "Failed to unregister IVibratorController. The provided "
-                        + "controller doesn't match the registered one. " + this);
-                return;
-            }
-            mVibratorControllerHolder.setVibratorController(null);
-        }
-    }
-
-    @Override
-    public void setVibrationParams(
-            @SuppressLint("ArrayReturn") VibrationParam[] params, IVibratorController token)
-            throws RemoteException {
-        // TODO(b/305939964): Add set vibration implementation.
-    }
-
-    @Override
-    public void clearVibrationParams(int types, IVibratorController token) throws RemoteException {
-        // TODO(b/305939964): Add clear vibration implementation.
-    }
-
-    @Override
-    public void onRequestVibrationParamsComplete(
-            IBinder requestToken, @SuppressLint("ArrayReturn") VibrationParam[] result)
-            throws RemoteException {
-        // TODO(305942827): Cache the vibration params in VibrationScaler
-    }
-
-    @Override
-    public int getInterfaceVersion() throws RemoteException {
-        return this.VERSION;
-    }
-
-    @Override
-    public String getInterfaceHash() throws RemoteException {
-        return this.HASH;
-    }
-}
diff --git a/services/core/java/com/android/server/vibrator/VibratorControllerHolder.java b/services/core/java/com/android/server/vibrator/VibratorControllerHolder.java
deleted file mode 100644
index 63e69db..0000000
--- a/services/core/java/com/android/server/vibrator/VibratorControllerHolder.java
+++ /dev/null
@@ -1,70 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.vibrator;
-
-import android.annotation.NonNull;
-import android.frameworks.vibrator.IVibratorController;
-import android.os.IBinder;
-import android.os.RemoteException;
-import android.util.Slog;
-
-/**
- * Holder class for {@link IVibratorController}.
- *
- * @hide
- */
-public final class VibratorControllerHolder implements IBinder.DeathRecipient {
-    private static final String TAG = "VibratorControllerHolder";
-
-    private IVibratorController mVibratorController;
-
-    public IVibratorController getVibratorController() {
-        return mVibratorController;
-    }
-
-    /**
-     * Sets the {@link IVibratorController} in {@link VibratorControllerHolder} to the new
-     * controller. This will also take care of registering and unregistering death notifications
-     * for the cached {@link IVibratorController}.
-     */
-    public void setVibratorController(IVibratorController controller) {
-        try {
-            if (mVibratorController != null) {
-                mVibratorController.asBinder().unlinkToDeath(this, 0);
-            }
-            mVibratorController = controller;
-            if (mVibratorController != null) {
-                mVibratorController.asBinder().linkToDeath(this, 0);
-            }
-        } catch (RemoteException e) {
-            Slog.wtf(TAG, "Failed to set IVibratorController: " + this, e);
-        }
-    }
-
-    @Override
-    public void binderDied(@NonNull IBinder deadBinder) {
-        if (deadBinder == mVibratorController.asBinder()) {
-            setVibratorController(null);
-        }
-    }
-
-    @Override
-    public void binderDied() {
-        // Should not be used as binderDied(IBinder who) is overridden.
-        Slog.wtf(TAG, "binderDied() called unexpectedly.");
-    }
-}
diff --git a/services/core/java/com/android/server/vibrator/VibratorManagerService.java b/services/core/java/com/android/server/vibrator/VibratorManagerService.java
index d5044d9..7d4bd3b 100644
--- a/services/core/java/com/android/server/vibrator/VibratorManagerService.java
+++ b/services/core/java/com/android/server/vibrator/VibratorManagerService.java
@@ -53,7 +53,6 @@
 import android.os.VibrationAttributes;
 import android.os.VibrationEffect;
 import android.os.VibratorInfo;
-import android.os.vibrator.Flags;
 import android.os.vibrator.PrebakedSegment;
 import android.os.vibrator.VibrationEffectSegment;
 import android.os.vibrator.VibratorInfoFactory;
@@ -88,13 +87,10 @@
 import java.util.function.Consumer;
 import java.util.function.Function;
 
-
 /** System implementation of {@link IVibratorManagerService}. */
 public class VibratorManagerService extends IVibratorManagerService.Stub {
     private static final String TAG = "VibratorManagerService";
     private static final String EXTERNAL_VIBRATOR_SERVICE = "external_vibrator_service";
-    private static final String VIBRATOR_CONTROL_SERVICE =
-            "android.frameworks.vibrator.IVibratorControlService/default";
     private static final boolean DEBUG = false;
     private static final VibrationAttributes DEFAULT_ATTRIBUTES =
             new VibrationAttributes.Builder().build();
@@ -273,10 +269,6 @@
         context.registerReceiver(mIntentReceiver, filter, Context.RECEIVER_NOT_EXPORTED);
 
         injector.addService(EXTERNAL_VIBRATOR_SERVICE, new ExternalVibratorService());
-        if (Flags.adaptiveHapticsEnabled()) {
-            injector.addService(VIBRATOR_CONTROL_SERVICE,
-                    new VibratorControlService(new VibratorControllerHolder(), mLock));
-        }
     }
 
     /** Finish initialization at boot phase {@link SystemService#PHASE_SYSTEM_SERVICES_READY}. */
@@ -411,9 +403,13 @@
 
     @Override // Binder call
     public void performHapticFeedback(
-            int uid, int deviceId, String opPkg, int constant, boolean always, String reason,
-            IBinder token) {
-        performHapticFeedbackInternal(uid, deviceId, opPkg, constant, always, reason, token);
+            int uid, int deviceId, String opPkg, int constant, boolean always, String reason) {
+        // Note that the `performHapticFeedback` method does not take a token argument from the
+        // caller, and instead, uses this service as the token. This is to mitigate performance
+        // impact that would otherwise be caused due to marshal latency. Haptic feedback effects are
+        // short-lived, so we don't need to cancel when the process dies.
+        performHapticFeedbackInternal(
+                uid, deviceId, opPkg, constant, always, reason, /* token= */ this);
     }
 
     /**
diff --git a/services/core/java/com/android/server/wm/ActivityStarter.java b/services/core/java/com/android/server/wm/ActivityStarter.java
index e5794a1..dfb2a5f 100644
--- a/services/core/java/com/android/server/wm/ActivityStarter.java
+++ b/services/core/java/com/android/server/wm/ActivityStarter.java
@@ -1121,6 +1121,7 @@
                             callerApp,
                             request.originatingPendingIntent,
                             request.forcedBalByPiSender,
+                            resultRecord,
                             intent,
                             checkedOptions);
                 request.logMessage.append(" (").append(balVerdict).append(")");
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
index a04513f..73edb4b 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
@@ -103,6 +103,7 @@
 import static com.android.server.wm.ActivityInterceptorCallback.SYSTEM_LAST_ORDERED_ID;
 import static com.android.server.wm.ActivityRecord.State.PAUSING;
 import static com.android.server.wm.ActivityRecord.State.RESUMED;
+import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_ACTIVITY_STARTS;
 import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_ALL;
 import static com.android.server.wm.ActivityTaskManagerDebugConfig.POSTFIX_ROOT_TASK;
 import static com.android.server.wm.ActivityTaskManagerDebugConfig.POSTFIX_SWITCH;
@@ -118,6 +119,7 @@
 import static com.android.server.wm.ActivityTaskSupervisor.ON_TOP;
 import static com.android.server.wm.ActivityTaskSupervisor.PRESERVE_WINDOWS;
 import static com.android.server.wm.ActivityTaskSupervisor.REMOVE_FROM_RECENTS;
+import static com.android.server.wm.BackgroundActivityStartController.BalVerdict;
 import static com.android.server.wm.LockTaskController.LOCK_TASK_AUTH_DONT_LOCK;
 import static com.android.server.wm.RecentsAnimationController.REORDER_KEEP_IN_PLACE;
 import static com.android.server.wm.RecentsAnimationController.REORDER_MOVE_TO_ORIGINAL_POSITION;
@@ -2242,7 +2244,7 @@
         }
         final BackgroundActivityStartController balController =
                 mTaskSupervisor.getBackgroundActivityLaunchController();
-        if (balController.shouldAbortBackgroundActivityStart(
+        final BalVerdict balVerdict = balController.checkBackgroundActivityStart(
                 callingUid,
                 callingPid,
                 callingPackage,
@@ -2252,10 +2254,14 @@
                 null,
                 BackgroundStartPrivileges.NONE,
                 null,
-                null)) {
-            if (!isBackgroundActivityStartsEnabled()) {
-                return;
-            }
+                null,
+                null);
+        if (balVerdict.blocks() && !isBackgroundActivityStartsEnabled()) {
+            Slog.w(TAG, "moveTaskToFront blocked: " + balVerdict);
+            return;
+        }
+        if (DEBUG_ACTIVITY_STARTS) {
+            Slog.d(TAG, "moveTaskToFront allowed: " + balVerdict);
         }
         try {
             final Task task = mRootWindowContainer.anyTaskForId(taskId);
diff --git a/services/core/java/com/android/server/wm/AppTaskImpl.java b/services/core/java/com/android/server/wm/AppTaskImpl.java
index 761b0a8..50de0b0 100644
--- a/services/core/java/com/android/server/wm/AppTaskImpl.java
+++ b/services/core/java/com/android/server/wm/AppTaskImpl.java
@@ -16,6 +16,8 @@
 
 package com.android.server.wm;
 
+import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_ACTIVITY_STARTS;
+import static com.android.server.wm.BackgroundActivityStartController.BalVerdict;
 import static com.android.server.wm.ActivityTaskSupervisor.REMOVE_FROM_RECENTS;
 import static com.android.server.wm.RootWindowContainer.MATCH_ATTACHED_TASK_OR_RECENT_TASKS;
 
@@ -31,6 +33,7 @@
 import android.os.Process;
 import android.os.RemoteException;
 import android.os.UserHandle;
+import android.util.Slog;
 
 /**
  * An implementation of IAppTask, that allows an app to manage its own tasks via
@@ -123,7 +126,7 @@
                 }
                 final BackgroundActivityStartController balController =
                         mService.mTaskSupervisor.getBackgroundActivityLaunchController();
-                if (balController.shouldAbortBackgroundActivityStart(
+                BalVerdict balVerdict = balController.checkBackgroundActivityStart(
                         callingUid,
                         callingPid,
                         callingPackage,
@@ -133,10 +136,14 @@
                         null,
                         BackgroundStartPrivileges.NONE,
                         null,
-                        null)) {
-                    if (!mService.isBackgroundActivityStartsEnabled()) {
-                        return;
-                    }
+                        null,
+                        null);
+                if (balVerdict.blocks() && !mService.isBackgroundActivityStartsEnabled()) {
+                    Slog.w(TAG, "moveTaskToFront blocked: : " + balVerdict);
+                    return;
+                }
+                if (DEBUG_ACTIVITY_STARTS) {
+                    Slog.d(TAG, "moveTaskToFront allowed: " + balVerdict);
                 }
             }
             mService.mTaskSupervisor.startActivityFromRecents(callingPid, callingUid, mTaskId,
diff --git a/services/core/java/com/android/server/wm/BackgroundActivityStartController.java b/services/core/java/com/android/server/wm/BackgroundActivityStartController.java
index f8b22c9..92665af 100644
--- a/services/core/java/com/android/server/wm/BackgroundActivityStartController.java
+++ b/services/core/java/com/android/server/wm/BackgroundActivityStartController.java
@@ -205,27 +205,6 @@
         return activity != null && packageName.equals(activity.getPackageName());
     }
 
-    /**
-     * @see #checkBackgroundActivityStart(int, int, String, int, int, WindowProcessController,
-     *      PendingIntentRecord, BackgroundStartPrivileges, Intent, ActivityOptions)
-     */
-    boolean shouldAbortBackgroundActivityStart(
-            int callingUid,
-            int callingPid,
-            final String callingPackage,
-            int realCallingUid,
-            int realCallingPid,
-            WindowProcessController callerApp,
-            PendingIntentRecord originatingPendingIntent,
-            BackgroundStartPrivileges forcedBalByPiSender,
-            Intent intent,
-            ActivityOptions checkedOptions) {
-        return checkBackgroundActivityStart(callingUid, callingPid, callingPackage,
-                realCallingUid, realCallingPid,
-                callerApp, originatingPendingIntent,
-                forcedBalByPiSender, intent, checkedOptions).blocks();
-    }
-
     private class BalState {
 
         private final String mCallingPackage;
@@ -255,6 +234,7 @@
                  WindowProcessController callerApp,
                  PendingIntentRecord originatingPendingIntent,
                  BackgroundStartPrivileges forcedBalByPiSender,
+                 ActivityRecord resultRecord,
                  Intent intent,
                  ActivityOptions checkedOptions) {
             this.mCallingPackage = callingPackage;
@@ -267,7 +247,9 @@
             mOriginatingPendingIntent = originatingPendingIntent;
             mIntent = intent;
             mRealCallingPackage = mService.getPackageNameIfUnique(realCallingUid, realCallingPid);
-            if (originatingPendingIntent == null) {
+            if (originatingPendingIntent == null // not a PendingIntent
+                    || resultRecord != null // sent for result
+            ) {
                 // grant BAL privileges unless explicitly opted out
                 mBalAllowedByPiCreatorWithHardening = mBalAllowedByPiCreator =
                         checkedOptions.getPendingIntentCreatorBackgroundActivityStartMode()
@@ -443,6 +425,8 @@
         // indicates BAL would be blocked because only creator of the PI has the privilege to allow
         // BAL, the sender does not have the privilege to allow BAL.
         private boolean mOnlyCreatorAllows;
+        /** indicates that this verdict is based on the real calling UID and not the calling UID */
+        private boolean mBasedOnRealCaller;
 
         BalVerdict(@BalCode int balCode, boolean background, String message) {
             this.mBackground = background;
@@ -472,6 +456,15 @@
             return mOnlyCreatorAllows;
         }
 
+        private BalVerdict setBasedOnRealCaller() {
+            mBasedOnRealCaller = true;
+            return this;
+        }
+
+        private boolean isBasedOnRealCaller() {
+            return mBasedOnRealCaller;
+        }
+
         public String toString() {
             StringBuilder builder = new StringBuilder();
             builder.append(balCodeToString(mCode));
@@ -495,7 +488,15 @@
             return builder.toString();
         }
 
+        public @BalCode int getRawCode() {
+            return mCode;
+        }
+
         public @BalCode int getCode() {
+            if (mBasedOnRealCaller && mCode != BAL_BLOCK) {
+                // for compatibility always return BAL_ALLOW_PENDING_INTENT if based on real caller
+                return BAL_ALLOW_PENDING_INTENT;
+            }
             return mCode;
         }
     }
@@ -516,6 +517,7 @@
      * @param forcedBalByPiSender If set to allow, the
      *        PendingIntent's sender will try to force allow background activity starts.
      *        This is only possible if the sender of the PendingIntent is a system process.
+     * @param resultRecord If not null, this indicates that the caller expects a result.
      * @param intent Intent that should be started.
      * @param checkedOptions ActivityOptions to allow specific opt-ins/opt outs.
      *
@@ -531,6 +533,7 @@
             WindowProcessController callerApp,
             PendingIntentRecord originatingPendingIntent,
             BackgroundStartPrivileges forcedBalByPiSender,
+            ActivityRecord resultRecord,
             Intent intent,
             ActivityOptions checkedOptions) {
 
@@ -541,7 +544,7 @@
 
         BalState state = new BalState(callingUid, callingPid, callingPackage,
                 realCallingUid, realCallingPid, callerApp, originatingPendingIntent,
-                forcedBalByPiSender, intent, checkedOptions);
+                forcedBalByPiSender, resultRecord, intent, checkedOptions);
 
         // In the case of an SDK sandbox calling uid, check if the corresponding app uid has a
         // visible window.
@@ -580,7 +583,8 @@
         // PendingIntents is null).
         BalVerdict resultForRealCaller = state.callerIsRealCaller() && resultForCaller.allows()
                 ? resultForCaller
-                : checkBackgroundActivityStartAllowedBySender(state, checkedOptions);
+                : checkBackgroundActivityStartAllowedBySender(state, checkedOptions)
+                        .setBasedOnRealCaller();
         if (state.isPendingIntent()) {
             resultForCaller.setOnlyCreatorAllows(
                     resultForCaller.allows() && resultForRealCaller.blocks());
@@ -828,7 +832,7 @@
                 && ActivityManager.checkComponentPermission(
                 android.Manifest.permission.START_ACTIVITIES_FROM_BACKGROUND,
                 state.mRealCallingUid, NO_PROCESS_UID, true) == PackageManager.PERMISSION_GRANTED) {
-            return new BalVerdict(BAL_ALLOW_PENDING_INTENT,
+            return new BalVerdict(BAL_ALLOW_PERMISSION,
                     /*background*/ false,
                     "realCallingUid has BAL permission.");
         }
@@ -839,18 +843,18 @@
                 || state.mAppSwitchState == APP_SWITCH_FG_ONLY;
         if (Flags.balImproveRealCallerVisibilityCheck()) {
             if (appSwitchAllowedOrFg && state.mRealCallingUidHasAnyVisibleWindow) {
-                return new BalVerdict(BAL_ALLOW_PENDING_INTENT,
+                return new BalVerdict(BAL_ALLOW_VISIBLE_WINDOW,
                         /*background*/ false, "realCallingUid has visible window");
             }
             if (mService.mActiveUids.hasNonAppVisibleWindow(state.mRealCallingUid)) {
-                return new BalVerdict(BAL_ALLOW_PENDING_INTENT,
+                return new BalVerdict(BAL_ALLOW_VISIBLE_WINDOW,
                         /*background*/ false, "realCallingUid has non-app visible window");
             }
         } else {
             // don't abort if the realCallingUid has a visible window
             // TODO(b/171459802): We should check appSwitchAllowed also
             if (state.mRealCallingUidHasAnyVisibleWindow) {
-                return new BalVerdict(BAL_ALLOW_PENDING_INTENT,
+                return new BalVerdict(BAL_ALLOW_VISIBLE_WINDOW,
                         /*background*/ false,
                         "realCallingUid has visible (non-toast) window.");
             }
@@ -860,7 +864,7 @@
         // wasn't allowed to start an activity
         if (state.mForcedBalByPiSender.allowsBackgroundActivityStarts()
                 && state.mIsRealCallingUidPersistentSystemProcess) {
-            return new BalVerdict(BAL_ALLOW_PENDING_INTENT,
+            return new BalVerdict(BAL_ALLOW_ALLOWLISTED_UID,
                     /*background*/ false,
                     "realCallingUid is persistent system process AND intent "
                             + "sender forced to allow.");
@@ -868,7 +872,7 @@
         // don't abort if the realCallingUid is an associated companion app
         if (mService.isAssociatedCompanionApp(
                 UserHandle.getUserId(state.mRealCallingUid), state.mRealCallingUid)) {
-            return new BalVerdict(BAL_ALLOW_PENDING_INTENT,
+            return new BalVerdict(BAL_ALLOW_ALLOWLISTED_COMPONENT,
                     /*background*/ false,
                     "realCallingUid is a companion app.");
         }
@@ -1469,7 +1473,7 @@
                     intent != null ? intent.getComponent().flattenToShortString() : "";
             FrameworkStatsLog.write(FrameworkStatsLog.BAL_ALLOWED,
                     activityName,
-                    code,
+                    BAL_ALLOW_PENDING_INTENT,
                     callingUid,
                     realCallingUid);
         }
diff --git a/services/core/java/com/android/server/wm/DeferredDisplayUpdater.java b/services/core/java/com/android/server/wm/DeferredDisplayUpdater.java
new file mode 100644
index 0000000..975fdc0
--- /dev/null
+++ b/services/core/java/com/android/server/wm/DeferredDisplayUpdater.java
@@ -0,0 +1,345 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.view.WindowManager.TRANSIT_CHANGE;
+
+import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS;
+import static com.android.server.wm.ActivityTaskManagerService.POWER_MODE_REASON_CHANGE_DISPLAY;
+import static com.android.server.wm.utils.DisplayInfoOverrides.WM_OVERRIDE_FIELDS;
+import static com.android.server.wm.utils.DisplayInfoOverrides.copyDisplayInfoFields;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.graphics.Rect;
+import android.view.DisplayInfo;
+import android.window.DisplayAreaInfo;
+import android.window.TransitionRequestInfo;
+import android.window.WindowContainerTransaction;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.display.BrightnessSynchronizer;
+import com.android.internal.protolog.common.ProtoLog;
+import com.android.server.wm.utils.DisplayInfoOverrides.DisplayInfoFieldsUpdater;
+
+import java.util.Arrays;
+import java.util.Objects;
+
+/**
+ * A DisplayUpdater that could defer and queue display updates coming from DisplayManager to
+ * WindowManager. It allows to defer pending display updates if WindowManager is currently not
+ * ready to apply them.
+ * For example, this might happen if there is a Shell transition running and physical display
+ * changed. We can't immediately apply the display updates because we want to start a separate
+ * display change transition. In this case, we will queue all display updates until the current
+ * transition's collection finishes and then apply them afterwards.
+ */
+public class DeferredDisplayUpdater implements DisplayUpdater {
+
+    /**
+     * List of fields that could be deferred before applying to DisplayContent.
+     * This should be kept in sync with {@link DeferredDisplayUpdater#calculateDisplayInfoDiff}
+     */
+    @VisibleForTesting
+    static final DisplayInfoFieldsUpdater DEFERRABLE_FIELDS = (out, override) -> {
+        // Treat unique id and address change as WM-specific display change as we re-query display
+        // settings and parameters based on it which could cause window changes
+        out.uniqueId = override.uniqueId;
+        out.address = override.address;
+
+        // Also apply WM-override fields, since they might produce differences in window hierarchy
+        WM_OVERRIDE_FIELDS.setFields(out, override);
+    };
+
+    private final DisplayContent mDisplayContent;
+
+    @NonNull
+    private final DisplayInfo mNonOverrideDisplayInfo = new DisplayInfo();
+
+    /**
+     * The last known display parameters from DisplayManager, some WM-specific fields in this object
+     * might not be applied to the DisplayContent yet
+     */
+    @Nullable
+    private DisplayInfo mLastDisplayInfo;
+
+    /**
+     * The last DisplayInfo that was applied to DisplayContent, only WM-specific parameters must be
+     * used from this object. This object is used to store old values of DisplayInfo while these
+     * fields are pending to be applied to DisplayContent.
+     */
+    @Nullable
+    private DisplayInfo mLastWmDisplayInfo;
+
+    @NonNull
+    private final DisplayInfo mOutputDisplayInfo = new DisplayInfo();
+
+    public DeferredDisplayUpdater(@NonNull DisplayContent displayContent) {
+        mDisplayContent = displayContent;
+        mNonOverrideDisplayInfo.copyFrom(mDisplayContent.getDisplayInfo());
+    }
+
+    /**
+     * Reads the latest display parameters from the display manager and returns them in a callback.
+     * If there are pending display updates, it will wait for them to finish first and only then it
+     * will call the callback with the latest display parameters.
+     *
+     * @param finishCallback is called when all pending display updates are finished
+     */
+    @Override
+    public void updateDisplayInfo(@NonNull Runnable finishCallback) {
+        // Get the latest display parameters from the DisplayManager
+        final DisplayInfo displayInfo = getCurrentDisplayInfo();
+
+        final int displayInfoDiff = calculateDisplayInfoDiff(mLastDisplayInfo, displayInfo);
+        final boolean physicalDisplayUpdated = isPhysicalDisplayUpdated(mLastDisplayInfo,
+                displayInfo);
+
+        mLastDisplayInfo = displayInfo;
+
+        // Apply whole display info immediately as is if either:
+        // * it is the first display update
+        // * shell transitions are disabled or temporary unavailable
+        if (displayInfoDiff == DIFF_EVERYTHING
+                || !mDisplayContent.mTransitionController.isShellTransitionsEnabled()) {
+            ProtoLog.d(WM_DEBUG_WINDOW_TRANSITIONS,
+                    "DeferredDisplayUpdater: applying DisplayInfo immediately");
+
+            mLastWmDisplayInfo = displayInfo;
+            applyLatestDisplayInfo();
+            finishCallback.run();
+            return;
+        }
+
+        // If there are non WM-specific display info changes, apply only these fields immediately
+        if ((displayInfoDiff & DIFF_NOT_WM_DEFERRABLE) > 0) {
+            ProtoLog.d(WM_DEBUG_WINDOW_TRANSITIONS,
+                    "DeferredDisplayUpdater: partially applying DisplayInfo immediately");
+            applyLatestDisplayInfo();
+        }
+
+        // If there are WM-specific display info changes, apply them through a Shell transition
+        if ((displayInfoDiff & DIFF_WM_DEFERRABLE) > 0) {
+            ProtoLog.d(WM_DEBUG_WINDOW_TRANSITIONS,
+                    "DeferredDisplayUpdater: deferring DisplayInfo update");
+
+            requestDisplayChangeTransition(physicalDisplayUpdated, () -> {
+                // Apply deferrable fields to DisplayContent only when the transition
+                // starts collecting, non-deferrable fields are ignored in mLastWmDisplayInfo
+                mLastWmDisplayInfo = displayInfo;
+                applyLatestDisplayInfo();
+                finishCallback.run();
+            });
+        } else {
+            // There are no WM-specific updates, so we can immediately notify that all display
+            // info changes are applied
+            finishCallback.run();
+        }
+    }
+
+    /**
+     * Requests a display change Shell transition
+     *
+     * @param physicalDisplayUpdated if true also starts remote display change
+     * @param onStartCollect         called when the Shell transition starts collecting
+     */
+    private void requestDisplayChangeTransition(boolean physicalDisplayUpdated,
+            @NonNull Runnable onStartCollect) {
+
+        final Transition transition = new Transition(TRANSIT_CHANGE, /* flags= */ 0,
+                mDisplayContent.mTransitionController,
+                mDisplayContent.mTransitionController.mSyncEngine);
+
+        mDisplayContent.mAtmService.startPowerMode(POWER_MODE_REASON_CHANGE_DISPLAY);
+
+        mDisplayContent.mTransitionController.startCollectOrQueue(transition, deferred -> {
+            final Rect startBounds = new Rect(0, 0, mDisplayContent.mInitialDisplayWidth,
+                    mDisplayContent.mInitialDisplayHeight);
+            final int fromRotation = mDisplayContent.getRotation();
+
+            onStartCollect.run();
+
+            ProtoLog.d(WM_DEBUG_WINDOW_TRANSITIONS,
+                    "DeferredDisplayUpdater: applied DisplayInfo after deferring");
+
+            if (physicalDisplayUpdated) {
+                onDisplayUpdated(transition, fromRotation, startBounds);
+            } else {
+                transition.setAllReady();
+            }
+        });
+    }
+
+    /**
+     * Applies current DisplayInfo to DisplayContent, DisplayContent is merged from two parts:
+     * - non-deferrable fields are set from the most recent values received from DisplayManager
+     * (uses {@link mLastDisplayInfo} field)
+     * - deferrable fields are set from the latest values that we could apply to WM
+     * (uses {@link mLastWmDisplayInfo} field)
+     */
+    private void applyLatestDisplayInfo() {
+        copyDisplayInfoFields(mOutputDisplayInfo, /* base= */ mLastDisplayInfo,
+                /* override= */ mLastWmDisplayInfo, /* fields= */ DEFERRABLE_FIELDS);
+        mDisplayContent.onDisplayInfoUpdated(mOutputDisplayInfo);
+    }
+
+    @NonNull
+    private DisplayInfo getCurrentDisplayInfo() {
+        mDisplayContent.mWmService.mDisplayManagerInternal.getNonOverrideDisplayInfo(
+                mDisplayContent.mDisplayId, mNonOverrideDisplayInfo);
+        return new DisplayInfo(mNonOverrideDisplayInfo);
+    }
+
+    /**
+     * Called when physical display is updated, this could happen e.g. on foldable
+     * devices when the physical underlying display is replaced. This method should be called
+     * when the new display info is already applied to the WM hierarchy.
+     *
+     * @param fromRotation rotation before the display change
+     * @param startBounds  display bounds before the display change
+     */
+    private void onDisplayUpdated(@NonNull Transition transition, int fromRotation,
+            @NonNull Rect startBounds) {
+        final Rect endBounds = new Rect(0, 0, mDisplayContent.mInitialDisplayWidth,
+                mDisplayContent.mInitialDisplayHeight);
+        final int toRotation = mDisplayContent.getRotation();
+
+        final TransitionRequestInfo.DisplayChange displayChange =
+                new TransitionRequestInfo.DisplayChange(mDisplayContent.getDisplayId());
+        displayChange.setStartAbsBounds(startBounds);
+        displayChange.setEndAbsBounds(endBounds);
+        displayChange.setStartRotation(fromRotation);
+        displayChange.setEndRotation(toRotation);
+        displayChange.setPhysicalDisplayChanged(true);
+
+        mDisplayContent.mTransitionController.requestStartTransition(transition,
+                /* startTask= */ null, /* remoteTransition= */ null, displayChange);
+
+        final DisplayAreaInfo newDisplayAreaInfo = mDisplayContent.getDisplayAreaInfo();
+
+        final boolean startedRemoteChange = mDisplayContent.mRemoteDisplayChangeController
+                .performRemoteDisplayChange(fromRotation, toRotation, newDisplayAreaInfo,
+                        transaction -> finishDisplayUpdate(transaction, transition));
+
+        if (!startedRemoteChange) {
+            finishDisplayUpdate(/* wct= */ null, transition);
+        }
+    }
+
+    private void finishDisplayUpdate(@Nullable WindowContainerTransaction wct,
+            @NonNull Transition transition) {
+        if (wct != null) {
+            mDisplayContent.mAtmService.mWindowOrganizerController.applyTransaction(
+                    wct);
+        }
+        transition.setAllReady();
+    }
+
+    private boolean isPhysicalDisplayUpdated(@Nullable DisplayInfo first,
+            @Nullable DisplayInfo second) {
+        if (first == null || second == null) return true;
+        return !Objects.equals(first.uniqueId, second.uniqueId);
+    }
+
+    /**
+     * Diff result: fields are the same
+     */
+    static final int DIFF_NONE = 0;
+
+    /**
+     * Diff result: fields that could be deferred in WM are different
+     */
+    static final int DIFF_WM_DEFERRABLE = 1 << 0;
+
+    /**
+     * Diff result: fields that could not be deferred in WM are different
+     */
+    static final int DIFF_NOT_WM_DEFERRABLE = 1 << 1;
+
+    /**
+     * Diff result: everything is different
+     */
+    static final int DIFF_EVERYTHING = 0XFFFFFFFF;
+
+    @VisibleForTesting
+    static int calculateDisplayInfoDiff(@Nullable DisplayInfo first, @Nullable DisplayInfo second) {
+        int diff = DIFF_NONE;
+
+        if (Objects.equals(first, second)) return diff;
+        if (first == null || second == null) return DIFF_EVERYTHING;
+
+        if (first.layerStack != second.layerStack
+                || first.flags != second.flags
+                || first.type != second.type
+                || first.displayId != second.displayId
+                || first.displayGroupId != second.displayGroupId
+                || !Objects.equals(first.deviceProductInfo, second.deviceProductInfo)
+                || first.modeId != second.modeId
+                || first.renderFrameRate != second.renderFrameRate
+                || first.defaultModeId != second.defaultModeId
+                || first.userPreferredModeId != second.userPreferredModeId
+                || !Arrays.equals(first.supportedModes, second.supportedModes)
+                || first.colorMode != second.colorMode
+                || !Arrays.equals(first.supportedColorModes, second.supportedColorModes)
+                || !Objects.equals(first.hdrCapabilities, second.hdrCapabilities)
+                || !Arrays.equals(first.userDisabledHdrTypes, second.userDisabledHdrTypes)
+                || first.minimalPostProcessingSupported != second.minimalPostProcessingSupported
+                || first.appVsyncOffsetNanos != second.appVsyncOffsetNanos
+                || first.presentationDeadlineNanos != second.presentationDeadlineNanos
+                || first.state != second.state
+                || first.committedState != second.committedState
+                || first.ownerUid != second.ownerUid
+                || !Objects.equals(first.ownerPackageName, second.ownerPackageName)
+                || first.removeMode != second.removeMode
+                || first.getRefreshRate() != second.getRefreshRate()
+                || first.brightnessMinimum != second.brightnessMinimum
+                || first.brightnessMaximum != second.brightnessMaximum
+                || first.brightnessDefault != second.brightnessDefault
+                || first.installOrientation != second.installOrientation
+                || !Objects.equals(first.layoutLimitedRefreshRate, second.layoutLimitedRefreshRate)
+                || !BrightnessSynchronizer.floatEquals(first.hdrSdrRatio, second.hdrSdrRatio)
+                || !first.thermalRefreshRateThrottling.contentEquals(
+                second.thermalRefreshRateThrottling)
+                || !Objects.equals(first.thermalBrightnessThrottlingDataId,
+                second.thermalBrightnessThrottlingDataId)) {
+            diff |= DIFF_NOT_WM_DEFERRABLE;
+        }
+
+        if (first.appWidth != second.appWidth
+                || first.appHeight != second.appHeight
+                || first.smallestNominalAppWidth != second.smallestNominalAppWidth
+                || first.smallestNominalAppHeight != second.smallestNominalAppHeight
+                || first.largestNominalAppWidth != second.largestNominalAppWidth
+                || first.largestNominalAppHeight != second.largestNominalAppHeight
+                || first.logicalWidth != second.logicalWidth
+                || first.logicalHeight != second.logicalHeight
+                || first.physicalXDpi != second.physicalXDpi
+                || first.physicalYDpi != second.physicalYDpi
+                || first.rotation != second.rotation
+                || !Objects.equals(first.displayCutout, second.displayCutout)
+                || first.logicalDensityDpi != second.logicalDensityDpi
+                || !Objects.equals(first.roundedCorners, second.roundedCorners)
+                || !Objects.equals(first.displayShape, second.displayShape)
+                || !Objects.equals(first.uniqueId, second.uniqueId)
+                || !Objects.equals(first.address, second.address)
+        ) {
+            diff |= DIFF_WM_DEFERRABLE;
+        }
+
+        return diff;
+    }
+}
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index a840973..1861b2e 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -276,6 +276,7 @@
 import java.util.Set;
 import java.util.function.Consumer;
 import java.util.function.Predicate;
+import static com.android.window.flags.Flags.deferDisplayUpdates;
 
 /**
  * Utility class for keeping track of the WindowStates and other pertinent contents of a
@@ -545,10 +546,12 @@
     boolean isDefaultDisplay;
 
     /** Detect user tapping outside of current focused task bounds .*/
+    // TODO(b/315321016): Remove once pointer event detection is removed from WM.
     @VisibleForTesting
     final TaskTapPointerEventListener mTapDetector;
 
     /** Detect user tapping outside of current focused root task bounds .*/
+    // TODO(b/315321016): Remove once pointer event detection is removed from WM.
     private Region mTouchExcludeRegion = new Region();
 
     /** Save allocating when calculating rects */
@@ -1158,7 +1161,11 @@
         mWallpaperController.resetLargestDisplay(display);
         display.getDisplayInfo(mDisplayInfo);
         display.getMetrics(mDisplayMetrics);
-        mDisplayUpdater = new ImmediateDisplayUpdater(this);
+        if (deferDisplayUpdates()) {
+            mDisplayUpdater = new DeferredDisplayUpdater(this);
+        } else {
+            mDisplayUpdater = new ImmediateDisplayUpdater(this);
+        }
         mSystemGestureExclusionLimit = mWmService.mConstants.mSystemGestureExclusionLimitDp
                 * mDisplayMetrics.densityDpi / DENSITY_DEFAULT;
         isDefaultDisplay = mDisplayId == DEFAULT_DISPLAY;
@@ -1189,12 +1196,18 @@
                 "PointerEventDispatcher" + mDisplayId, mDisplayId);
         mPointerEventDispatcher = new PointerEventDispatcher(inputChannel);
 
-        // Tap Listeners are supported for:
-        // 1. All physical displays (multi-display).
-        // 2. VirtualDisplays on VR, AA (and everything else).
-        mTapDetector = new TaskTapPointerEventListener(mWmService, this);
-        registerPointerEventListener(mTapDetector);
-        registerPointerEventListener(mWmService.mMousePositionTracker);
+        if (com.android.input.flags.Flags.removePointerEventTrackingInWm()) {
+            mTapDetector = null;
+        } else {
+            // Tap Listeners are supported for:
+            // 1. All physical displays (multi-display).
+            // 2. VirtualDisplays on VR, AA (and everything else).
+            mTapDetector = new TaskTapPointerEventListener(mWmService, this);
+            registerPointerEventListener(mTapDetector);
+        }
+        if (mWmService.mMousePositionTracker != null) {
+            registerPointerEventListener(mWmService.mMousePositionTracker);
+        }
         if (mWmService.mAtmService.getRecentTasks() != null) {
             registerPointerEventListener(
                     mWmService.mAtmService.getRecentTasks().getInputListener());
@@ -3260,6 +3273,12 @@
     }
 
     void updateTouchExcludeRegion() {
+        if (mTapDetector == null) {
+            // The touch exclude region is used to detect the region outside of the focused task
+            // so that the tap detector can detect outside touches. Don't calculate the exclude
+            // region when the tap detector is disabled.
+            return;
+        }
         final Task focusedTask = (mFocusedApp != null ? mFocusedApp.getTask() : null);
         if (focusedTask == null) {
             mTouchExcludeRegion.setEmpty();
@@ -3298,6 +3317,11 @@
     }
 
     private void processTaskForTouchExcludeRegion(Task task, Task focusedTask, int delta) {
+        if (mTapDetector == null) {
+            // The touch exclude region is used to detect the region outside of the focused task
+            // so that the tap detector can detect outside touches. Don't calculate the exclude
+            // region when the tap detector is disabled.
+        }
         final ActivityRecord topVisibleActivity = task.getTopVisibleActivity();
 
         if (topVisibleActivity == null || !topVisibleActivity.hasContentToDisplay()) {
diff --git a/services/core/java/com/android/server/wm/DisplayPolicy.java b/services/core/java/com/android/server/wm/DisplayPolicy.java
index b862d7c..460a68f 100644
--- a/services/core/java/com/android/server/wm/DisplayPolicy.java
+++ b/services/core/java/com/android/server/wm/DisplayPolicy.java
@@ -39,6 +39,7 @@
 import static android.view.WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS;
 import static android.view.WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE;
 import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
+import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_CONSUME_IME_INSETS;
 import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_FORCE_DRAW_BAR_BACKGROUNDS;
 import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_IMMERSIVE_CONFIRMATION_WINDOW;
 import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_INTERCEPT_GLOBAL_DRAG_AND_DROP;
@@ -272,6 +273,8 @@
 
     private @InsetsType int mForciblyShownTypes;
 
+    private boolean mImeInsetsConsumed;
+
     private boolean mIsImmersiveMode;
 
     // The windows we were told about in focusChanged.
@@ -1420,6 +1423,7 @@
         mShowingDream = false;
         mIsFreeformWindowOverlappingWithNavBar = false;
         mForciblyShownTypes = 0;
+        mImeInsetsConsumed = false;
     }
 
     /**
@@ -1481,6 +1485,17 @@
             mForciblyShownTypes |= win.mAttrs.forciblyShownTypes;
         }
 
+        if (win.mImeInsetsConsumed != mImeInsetsConsumed) {
+            win.mImeInsetsConsumed = mImeInsetsConsumed;
+            final WindowState imeWin = mDisplayContent.mInputMethodWindow;
+            if (win.isReadyToDispatchInsetsState() && imeWin != null && imeWin.isVisible()) {
+                win.notifyInsetsChanged();
+            }
+        }
+        if ((attrs.privateFlags & PRIVATE_FLAG_CONSUME_IME_INSETS) != 0 && win.isVisible()) {
+            mImeInsetsConsumed = true;
+        }
+
         if (!affectsSystemUi) {
             return;
         }
@@ -2828,6 +2843,7 @@
             }
         }
         pw.print(prefix); pw.print("mTopIsFullscreen="); pw.println(mTopIsFullscreen);
+        pw.print(prefix); pw.print("mImeInsetsConsumed="); pw.println(mImeInsetsConsumed);
         pw.print(prefix); pw.print("mForceShowNavigationBarEnabled=");
         pw.print(mForceShowNavigationBarEnabled);
         pw.print(" mAllowLockscreenWhenOn="); pw.println(mAllowLockscreenWhenOn);
diff --git a/services/core/java/com/android/server/wm/InsetsPolicy.java b/services/core/java/com/android/server/wm/InsetsPolicy.java
index c089d10..7815679 100644
--- a/services/core/java/com/android/server/wm/InsetsPolicy.java
+++ b/services/core/java/com/android/server/wm/InsetsPolicy.java
@@ -391,13 +391,26 @@
 
             if (originalImeSource != null) {
                 final boolean imeVisibility = w.isRequestedVisible(Type.ime());
-                final InsetsState state = copyState ? new InsetsState(originalState)
+                final InsetsState state = copyState
+                        ? new InsetsState(originalState)
                         : originalState;
                 final InsetsSource imeSource = new InsetsSource(originalImeSource);
                 imeSource.setVisible(imeVisibility);
                 state.addSource(imeSource);
                 return state;
             }
+        } else if (w.mImeInsetsConsumed) {
+            // Set the IME source (if there is one) to be invisible if it has been consumed.
+            final InsetsSource originalImeSource = originalState.peekSource(ID_IME);
+            if (originalImeSource != null && originalImeSource.isVisible()) {
+                final InsetsState state = copyState
+                        ? new InsetsState(originalState)
+                        : originalState;
+                final InsetsSource imeSource = new InsetsSource(originalImeSource);
+                imeSource.setVisible(false);
+                state.addSource(imeSource);
+                return state;
+            }
         }
         return originalState;
     }
diff --git a/services/core/java/com/android/server/wm/RefreshRatePolicy.java b/services/core/java/com/android/server/wm/RefreshRatePolicy.java
index 23c135a..03574029 100644
--- a/services/core/java/com/android/server/wm/RefreshRatePolicy.java
+++ b/services/core/java/com/android/server/wm/RefreshRatePolicy.java
@@ -26,6 +26,7 @@
 import android.view.Display.Mode;
 import android.view.DisplayInfo;
 import android.view.Surface;
+import android.view.SurfaceControl;
 import android.view.SurfaceControl.RefreshRateRange;
 
 import java.util.HashMap;
@@ -191,26 +192,35 @@
     public static class FrameRateVote {
         float mRefreshRate;
         @Surface.FrameRateCompatibility int mCompatibility;
+        @SurfaceControl.FrameRateSelectionStrategy int mSelectionStrategy;
 
-        FrameRateVote(float refreshRate, @Surface.FrameRateCompatibility int compatibility) {
-            update(refreshRate, compatibility);
+
+
+        FrameRateVote(float refreshRate, @Surface.FrameRateCompatibility int compatibility,
+                      @SurfaceControl.FrameRateSelectionStrategy int selectionStrategy) {
+            update(refreshRate, compatibility, selectionStrategy);
         }
 
         FrameRateVote() {
             reset();
         }
 
-        boolean update(float refreshRate, @Surface.FrameRateCompatibility int compatibility) {
-            if (!refreshRateEquals(refreshRate) || mCompatibility != compatibility) {
+        boolean update(float refreshRate, @Surface.FrameRateCompatibility int compatibility,
+                       @SurfaceControl.FrameRateSelectionStrategy int selectionStrategy) {
+            if (!refreshRateEquals(refreshRate)
+                    || mCompatibility != compatibility
+                    || mSelectionStrategy != selectionStrategy) {
                 mRefreshRate = refreshRate;
                 mCompatibility = compatibility;
+                mSelectionStrategy = selectionStrategy;
                 return true;
             }
             return false;
         }
 
         boolean reset() {
-            return update(0, Surface.FRAME_RATE_COMPATIBILITY_DEFAULT);
+            return update(0, Surface.FRAME_RATE_COMPATIBILITY_DEFAULT,
+                    SurfaceControl.FRAME_RATE_SELECTION_STRATEGY_PROPAGATE);
         }
 
         @Override
@@ -221,17 +231,20 @@
 
             FrameRateVote other = (FrameRateVote) o;
             return refreshRateEquals(other.mRefreshRate)
-                    && mCompatibility == other.mCompatibility;
+                    && mCompatibility == other.mCompatibility
+                    && mSelectionStrategy == other.mSelectionStrategy;
         }
 
         @Override
         public int hashCode() {
-            return Objects.hash(mRefreshRate, mCompatibility);
+            return Objects.hash(mRefreshRate, mCompatibility, mSelectionStrategy);
+
         }
 
         @Override
         public String toString() {
-            return "mRefreshRate=" + mRefreshRate + ", mCompatibility=" + mCompatibility;
+            return "mRefreshRate=" + mRefreshRate + ", mCompatibility=" + mCompatibility
+                    + ", mSelectionStrategy=" + mSelectionStrategy;
         }
 
         private boolean refreshRateEquals(float refreshRate) {
@@ -265,7 +278,8 @@
                 for (Display.Mode mode : mDisplayInfo.supportedModes) {
                     if (preferredModeId == mode.getModeId()) {
                         return w.mFrameRateVote.update(mode.getRefreshRate(),
-                                Surface.FRAME_RATE_COMPATIBILITY_EXACT);
+                                Surface.FRAME_RATE_COMPATIBILITY_EXACT,
+                                SurfaceControl.FRAME_RATE_SELECTION_STRATEGY_OVERRIDE_CHILDREN);
                     }
                 }
             }
@@ -273,7 +287,8 @@
 
         if (w.mAttrs.preferredRefreshRate > 0) {
             return w.mFrameRateVote.update(w.mAttrs.preferredRefreshRate,
-                    Surface.FRAME_RATE_COMPATIBILITY_DEFAULT);
+                    Surface.FRAME_RATE_COMPATIBILITY_DEFAULT,
+                    SurfaceControl.FRAME_RATE_SELECTION_STRATEGY_OVERRIDE_CHILDREN);
         }
 
         // If the app didn't set a preferred mode id or refresh rate, but it is part of the deny
@@ -282,7 +297,8 @@
             final String packageName = w.getOwningPackage();
             if (mHighRefreshRateDenylist.isDenylisted(packageName)) {
                 return w.mFrameRateVote.update(mLowRefreshRateMode.getRefreshRate(),
-                        Surface.FRAME_RATE_COMPATIBILITY_EXACT);
+                        Surface.FRAME_RATE_COMPATIBILITY_EXACT,
+                        SurfaceControl.FRAME_RATE_SELECTION_STRATEGY_OVERRIDE_CHILDREN);
             }
         }
 
diff --git a/services/core/java/com/android/server/wm/TaskTapPointerEventListener.java b/services/core/java/com/android/server/wm/TaskTapPointerEventListener.java
index 7d22b74..ac244c7 100644
--- a/services/core/java/com/android/server/wm/TaskTapPointerEventListener.java
+++ b/services/core/java/com/android/server/wm/TaskTapPointerEventListener.java
@@ -45,6 +45,10 @@
 
     public TaskTapPointerEventListener(WindowManagerService service,
             DisplayContent displayContent) {
+        // TODO(b/315321016): Remove this class when the flag rollout is complete.
+        if (com.android.input.flags.Flags.removePointerEventTrackingInWm()) {
+            throw new IllegalStateException("TaskTapPointerEventListener should not be used!");
+        }
         mService = service;
         mDisplayContent = displayContent;
     }
diff --git a/services/core/java/com/android/server/wm/TrustedPresentationListenerController.java b/services/core/java/com/android/server/wm/TrustedPresentationListenerController.java
new file mode 100644
index 0000000..1688a1a
--- /dev/null
+++ b/services/core/java/com/android/server/wm/TrustedPresentationListenerController.java
@@ -0,0 +1,448 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm;
+
+import static android.graphics.Matrix.MSCALE_X;
+import static android.graphics.Matrix.MSCALE_Y;
+import static android.graphics.Matrix.MSKEW_X;
+import static android.graphics.Matrix.MSKEW_Y;
+
+import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_TPL;
+
+import android.graphics.Matrix;
+import android.graphics.Rect;
+import android.graphics.RectF;
+import android.graphics.Region;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.util.ArrayMap;
+import android.util.ArraySet;
+import android.util.IntArray;
+import android.util.Pair;
+import android.util.Size;
+import android.view.InputWindowHandle;
+import android.window.ITrustedPresentationListener;
+import android.window.TrustedPresentationThresholds;
+import android.window.WindowInfosListener;
+
+import com.android.internal.protolog.common.ProtoLog;
+import com.android.server.wm.utils.RegionUtils;
+
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.Optional;
+
+/**
+ * Class to handle TrustedPresentationListener registrations in a thread safe manner. This class
+ * also takes care of cleaning up listeners when the remote process dies.
+ */
+public class TrustedPresentationListenerController {
+
+    // Should only be accessed by the posting to the handler
+    private class Listeners {
+        private final class ListenerDeathRecipient implements IBinder.DeathRecipient {
+            IBinder mListenerBinder;
+            int mInstances;
+
+            ListenerDeathRecipient(IBinder listenerBinder) {
+                mListenerBinder = listenerBinder;
+                mInstances = 0;
+                try {
+                    mListenerBinder.linkToDeath(this, 0);
+                } catch (RemoteException ignore) {
+                }
+            }
+
+            void addInstance() {
+                mInstances++;
+            }
+
+            // return true if there are no instances alive
+            boolean removeInstance() {
+                mInstances--;
+                if (mInstances > 0) {
+                    return false;
+                }
+                mListenerBinder.unlinkToDeath(this, 0);
+                return true;
+            }
+
+            public void binderDied() {
+                mHandler.post(() -> {
+                    mUniqueListeners.remove(mListenerBinder);
+                    removeListeners(mListenerBinder, Optional.empty());
+                });
+            }
+        }
+
+        // tracks binder deaths for cleanup
+        ArrayMap<IBinder, ListenerDeathRecipient> mUniqueListeners = new ArrayMap<>();
+        ArrayMap<IBinder /*window*/, ArrayList<TrustedPresentationInfo>> mWindowToListeners =
+                new ArrayMap<>();
+
+        void register(IBinder window, ITrustedPresentationListener listener,
+                TrustedPresentationThresholds thresholds, int id) {
+            var listenersForWindow = mWindowToListeners.computeIfAbsent(window,
+                    iBinder -> new ArrayList<>());
+            listenersForWindow.add(new TrustedPresentationInfo(thresholds, id, listener));
+
+            // register death listener
+            var listenerBinder = listener.asBinder();
+            var deathRecipient = mUniqueListeners.computeIfAbsent(listenerBinder,
+                    ListenerDeathRecipient::new);
+            deathRecipient.addInstance();
+        }
+
+        void unregister(ITrustedPresentationListener trustedPresentationListener, int id) {
+            var listenerBinder = trustedPresentationListener.asBinder();
+            var deathRecipient = mUniqueListeners.get(listenerBinder);
+            if (deathRecipient == null) {
+                ProtoLog.e(WM_DEBUG_TPL, "unregister failed, couldn't find"
+                        + " deathRecipient for %s with id=%d", trustedPresentationListener, id);
+                return;
+            }
+
+            if (deathRecipient.removeInstance()) {
+                mUniqueListeners.remove(listenerBinder);
+            }
+            removeListeners(listenerBinder, Optional.of(id));
+        }
+
+        boolean isEmpty() {
+            return mWindowToListeners.isEmpty();
+        }
+
+        ArrayList<TrustedPresentationInfo> get(IBinder windowToken) {
+            return mWindowToListeners.get(windowToken);
+        }
+
+        private void removeListeners(IBinder listenerBinder, Optional<Integer> id) {
+            for (int i = mWindowToListeners.size() - 1; i >= 0; i--) {
+                var listeners = mWindowToListeners.valueAt(i);
+                for (int j = listeners.size() - 1; j >= 0; j--) {
+                    var listener = listeners.get(j);
+                    if (listener.mListener.asBinder() == listenerBinder && (id.isEmpty()
+                            || listener.mId == id.get())) {
+                        listeners.remove(j);
+                    }
+                }
+                if (listeners.isEmpty()) {
+                    mWindowToListeners.removeAt(i);
+                }
+            }
+        }
+    }
+
+    private final Object mHandlerThreadLock = new Object();
+    private HandlerThread mHandlerThread;
+    private Handler mHandler;
+
+    private WindowInfosListener mWindowInfosListener;
+
+    Listeners mRegisteredListeners = new Listeners();
+
+    private InputWindowHandle[] mLastWindowHandles;
+
+    private final Object mIgnoredWindowTokensLock = new Object();
+
+    private final ArraySet<IBinder> mIgnoredWindowTokens = new ArraySet<>();
+
+    private void startHandlerThreadIfNeeded() {
+        synchronized (mHandlerThreadLock) {
+            if (mHandler == null) {
+                mHandlerThread = new HandlerThread("WindowInfosListenerForTpl");
+                mHandlerThread.start();
+                mHandler = new Handler(mHandlerThread.getLooper());
+            }
+        }
+    }
+
+    void addIgnoredWindowTokens(IBinder token) {
+        synchronized (mIgnoredWindowTokensLock) {
+            mIgnoredWindowTokens.add(token);
+        }
+    }
+
+    void removeIgnoredWindowTokens(IBinder token) {
+        synchronized (mIgnoredWindowTokensLock) {
+            mIgnoredWindowTokens.remove(token);
+        }
+    }
+
+    void registerListener(IBinder window, ITrustedPresentationListener listener,
+            TrustedPresentationThresholds thresholds, int id) {
+        startHandlerThreadIfNeeded();
+        mHandler.post(() -> {
+            ProtoLog.d(WM_DEBUG_TPL, "Registering listener=%s with id=%d for window=%s with %s",
+                    listener, id, window, thresholds);
+
+            mRegisteredListeners.register(window, listener, thresholds, id);
+            registerWindowInfosListener();
+            // Update the initial state for the new registered listener
+            computeTpl(mLastWindowHandles);
+        });
+    }
+
+    void unregisterListener(ITrustedPresentationListener listener, int id) {
+        startHandlerThreadIfNeeded();
+        mHandler.post(() -> {
+            ProtoLog.d(WM_DEBUG_TPL, "Unregistering listener=%s with id=%d",
+                    listener, id);
+
+            mRegisteredListeners.unregister(listener, id);
+            if (mRegisteredListeners.isEmpty()) {
+                unregisterWindowInfosListener();
+            }
+        });
+    }
+
+    void dump(PrintWriter pw) {
+        final String innerPrefix = "  ";
+        pw.println("TrustedPresentationListenerController:");
+        pw.println(innerPrefix + "Active unique listeners ("
+                + mRegisteredListeners.mUniqueListeners.size() + "):");
+        for (int i = 0; i < mRegisteredListeners.mWindowToListeners.size(); i++) {
+            pw.println(
+                    innerPrefix + "  window=" + mRegisteredListeners.mWindowToListeners.keyAt(i));
+            final var listeners = mRegisteredListeners.mWindowToListeners.valueAt(i);
+            for (int j = 0; j < listeners.size(); j++) {
+                final var listener = listeners.get(j);
+                pw.println(innerPrefix + innerPrefix + "  listener=" + listener.mListener.asBinder()
+                        + " id=" + listener.mId
+                        + " thresholds=" + listener.mThresholds);
+            }
+        }
+    }
+
+    private void registerWindowInfosListener() {
+        if (mWindowInfosListener != null) {
+            return;
+        }
+
+        mWindowInfosListener = new WindowInfosListener() {
+            @Override
+            public void onWindowInfosChanged(InputWindowHandle[] windowHandles,
+                    DisplayInfo[] displayInfos) {
+                mHandler.post(() -> computeTpl(windowHandles));
+            }
+        };
+        mLastWindowHandles = mWindowInfosListener.register().first;
+    }
+
+    private void unregisterWindowInfosListener() {
+        if (mWindowInfosListener == null) {
+            return;
+        }
+
+        mWindowInfosListener.unregister();
+        mWindowInfosListener = null;
+        mLastWindowHandles = null;
+    }
+
+    private void computeTpl(InputWindowHandle[] windowHandles) {
+        mLastWindowHandles = windowHandles;
+        if (mLastWindowHandles == null || mLastWindowHandles.length == 0
+                || mRegisteredListeners.isEmpty()) {
+            return;
+        }
+
+        Rect tmpRect = new Rect();
+        Matrix tmpInverseMatrix = new Matrix();
+        float[] tmpMatrix = new float[9];
+        Region coveredRegionsAbove = new Region();
+        long currTimeMs = System.currentTimeMillis();
+        ProtoLog.v(WM_DEBUG_TPL, "Checking %d windows", mLastWindowHandles.length);
+
+        ArrayMap<ITrustedPresentationListener, Pair<IntArray, IntArray>> listenerUpdates =
+                new ArrayMap<>();
+        ArraySet<IBinder> ignoredWindowTokens;
+        synchronized (mIgnoredWindowTokensLock) {
+            ignoredWindowTokens = new ArraySet<>(mIgnoredWindowTokens);
+        }
+        for (var windowHandle : mLastWindowHandles) {
+            if (ignoredWindowTokens.contains(windowHandle.getWindowToken())) {
+                ProtoLog.v(WM_DEBUG_TPL, "Skipping %s", windowHandle.name);
+                continue;
+            }
+            tmpRect.set(windowHandle.frame);
+            var listeners = mRegisteredListeners.get(windowHandle.getWindowToken());
+            if (listeners != null) {
+                Region region = new Region();
+                region.op(tmpRect, coveredRegionsAbove, Region.Op.DIFFERENCE);
+                windowHandle.transform.invert(tmpInverseMatrix);
+                tmpInverseMatrix.getValues(tmpMatrix);
+                float scaleX = (float) Math.sqrt(tmpMatrix[MSCALE_X] * tmpMatrix[MSCALE_X]
+                        + tmpMatrix[MSKEW_X] * tmpMatrix[MSKEW_X]);
+                float scaleY = (float) Math.sqrt(tmpMatrix[MSCALE_Y] * tmpMatrix[MSCALE_Y]
+                        + tmpMatrix[MSKEW_Y] * tmpMatrix[MSKEW_Y]);
+
+                float fractionRendered = computeFractionRendered(region, new RectF(tmpRect),
+                        windowHandle.contentSize,
+                        scaleX, scaleY);
+
+                checkIfInThreshold(listeners, listenerUpdates, fractionRendered, windowHandle.alpha,
+                        currTimeMs);
+            }
+
+            coveredRegionsAbove.op(tmpRect, Region.Op.UNION);
+            ProtoLog.v(WM_DEBUG_TPL, "coveredRegionsAbove updated with %s frame:%s region:%s",
+                    windowHandle.name, tmpRect.toShortString(), coveredRegionsAbove);
+        }
+
+        for (int i = 0; i < listenerUpdates.size(); i++) {
+            var updates = listenerUpdates.valueAt(i);
+            var listener = listenerUpdates.keyAt(i);
+            try {
+                listener.onTrustedPresentationChanged(updates.first.toArray(),
+                        updates.second.toArray());
+            } catch (RemoteException ignore) {
+            }
+        }
+    }
+
+    private void addListenerUpdate(
+            ArrayMap<ITrustedPresentationListener, Pair<IntArray, IntArray>> listenerUpdates,
+            ITrustedPresentationListener listener, int id, boolean presentationState) {
+        var updates = listenerUpdates.get(listener);
+        if (updates == null) {
+            updates = new Pair<>(new IntArray(), new IntArray());
+            listenerUpdates.put(listener, updates);
+        }
+        if (presentationState) {
+            updates.first.add(id);
+        } else {
+            updates.second.add(id);
+        }
+    }
+
+
+    private void checkIfInThreshold(
+            ArrayList<TrustedPresentationInfo> listeners,
+            ArrayMap<ITrustedPresentationListener, Pair<IntArray, IntArray>> listenerUpdates,
+            float fractionRendered, float alpha, long currTimeMs) {
+        ProtoLog.v(WM_DEBUG_TPL, "checkIfInThreshold fractionRendered=%f alpha=%f currTimeMs=%d",
+                fractionRendered, alpha, currTimeMs);
+        for (int i = 0; i < listeners.size(); i++) {
+            var trustedPresentationInfo = listeners.get(i);
+            var listener = trustedPresentationInfo.mListener;
+            boolean lastState = trustedPresentationInfo.mLastComputedTrustedPresentationState;
+            boolean newState =
+                    (alpha >= trustedPresentationInfo.mThresholds.minAlpha) && (fractionRendered
+                            >= trustedPresentationInfo.mThresholds.minFractionRendered);
+            trustedPresentationInfo.mLastComputedTrustedPresentationState = newState;
+
+            ProtoLog.v(WM_DEBUG_TPL,
+                    "lastState=%s newState=%s alpha=%f minAlpha=%f fractionRendered=%f "
+                            + "minFractionRendered=%f",
+                    lastState, newState, alpha, trustedPresentationInfo.mThresholds.minAlpha,
+                    fractionRendered, trustedPresentationInfo.mThresholds.minFractionRendered);
+
+            if (lastState && !newState) {
+                // We were in the trusted presentation state, but now we left it,
+                // emit the callback if needed
+                if (trustedPresentationInfo.mLastReportedTrustedPresentationState) {
+                    trustedPresentationInfo.mLastReportedTrustedPresentationState = false;
+                    addListenerUpdate(listenerUpdates, listener,
+                            trustedPresentationInfo.mId, /*presentationState*/ false);
+                    ProtoLog.d(WM_DEBUG_TPL, "Adding untrusted state listener=%s with id=%d",
+                            listener, trustedPresentationInfo.mId);
+                }
+                // Reset the timer
+                trustedPresentationInfo.mEnteredTrustedPresentationStateTime = -1;
+            } else if (!lastState && newState) {
+                // We were not in the trusted presentation state, but we entered it, begin the timer
+                // and make sure this gets called at least once more!
+                trustedPresentationInfo.mEnteredTrustedPresentationStateTime = currTimeMs;
+                mHandler.postDelayed(() -> {
+                    computeTpl(mLastWindowHandles);
+                }, (long) (trustedPresentationInfo.mThresholds.stabilityRequirementMs * 1.5));
+            }
+
+            // Has the timer elapsed, but we are still in the state? Emit a callback if needed
+            if (!trustedPresentationInfo.mLastReportedTrustedPresentationState && newState && (
+                    currTimeMs - trustedPresentationInfo.mEnteredTrustedPresentationStateTime
+                            > trustedPresentationInfo.mThresholds.stabilityRequirementMs)) {
+                trustedPresentationInfo.mLastReportedTrustedPresentationState = true;
+                addListenerUpdate(listenerUpdates, listener,
+                        trustedPresentationInfo.mId, /*presentationState*/ true);
+                ProtoLog.d(WM_DEBUG_TPL, "Adding trusted state listener=%s with id=%d",
+                        listener, trustedPresentationInfo.mId);
+            }
+        }
+    }
+
+    private float computeFractionRendered(Region visibleRegion, RectF screenBounds,
+            Size contentSize,
+            float sx, float sy) {
+        ProtoLog.v(WM_DEBUG_TPL,
+                "computeFractionRendered: visibleRegion=%s screenBounds=%s contentSize=%s "
+                        + "scale=%f,%f",
+                visibleRegion, screenBounds, contentSize, sx, sy);
+
+        if (contentSize.getWidth() == 0 || contentSize.getHeight() == 0) {
+            return -1;
+        }
+        if (screenBounds.width() == 0 || screenBounds.height() == 0) {
+            return -1;
+        }
+
+        float fractionRendered = Math.min(sx * sy, 1.0f);
+        ProtoLog.v(WM_DEBUG_TPL, "fractionRendered scale=%f", fractionRendered);
+
+        float boundsOverSourceW = screenBounds.width() / (float) contentSize.getWidth();
+        float boundsOverSourceH = screenBounds.height() / (float) contentSize.getHeight();
+        fractionRendered *= boundsOverSourceW * boundsOverSourceH;
+        ProtoLog.v(WM_DEBUG_TPL, "fractionRendered boundsOverSource=%f", fractionRendered);
+        // Compute the size of all the rects since they may be disconnected.
+        float[] visibleSize = new float[1];
+        RegionUtils.forEachRect(visibleRegion, rect -> {
+            float size = rect.width() * rect.height();
+            visibleSize[0] += size;
+        });
+
+        fractionRendered *= visibleSize[0] / (screenBounds.width() * screenBounds.height());
+        return fractionRendered;
+    }
+
+    private static class TrustedPresentationInfo {
+        boolean mLastComputedTrustedPresentationState = false;
+        boolean mLastReportedTrustedPresentationState = false;
+        long mEnteredTrustedPresentationStateTime = -1;
+        final TrustedPresentationThresholds mThresholds;
+
+        final ITrustedPresentationListener mListener;
+        final int mId;
+
+        private TrustedPresentationInfo(TrustedPresentationThresholds thresholds, int id,
+                ITrustedPresentationListener listener) {
+            mThresholds = thresholds;
+            mId = id;
+            mListener = listener;
+            checkValid(thresholds);
+        }
+
+        private void checkValid(TrustedPresentationThresholds thresholds) {
+            if (thresholds.minAlpha <= 0 || thresholds.minFractionRendered <= 0
+                    || thresholds.stabilityRequirementMs < 1) {
+                throw new IllegalArgumentException(
+                        "TrustedPresentationThresholds values are invalid");
+            }
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 72632dc..10dd334 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -303,9 +303,11 @@
 import android.window.ClientWindowFrames;
 import android.window.ISurfaceSyncGroupCompletedListener;
 import android.window.ITaskFpsCallback;
+import android.window.ITrustedPresentationListener;
 import android.window.ScreenCapture;
 import android.window.SystemPerformanceHinter;
 import android.window.TaskSnapshot;
+import android.window.TrustedPresentationThresholds;
 import android.window.WindowContainerToken;
 import android.window.WindowContextInfo;
 
@@ -764,6 +766,9 @@
     private final SurfaceSyncGroupController mSurfaceSyncGroupController =
             new SurfaceSyncGroupController();
 
+    final TrustedPresentationListenerController mTrustedPresentationListenerController =
+            new TrustedPresentationListenerController();
+
     @VisibleForTesting
     final class SettingsObserver extends ContentObserver {
         private final Uri mDisplayInversionEnabledUri =
@@ -7171,6 +7176,7 @@
                 pw.println(separator);
             }
             mSystemPerformanceHinter.dump(pw, "");
+            mTrustedPresentationListenerController.dump(pw);
         }
     }
 
@@ -7302,7 +7308,12 @@
         }
     }
 
-    MousePositionTracker mMousePositionTracker = new MousePositionTracker();
+    // The mouse position tracker will be obsolete after the Pointer Icon Refactor.
+    // TODO(b/293587049): Remove after the refactoring is fully rolled out.
+    @Nullable
+    final MousePositionTracker mMousePositionTracker =
+            com.android.input.flags.Flags.enablePointerChoreographer() ? null
+                    : new MousePositionTracker();
 
     private static class MousePositionTracker implements PointerEventListener {
         private boolean mLatestEventWasMouse;
@@ -7354,6 +7365,9 @@
     };
 
     void updatePointerIcon(IWindow client) {
+        if (mMousePositionTracker == null) {
+            return;
+        }
         int pointerDisplayId;
         float mouseX, mouseY;
 
@@ -7400,6 +7414,9 @@
     }
 
     void restorePointerIconLocked(DisplayContent displayContent, float latestX, float latestY) {
+        if (mMousePositionTracker == null) {
+            return;
+        }
         // Mouse position tracker has not been getting updates while dragging, update it now.
         if (!mMousePositionTracker.updatePosition(
                 displayContent.getDisplayId(), latestX, latestY)) {
@@ -7423,6 +7440,9 @@
         }
     }
     void setMousePointerDisplayId(int displayId) {
+        if (mMousePositionTracker == null) {
+            return;
+        }
         mMousePositionTracker.setPointerDisplayId(displayId);
     }
 
@@ -9771,4 +9791,17 @@
             Binder.restoreCallingIdentity(origId);
         }
     }
+
+    @Override
+    public void registerTrustedPresentationListener(IBinder window,
+            ITrustedPresentationListener listener,
+            TrustedPresentationThresholds thresholds, int id) {
+        mTrustedPresentationListenerController.registerListener(window, listener, thresholds, id);
+    }
+
+    @Override
+    public void unregisterTrustedPresentationListener(ITrustedPresentationListener listener,
+            int id) {
+        mTrustedPresentationListenerController.unregisterListener(listener, id);
+    }
 }
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index b890a9e..4e9d23c 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -178,6 +178,7 @@
 import static com.android.server.wm.WindowStateProto.VIEW_VISIBILITY;
 import static com.android.server.wm.WindowStateProto.WINDOW_CONTAINER;
 import static com.android.server.wm.WindowStateProto.WINDOW_FRAMES;
+import static com.android.window.flags.Flags.explicitRefreshRateHints;
 import static com.android.window.flags.Flags.secureWindowState;
 import static com.android.window.flags.Flags.surfaceTrustedOverlay;
 
@@ -668,6 +669,12 @@
     boolean mSeamlesslyRotated = false;
 
     /**
+     * Whether the IME insets have been consumed. If {@code true}, this window won't be able to
+     * receive visible IME insets; {@code false}, otherwise.
+     */
+    boolean mImeInsetsConsumed = false;
+
+    /**
      * The insets state of sources provided by windows above the current window.
      */
     final InsetsState mAboveInsetsState = new InsetsState();
@@ -1189,6 +1196,11 @@
             ProtoLog.v(WM_DEBUG_ADD_REMOVE, "Adding %s to %s", this, parentWindow);
             parentWindow.addChild(this, sWindowSubLayerComparator);
         }
+
+        if (token.mRoundedCornerOverlay) {
+            mWmService.mTrustedPresentationListenerController.addIgnoredWindowTokens(
+                    getWindowToken());
+        }
     }
 
     @Override
@@ -1487,7 +1499,9 @@
 
             if (insetsChanged) {
                 mWindowFrames.setInsetsChanged(false);
-                mWmService.mWindowsInsetsChanged--;
+                if (mWmService.mWindowsInsetsChanged > 0) {
+                    mWmService.mWindowsInsetsChanged--;
+                }
                 if (mWmService.mWindowsInsetsChanged == 0) {
                     mWmService.mH.removeMessages(WindowManagerService.H.INSETS_CHANGED);
                 }
@@ -2393,6 +2407,9 @@
         }
 
         mWmService.postWindowRemoveCleanupLocked(this);
+
+        mWmService.mTrustedPresentationListenerController.removeIgnoredWindowTokens(
+                getWindowToken());
     }
 
     @Override
@@ -3796,13 +3813,15 @@
      */
     void notifyInsetsChanged() {
         ProtoLog.d(WM_DEBUG_WINDOW_INSETS, "notifyInsetsChanged for %s ", this);
-        mWindowFrames.setInsetsChanged(true);
+        if (!mWindowFrames.hasInsetsChanged()) {
+            mWindowFrames.setInsetsChanged(true);
 
-        // If the new InsetsState won't be dispatched before releasing WM lock, the following
-        // message will be executed.
-        mWmService.mWindowsInsetsChanged++;
-        mWmService.mH.removeMessages(WindowManagerService.H.INSETS_CHANGED);
-        mWmService.mH.sendEmptyMessage(WindowManagerService.H.INSETS_CHANGED);
+            // If the new InsetsState won't be dispatched before releasing WM lock, the following
+            // message will be executed.
+            mWmService.mWindowsInsetsChanged++;
+            mWmService.mH.removeMessages(WindowManagerService.H.INSETS_CHANGED);
+            mWmService.mH.sendEmptyMessage(WindowManagerService.H.INSETS_CHANGED);
+        }
 
         final WindowContainer p = getParent();
         if (p != null) {
@@ -4192,6 +4211,7 @@
         } else {
             pw.print("null");
         }
+        pw.println();
 
         if (mXOffset != 0 || mYOffset != 0) {
             pw.println(prefix + "mXOffset=" + mXOffset + " mYOffset=" + mYOffset);
@@ -4225,6 +4245,9 @@
         if (computeDragResizing()) {
             pw.println(prefix + "computeDragResizing=" + computeDragResizing());
         }
+        if (mImeInsetsConsumed) {
+            pw.println(prefix + "mImeInsetsConsumed=true");
+        }
         pw.println(prefix + "isOnScreen=" + isOnScreen());
         pw.println(prefix + "isVisible=" + isVisible());
         pw.println(prefix + "keepClearAreas: restricted=" + mKeepClearAreas
@@ -5185,9 +5208,13 @@
 
         boolean voteChanged = refreshRatePolicy.updateFrameRateVote(this);
         if (voteChanged) {
-            getPendingTransaction().setFrameRate(
-                    mSurfaceControl, mFrameRateVote.mRefreshRate,
-                    mFrameRateVote.mCompatibility, Surface.CHANGE_FRAME_RATE_ALWAYS);
+            getPendingTransaction()
+                    .setFrameRate(mSurfaceControl, mFrameRateVote.mRefreshRate,
+                        mFrameRateVote.mCompatibility, Surface.CHANGE_FRAME_RATE_ALWAYS);
+            if (explicitRefreshRateHints()) {
+                getPendingTransaction().setFrameRateSelectionStrategy(mSurfaceControl,
+                        mFrameRateVote.mSelectionStrategy);
+            }
 
         }
     }
diff --git a/services/core/java/com/android/server/wm/utils/DisplayInfoOverrides.java b/services/core/java/com/android/server/wm/utils/DisplayInfoOverrides.java
index 8c8f6a6..193a0c8 100644
--- a/services/core/java/com/android/server/wm/utils/DisplayInfoOverrides.java
+++ b/services/core/java/com/android/server/wm/utils/DisplayInfoOverrides.java
@@ -30,7 +30,7 @@
      * Set of DisplayInfo fields that are overridden in DisplayManager using values from
      * WindowManager
      */
-    public static final DisplayInfoFields WM_OVERRIDE_FIELDS = (out, source) -> {
+    public static final DisplayInfoFieldsUpdater WM_OVERRIDE_FIELDS = (out, source) -> {
         out.appWidth = source.appWidth;
         out.appHeight = source.appHeight;
         out.smallestNominalAppWidth = source.smallestNominalAppWidth;
@@ -55,7 +55,7 @@
     public static void copyDisplayInfoFields(@NonNull DisplayInfo out,
             @NonNull DisplayInfo base,
             @Nullable DisplayInfo override,
-            @NonNull DisplayInfoFields fields) {
+            @NonNull DisplayInfoFieldsUpdater fields) {
         out.copyFrom(base);
 
         if (override != null) {
@@ -66,7 +66,7 @@
     /**
      * Callback interface that allows to specify a subset of fields of DisplayInfo object
      */
-    public interface DisplayInfoFields {
+    public interface DisplayInfoFieldsUpdater {
         /**
          * Copies a subset of fields from {@param source} to {@param out}
          *
diff --git a/services/core/jni/Android.bp b/services/core/jni/Android.bp
index 24ee163..b19f3d8 100644
--- a/services/core/jni/Android.bp
+++ b/services/core/jni/Android.bp
@@ -187,7 +187,7 @@
         "android.hardware.power.stats@1.0",
         "android.hardware.power.stats-V1-ndk",
         "android.hardware.thermal@1.0",
-        "android.hardware.thermal-V1-ndk",
+        "android.hardware.thermal-V2-ndk",
         "android.hardware.tv.input@1.0",
         "android.hardware.tv.input-V2-ndk",
         "android.hardware.vibrator-V2-cpp",
diff --git a/services/core/jni/OWNERS b/services/core/jni/OWNERS
index 0e45f61..061fe0f 100644
--- a/services/core/jni/OWNERS
+++ b/services/core/jni/OWNERS
@@ -30,3 +30,6 @@
 per-file com_android_server_vibrator_* = file:/services/core/java/com/android/server/vibrator/OWNERS
 per-file com_android_server_am_CachedAppOptimizer.cpp = timmurray@google.com, edgararriaga@google.com, dualli@google.com, carmenjackson@google.com, philipcuadra@google.com
 per-file com_android_server_companion_virtual_InputController.cpp = file:/services/companion/java/com/android/server/companion/virtual/OWNERS
+
+# Bug component : 158088 = per-file com_android_server_utils_AnrTimer*.java
+per-file com_android_server_utils_AnrTimer*.java = file:/PERFORMANCE_OWNERS
diff --git a/services/manifest_services.xml b/services/manifest_services.xml
index e2fdfe9..7638915 100644
--- a/services/manifest_services.xml
+++ b/services/manifest_services.xml
@@ -4,14 +4,4 @@
         <version>1</version>
         <fqname>IAltitudeService/default</fqname>
     </hal>
-    <hal format="aidl">
-        <name>android.frameworks.vibrator</name>
-        <version>1</version>
-        <fqname>IVibratorController/default</fqname>
-    </hal>
-    <hal format="aidl">
-        <name>android.frameworks.vibrator</name>
-        <version>1</version>
-        <fqname>IVibratorControlService/default</fqname>
-    </hal>
 </manifest>
diff --git a/services/midi/java/com/android/server/midi/MidiService.java b/services/midi/java/com/android/server/midi/MidiService.java
index 39aaab2..a212812 100644
--- a/services/midi/java/com/android/server/midi/MidiService.java
+++ b/services/midi/java/com/android/server/midi/MidiService.java
@@ -31,6 +31,7 @@
 import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageInfo;
 import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.Property;
 import android.content.pm.ResolveInfo;
 import android.content.pm.ServiceInfo;
 import android.content.res.Resources;
@@ -1396,16 +1397,20 @@
         XmlResourceParser parser = null;
 
         try {
-            parser = serviceInfo.loadXmlMetaData(mPackageManager,
-                    MidiDeviceService.SERVICE_INTERFACE);
-            if (parser == null) return;
+            if (serviceInfo == null) {
+                Log.w(TAG, "Skipping null service info");
+                return;
+            }
 
             // ignore virtual device servers that do not require the correct permission
             if (!android.Manifest.permission.BIND_MIDI_DEVICE_SERVICE.equals(
                     serviceInfo.permission)) {
-                Log.w(TAG, "Skipping MIDI device service " + serviceInfo.packageName
-                        + ": it does not require the permission "
-                        + android.Manifest.permission.BIND_MIDI_DEVICE_SERVICE);
+                return;
+            }
+            parser = serviceInfo.loadXmlMetaData(mPackageManager,
+                    MidiDeviceService.SERVICE_INTERFACE);
+            if (parser == null) {
+                Log.w(TAG, "loading xml metadata failed");
                 return;
             }
 
@@ -1533,21 +1538,14 @@
         XmlResourceParser parser = null;
 
         try {
-            ComponentName componentName = new ComponentName(serviceInfo.packageName,
-                    serviceInfo.name);
-            int resId = mPackageManager.getProperty(MidiUmpDeviceService.SERVICE_INTERFACE,
-                    componentName).getResourceId();
-            Resources resources = mPackageManager.getResourcesForApplication(
-                    serviceInfo.packageName);
-            parser = resources.getXml(resId);
-            if (parser == null) return;
+            if (serviceInfo == null) {
+                Log.w(TAG, "Skipping null service info");
+                return;
+            }
 
             // ignore virtual device servers that do not require the correct permission
             if (!android.Manifest.permission.BIND_MIDI_DEVICE_SERVICE.equals(
                     serviceInfo.permission)) {
-                Log.w(TAG, "Skipping MIDI device service " + serviceInfo.packageName
-                        + ": it does not require the permission "
-                        + android.Manifest.permission.BIND_MIDI_DEVICE_SERVICE);
                 return;
             }
 
@@ -1557,6 +1555,31 @@
                 return;
             }
 
+            ComponentName componentName = new ComponentName(serviceInfo.packageName,
+                    serviceInfo.name);
+            Property property = mPackageManager.getProperty(MidiUmpDeviceService.SERVICE_INTERFACE,
+                    componentName);
+            if (property == null) {
+                Log.w(TAG, "Getting MidiUmpDeviceService property failed");
+                return;
+            }
+            int resId = property.getResourceId();
+            if (resId == 0) {
+                Log.w(TAG, "Getting MidiUmpDeviceService resourceId failed");
+                return;
+            }
+            Resources resources = mPackageManager.getResourcesForApplication(
+                    serviceInfo.packageName);
+            if (resources == null) {
+                Log.w(TAG, "Getting resource failed " + serviceInfo.packageName);
+                return;
+            }
+            parser = resources.getXml(resId);
+            if (parser == null) {
+                Log.w(TAG, "Getting XML failed " + resId);
+                return;
+            }
+
             Bundle properties = null;
             int numPorts = 0;
             boolean isPrivate = false;
diff --git a/services/permission/java/com/android/server/permission/access/permission/DevicePermissionPolicy.kt b/services/permission/java/com/android/server/permission/access/permission/DevicePermissionPolicy.kt
index 44609ac..ea5fb5d6 100644
--- a/services/permission/java/com/android/server/permission/access/permission/DevicePermissionPolicy.kt
+++ b/services/permission/java/com/android/server/permission/access/permission/DevicePermissionPolicy.kt
@@ -304,7 +304,7 @@
         /** These permissions are supported for virtual devices. */
         // TODO: b/298661870 - Use new API to get the list of device aware permissions.
         val DEVICE_AWARE_PERMISSIONS =
-            if (Flags.deviceAwarePermissionApis()) {
+            if (Flags.deviceAwarePermissionApisEnabled()) {
                 setOf(Manifest.permission.CAMERA, Manifest.permission.RECORD_AUDIO)
             } else {
                 emptySet<String>()
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 a7d3249..0f65494 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
@@ -1551,7 +1551,8 @@
         permissionName: String,
         deviceId: Int,
     ): Int {
-        return if (!Flags.deviceAwarePermissionApis() || deviceId == Context.DEVICE_ID_DEFAULT) {
+        return if (!Flags.deviceAwarePermissionApisEnabled() ||
+            deviceId == Context.DEVICE_ID_DEFAULT) {
             with(policy) { getPermissionFlags(appId, userId, permissionName) }
         } else {
             if (permissionName !in DevicePermissionPolicy.DEVICE_AWARE_PERMISSIONS) {
@@ -1586,7 +1587,8 @@
         deviceId: Int,
         flags: Int
     ): Boolean {
-        return if (!Flags.deviceAwarePermissionApis() || deviceId == Context.DEVICE_ID_DEFAULT) {
+        return if (!Flags.deviceAwarePermissionApisEnabled() ||
+            deviceId == Context.DEVICE_ID_DEFAULT) {
             with(policy) { setPermissionFlags(appId, userId, permissionName, flags) }
         } else {
             if (permissionName !in DevicePermissionPolicy.DEVICE_AWARE_PERMISSIONS) {
diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageManagerSettingsTests.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageManagerSettingsTests.java
index a0dc2b6..f07e820 100644
--- a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageManagerSettingsTests.java
+++ b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageManagerSettingsTests.java
@@ -817,12 +817,14 @@
 
         ps1.setUsesSdkLibraries(new String[] { "com.example.sdk.one" });
         ps1.setUsesSdkLibrariesVersionsMajor(new long[] { 12 });
+        ps1.setUsesSdkLibrariesOptional(new boolean[] {true});
         ps1.setFlags(ps1.getFlags() | ApplicationInfo.FLAG_SYSTEM);
         settingsUnderTest.mPackages.put(PACKAGE_NAME_1, ps1);
         assertThat(settingsUnderTest.disableSystemPackageLPw(PACKAGE_NAME_1, false), is(true));
 
         ps2.setUsesSdkLibraries(new String[] { "com.example.sdk.two" });
         ps2.setUsesSdkLibrariesVersionsMajor(new long[] { 34 });
+        ps2.setUsesSdkLibrariesOptional(new boolean[] {false});
         settingsUnderTest.mPackages.put(PACKAGE_NAME_2, ps2);
 
         settingsUnderTest.writeLPr(computer, /*sync=*/true);
@@ -838,19 +840,30 @@
         Truth.assertThat(readPs1).isNotNull();
         Truth.assertThat(readPs1.getUsesSdkLibraries()).isNotNull();
         Truth.assertThat(readPs1.getUsesSdkLibrariesVersionsMajor()).isNotNull();
+        Truth.assertThat(readPs1.getUsesSdkLibrariesOptional()).isNotNull();
         Truth.assertThat(readPs2).isNotNull();
         Truth.assertThat(readPs2.getUsesSdkLibraries()).isNotNull();
         Truth.assertThat(readPs2.getUsesSdkLibrariesVersionsMajor()).isNotNull();
+        Truth.assertThat(readPs2.getUsesSdkLibrariesOptional()).isNotNull();
 
         List<Long> ps1VersionsAsList = new ArrayList<>();
         for (long version : ps1.getUsesSdkLibrariesVersionsMajor()) {
             ps1VersionsAsList.add(version);
         }
 
+        List<Boolean> ps1RequireAsList = new ArrayList<>();
+        for (boolean optional : ps1.getUsesSdkLibrariesOptional()) {
+            ps1RequireAsList.add(optional);
+        }
+
         List<Long> ps2VersionsAsList = new ArrayList<>();
         for (long version : ps2.getUsesSdkLibrariesVersionsMajor()) {
             ps2VersionsAsList.add(version);
         }
+        List<Boolean> ps2RequireAsList = new ArrayList<>();
+        for (boolean optional : ps2.getUsesSdkLibrariesOptional()) {
+            ps2RequireAsList.add(optional);
+        }
 
         Truth.assertThat(readPs1.getUsesSdkLibraries()).asList()
                 .containsExactlyElementsIn(ps1.getUsesSdkLibraries()).inOrder();
@@ -858,11 +871,17 @@
         Truth.assertThat(readPs1.getUsesSdkLibrariesVersionsMajor()).asList()
                 .containsExactlyElementsIn(ps1VersionsAsList).inOrder();
 
+        Truth.assertThat(readPs1.getUsesSdkLibrariesOptional()).asList()
+                .containsExactlyElementsIn(ps1RequireAsList).inOrder();
+
         Truth.assertThat(readPs2.getUsesSdkLibraries()).asList()
                 .containsExactlyElementsIn(ps2.getUsesSdkLibraries()).inOrder();
 
         Truth.assertThat(readPs2.getUsesSdkLibrariesVersionsMajor()).asList()
                 .containsExactlyElementsIn(ps2VersionsAsList).inOrder();
+
+        Truth.assertThat(readPs2.getUsesSdkLibrariesOptional()).asList()
+                .containsExactlyElementsIn(ps2RequireAsList).inOrder();
     }
 
     @Test
@@ -1047,6 +1066,7 @@
                 UserManagerService.getInstance(),
                 null /*usesSdkLibraries*/,
                 null /*usesSdkLibrariesVersions*/,
+                null /*usesSdkLibrariesOptional*/,
                 null /*usesStaticLibraries*/,
                 null /*usesStaticLibrariesVersions*/,
                 null /*mimeGroups*/,
@@ -1087,6 +1107,7 @@
                 UserManagerService.getInstance(),
                 null /*usesSdkLibraries*/,
                 null /*usesSdkLibrariesVersions*/,
+                null /*usesSdkLibrariesOptional*/,
                 null /*usesStaticLibraries*/,
                 null /*usesStaticLibrariesVersions*/,
                 null /*mimeGroups*/,
@@ -1129,6 +1150,7 @@
                     UserManagerService.getInstance(),
                     null /*usesSdkLibraries*/,
                     null /*usesSdkLibrariesVersions*/,
+                    null /*usesSdkLibrariesOptional*/,
                     null /*usesStaticLibraries*/,
                     null /*usesStaticLibrariesVersions*/,
                     null /*mimeGroups*/,
@@ -1167,6 +1189,7 @@
                 UserManagerService.getInstance(),
                 null /*usesSdkLibraries*/,
                 null /*usesSdkLibrariesVersions*/,
+                null /*usesSdkLibrariesOptional*/,
                 null /*usesStaticLibraries*/,
                 null /*usesStaticLibrariesVersions*/,
                 null /*mimeGroups*/,
@@ -1214,6 +1237,7 @@
                 UserManagerService.getInstance(),
                 null /*usesSdkLibraries*/,
                 null /*usesSdkLibrariesVersions*/,
+                null /*usesSdkLibrariesOptional*/,
                 null /*usesStaticLibraries*/,
                 null /*usesStaticLibrariesVersions*/,
                 null /*mimeGroups*/,
@@ -1263,6 +1287,7 @@
                 null /*usesSdkLibrariesVersions*/,
                 null /*usesStaticLibraries*/,
                 null /*usesStaticLibrariesVersions*/,
+                null /*usesSdkLibrariesOptional*/,
                 null /*mimeGroups*/,
                 UUID.randomUUID(),
                 34 /*targetSdkVersion*/,
@@ -1311,6 +1336,7 @@
                 null /*usesSdkLibrariesVersions*/,
                 null /*usesStaticLibraries*/,
                 null /*usesStaticLibrariesVersions*/,
+                null /*usesSdkLibrariesOptional*/,
                 null /*mimeGroups*/,
                 UUID.randomUUID(),
                 34 /*targetSdkVersion*/,
@@ -1356,6 +1382,7 @@
                 null /*usesSdkLibrariesVersions*/,
                 null /*usesStaticLibraries*/,
                 null /*usesStaticLibrariesVersions*/,
+                null /*usesSdkLibrariesOptional*/,
                 null /*mimeGroups*/,
                 UUID.randomUUID(),
                 34 /*targetSdkVersion*/,
diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageParserTest.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageParserTest.java
index ea88ec2..a62cd4f 100644
--- a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageParserTest.java
+++ b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageParserTest.java
@@ -1062,7 +1062,7 @@
                 .addProtectedBroadcast("foo8")
                 .setSdkLibraryName("sdk12")
                 .setSdkLibVersionMajor(42)
-                .addUsesSdkLibrary("sdk23", 200, new String[]{"digest2"})
+                .addUsesSdkLibrary("sdk23", 200, new String[]{"digest2"}, true)
                 .setStaticSharedLibraryName("foo23")
                 .setStaticSharedLibraryVersion(100)
                 .addUsesStaticLibrary("foo23", 100, new String[]{"digest"})
diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/ScanTests.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/ScanTests.java
index decb44c..6202908 100644
--- a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/ScanTests.java
+++ b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/ScanTests.java
@@ -599,8 +599,8 @@
                 .setVolumeUuid(UUID_ONE.toString())
                 .addUsesStaticLibrary("some.static.library", 234L, new String[]{"testCert1"})
                 .addUsesStaticLibrary("some.other.static.library", 456L, new String[]{"testCert2"})
-                .addUsesSdkLibrary("some.sdk.library", 123L, new String[]{"testCert3"})
-                .addUsesSdkLibrary("some.other.sdk.library", 789L, new String[]{"testCert4"})
+                .addUsesSdkLibrary("some.sdk.library", 123L, new String[]{"testCert3"}, false)
+                .addUsesSdkLibrary("some.other.sdk.library", 789L, new String[]{"testCert4"}, true)
                 .hideAsParsed())
                 .setNativeLibraryRootDir("/data/tmp/randompath/base.apk:/lib")
                 .setVersionCodeMajor(1)
@@ -628,6 +628,7 @@
         assertThat(pkgSetting.getUsesSdkLibraries(),
                 arrayContaining("some.sdk.library", "some.other.sdk.library"));
         assertThat(pkgSetting.getUsesSdkLibrariesVersionsMajor(), is(new long[]{123L, 789L}));
+        assertThat(pkgSetting.getUsesSdkLibrariesOptional(), is(new boolean[]{false, true}));
         assertThat(pkgSetting.getPkg(), is(scanResult.mRequest.mParsedPackage));
         assertThat(pkgSetting.getPath(), is(new File(createCodePath(packageName))));
         assertThat(pkgSetting.getVersionCode(),
diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/AndroidPackageTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/AndroidPackageTest.kt
index 170faf6..09b66c1 100644
--- a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/AndroidPackageTest.kt
+++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/AndroidPackageTest.kt
@@ -127,6 +127,7 @@
         "getUsesSdkLibraries",
         "getUsesSdkLibrariesVersionsMajor",
         "getUsesSdkLibrariesCertDigests",
+        "getUsesSdkLibrariesOptional",
         // Tested through addUsesStaticLibrary
         "addUsesStaticLibrary",
         "getUsesStaticLibraries",
@@ -608,7 +609,7 @@
         .setSplitHasCode(1, false)
         .setSplitClassLoaderName(0, "testSplitClassLoaderNameZero")
         .setSplitClassLoaderName(1, "testSplitClassLoaderNameOne")
-        .addUsesSdkLibrary("testSdk", 2L, arrayOf("testCertDigest1"))
+        .addUsesSdkLibrary("testSdk", 2L, arrayOf("testCertDigest1"), true)
         .addUsesStaticLibrary("testStatic", 3L, arrayOf("testCertDigest2"))
 
     override fun finalizeObject(parcelable: Parcelable) {
@@ -661,6 +662,7 @@
         expect.that(after.usesSdkLibrariesCertDigests!!.size).isEqualTo(1)
         expect.that(after.usesSdkLibrariesCertDigests!![0]).asList()
                 .containsExactly("testCertDigest1")
+        expect.that(after.usesSdkLibrariesOptional).asList().containsExactly(true)
 
         expect.that(after.usesStaticLibraries).containsExactly("testStatic")
         expect.that(after.usesStaticLibrariesVersions).asList().containsExactly(3L)
diff --git a/services/tests/mockingservicestests/src/com/android/server/job/controllers/BackgroundJobsControllerTest.java b/services/tests/mockingservicestests/src/com/android/server/job/controllers/BackgroundJobsControllerTest.java
index 25d331f..23886a1 100644
--- a/services/tests/mockingservicestests/src/com/android/server/job/controllers/BackgroundJobsControllerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/job/controllers/BackgroundJobsControllerTest.java
@@ -167,6 +167,11 @@
     }
 
     private void setStoppedState(int uid, String pkgName, boolean stopped) {
+        doReturn(stopped).when(mPackageManagerInternal).isPackageStopped(pkgName, uid);
+        sendPackageStoppedBroadcast(uid, pkgName, stopped);
+    }
+
+    private void sendPackageStoppedBroadcast(int uid, String pkgName, boolean stopped) {
         Intent intent = new Intent(
                 stopped ? Intent.ACTION_PACKAGE_RESTARTED : Intent.ACTION_PACKAGE_UNSTOPPED);
         intent.putExtra(Intent.EXTRA_UID, uid);
@@ -174,14 +179,6 @@
         mStoppedReceiver.onReceive(mContext, intent);
     }
 
-    private void setUidBias(int uid, int bias) {
-        int prevBias = mJobSchedulerService.getUidBias(uid);
-        doReturn(bias).when(mJobSchedulerService).getUidBias(uid);
-        synchronized (mBackgroundJobsController.mLock) {
-            mBackgroundJobsController.onUidBiasChangedLocked(uid, prevBias, bias);
-        }
-    }
-
     private void trackJobs(JobStatus... jobs) {
         for (JobStatus job : jobs) {
             mJobStore.add(job);
@@ -208,6 +205,47 @@
     }
 
     @Test
+    public void testRestartedBroadcastWithoutStopping() {
+        mSetFlagsRule.enableFlags(android.content.pm.Flags.FLAG_STAY_STOPPED);
+        // Scheduled by SOURCE_UID:SOURCE_PACKAGE for itself.
+        JobStatus directJob1 = createJobStatus("testStopped", SOURCE_PACKAGE, SOURCE_UID,
+                createBaseJobInfoBuilder(SOURCE_PACKAGE, 1).build());
+        // Scheduled by ALTERNATE_UID:ALTERNATE_SOURCE_PACKAGE for itself.
+        JobStatus directJob2 = createJobStatus("testStopped",
+                ALTERNATE_SOURCE_PACKAGE, ALTERNATE_UID,
+                createBaseJobInfoBuilder(ALTERNATE_SOURCE_PACKAGE, 2).build());
+        // Scheduled by CALLING_PACKAGE for SOURCE_PACKAGE.
+        JobStatus proxyJob1 = createJobStatus("testStopped", SOURCE_PACKAGE, CALLING_UID,
+                createBaseJobInfoBuilder(CALLING_PACKAGE, 3).build());
+        // Scheduled by CALLING_PACKAGE for ALTERNATE_SOURCE_PACKAGE.
+        JobStatus proxyJob2 = createJobStatus("testStopped",
+                ALTERNATE_SOURCE_PACKAGE, CALLING_UID,
+                createBaseJobInfoBuilder(CALLING_PACKAGE, 4).build());
+
+        trackJobs(directJob1, directJob2, proxyJob1, proxyJob2);
+
+        sendPackageStoppedBroadcast(ALTERNATE_UID, ALTERNATE_SOURCE_PACKAGE, true);
+        assertTrue(directJob1.isConstraintSatisfied(CONSTRAINT_BACKGROUND_NOT_RESTRICTED));
+        assertFalse(directJob1.isUserBgRestricted());
+        assertTrue(directJob2.isConstraintSatisfied(CONSTRAINT_BACKGROUND_NOT_RESTRICTED));
+        assertFalse(directJob2.isUserBgRestricted());
+        assertTrue(proxyJob1.isConstraintSatisfied(CONSTRAINT_BACKGROUND_NOT_RESTRICTED));
+        assertFalse(proxyJob1.isUserBgRestricted());
+        assertTrue(proxyJob2.isConstraintSatisfied(CONSTRAINT_BACKGROUND_NOT_RESTRICTED));
+        assertFalse(proxyJob2.isUserBgRestricted());
+
+        sendPackageStoppedBroadcast(ALTERNATE_UID, ALTERNATE_SOURCE_PACKAGE, false);
+        assertTrue(directJob1.isConstraintSatisfied(CONSTRAINT_BACKGROUND_NOT_RESTRICTED));
+        assertFalse(directJob1.isUserBgRestricted());
+        assertTrue(directJob2.isConstraintSatisfied(CONSTRAINT_BACKGROUND_NOT_RESTRICTED));
+        assertFalse(directJob2.isUserBgRestricted());
+        assertTrue(proxyJob1.isConstraintSatisfied(CONSTRAINT_BACKGROUND_NOT_RESTRICTED));
+        assertFalse(proxyJob1.isUserBgRestricted());
+        assertTrue(proxyJob2.isConstraintSatisfied(CONSTRAINT_BACKGROUND_NOT_RESTRICTED));
+        assertFalse(proxyJob2.isUserBgRestricted());
+    }
+
+    @Test
     public void testStopped_disabled() {
         mSetFlagsRule.disableFlags(android.content.pm.Flags.FLAG_STAY_STOPPED);
         // Scheduled by SOURCE_UID:SOURCE_PACKAGE for itself.
diff --git a/services/tests/mockingservicestests/src/com/android/server/job/controllers/FlexibilityControllerTest.java b/services/tests/mockingservicestests/src/com/android/server/job/controllers/FlexibilityControllerTest.java
index 4fb9472..650c473 100644
--- a/services/tests/mockingservicestests/src/com/android/server/job/controllers/FlexibilityControllerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/job/controllers/FlexibilityControllerTest.java
@@ -30,15 +30,16 @@
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
 import static com.android.server.job.controllers.FlexibilityController.FcConfig.DEFAULT_FALLBACK_FLEXIBILITY_DEADLINE_MS;
 import static com.android.server.job.controllers.FlexibilityController.FcConfig.DEFAULT_UNSEEN_CONSTRAINT_GRACE_PERIOD_MS;
+import static com.android.server.job.controllers.FlexibilityController.FcConfig.KEY_APPLIED_CONSTRAINTS;
 import static com.android.server.job.controllers.FlexibilityController.FcConfig.KEY_DEADLINE_PROXIMITY_LIMIT;
 import static com.android.server.job.controllers.FlexibilityController.FcConfig.KEY_FALLBACK_FLEXIBILITY_DEADLINE;
-import static com.android.server.job.controllers.FlexibilityController.FcConfig.KEY_FLEXIBILITY_ENABLED;
 import static com.android.server.job.controllers.FlexibilityController.FcConfig.KEY_PERCENTS_TO_DROP_NUM_FLEXIBLE_CONSTRAINTS;
-import static com.android.server.job.controllers.FlexibilityController.NUM_FLEXIBLE_CONSTRAINTS;
+import static com.android.server.job.controllers.FlexibilityController.FLEXIBLE_CONSTRAINTS;
 import static com.android.server.job.controllers.FlexibilityController.SYSTEM_WIDE_FLEXIBLE_CONSTRAINTS;
 import static com.android.server.job.controllers.JobStatus.CONSTRAINT_BATTERY_NOT_LOW;
 import static com.android.server.job.controllers.JobStatus.CONSTRAINT_CHARGING;
 import static com.android.server.job.controllers.JobStatus.CONSTRAINT_CONNECTIVITY;
+import static com.android.server.job.controllers.JobStatus.CONSTRAINT_CONTENT_TRIGGER;
 import static com.android.server.job.controllers.JobStatus.CONSTRAINT_FLEXIBLE;
 import static com.android.server.job.controllers.JobStatus.CONSTRAINT_IDLE;
 import static com.android.server.job.controllers.JobStatus.MIN_WINDOW_FOR_FLEXIBILITY_MS;
@@ -66,6 +67,7 @@
 import android.provider.DeviceConfig;
 import android.util.ArraySet;
 
+import com.android.server.AppSchedulingModuleThread;
 import com.android.server.LocalServices;
 import com.android.server.job.JobSchedulerService;
 import com.android.server.job.JobStore;
@@ -129,6 +131,7 @@
         when(mContext.getPackageManager()).thenReturn(mPackageManager);
         when(mPackageManager.hasSystemFeature(
                 PackageManager.FEATURE_AUTOMOTIVE)).thenReturn(false);
+        when(mPackageManager.hasSystemFeature(PackageManager.FEATURE_EMBEDDED)).thenReturn(false);
         // Used in FlexibilityController.FcConstants.
         doAnswer((Answer<Void>) invocationOnMock -> null)
                 .when(() -> DeviceConfig.addOnPropertiesChangedListener(
@@ -161,7 +164,8 @@
 
         setDeviceConfigString(KEY_PERCENTS_TO_DROP_NUM_FLEXIBLE_CONSTRAINTS, "50,60,70,80");
         setDeviceConfigLong(KEY_DEADLINE_PROXIMITY_LIMIT, 0L);
-        setDeviceConfigBoolean(KEY_FLEXIBILITY_ENABLED, true);
+        setDeviceConfigInt(KEY_APPLIED_CONSTRAINTS, FLEXIBLE_CONSTRAINTS);
+        waitForQuietModuleThread();
     }
 
     @After
@@ -171,17 +175,22 @@
         }
     }
 
-    private void setDeviceConfigBoolean(String key, boolean val) {
-        mDeviceConfigPropertiesBuilder.setBoolean(key, val);
-        synchronized (mFlexibilityController.mLock) {
-            mFlexibilityController.prepareForUpdatedConstantsLocked();
-            mFcConfig.processConstantLocked(mDeviceConfigPropertiesBuilder.build(), key);
-            mFlexibilityController.onConstantsUpdatedLocked();
-        }
+    private void setDeviceConfigInt(String key, int val) {
+        mDeviceConfigPropertiesBuilder.setInt(key, val);
+        updateDeviceConfig(key);
     }
 
     private void setDeviceConfigLong(String key, Long val) {
         mDeviceConfigPropertiesBuilder.setLong(key, val);
+        updateDeviceConfig(key);
+    }
+
+    private void setDeviceConfigString(String key, String val) {
+        mDeviceConfigPropertiesBuilder.setString(key, val);
+        updateDeviceConfig(key);
+    }
+
+    private void updateDeviceConfig(String key) {
         synchronized (mFlexibilityController.mLock) {
             mFlexibilityController.prepareForUpdatedConstantsLocked();
             mFcConfig.processConstantLocked(mDeviceConfigPropertiesBuilder.build(), key);
@@ -189,13 +198,9 @@
         }
     }
 
-    private void setDeviceConfigString(String key, String val) {
-        mDeviceConfigPropertiesBuilder.setString(key, val);
-        synchronized (mFlexibilityController.mLock) {
-            mFlexibilityController.prepareForUpdatedConstantsLocked();
-            mFcConfig.processConstantLocked(mDeviceConfigPropertiesBuilder.build(), key);
-            mFlexibilityController.onConstantsUpdatedLocked();
-        }
+    private void waitForQuietModuleThread() {
+        assertTrue("Failed to wait for quiet module thread",
+                AppSchedulingModuleThread.getHandler().runWithScissors(() -> {}, 10_000L));
     }
 
     private static JobInfo.Builder createJob(int id) {
@@ -207,6 +212,10 @@
         JobStatus js = JobStatus.createFromJobInfo(
                 jobInfo, 1000, SOURCE_PACKAGE, SOURCE_USER_ID, "FCTest", testTag);
         js.enqueueTime = FROZEN_TIME;
+        if (js.hasFlexibilityConstraint()) {
+            js.setNumAppliedFlexibleConstraints(Integer.bitCount(
+                    mFlexibilityController.getRelevantAppliedConstraintsLocked(js)));
+        }
         return js;
     }
 
@@ -215,18 +224,120 @@
      */
     @Test
     public void testDefaultVariableValues() {
-        assertEquals(NUM_FLEXIBLE_CONSTRAINTS,
+        assertEquals(Integer.bitCount(FLEXIBLE_CONSTRAINTS),
                 mFlexibilityController.mFcConfig.DEFAULT_PERCENT_TO_DROP_FLEXIBLE_CONSTRAINTS.length
         );
     }
 
     @Test
-    public void testOnConstantsUpdated_DefaultFlexibility() {
+    public void testAppliedConstraints() {
+        setDeviceConfigInt(KEY_APPLIED_CONSTRAINTS, FLEXIBLE_CONSTRAINTS);
+
+        // Add connectivity to require 4 constraints
+        JobStatus connJs = createJobStatus("testAppliedConstraints",
+                createJob(0).setRequiredNetworkType(NETWORK_TYPE_ANY));
+        JobStatus nonConnJs = createJobStatus("testAppliedConstraints",
+                createJob(1).setRequiredNetworkType(NETWORK_TYPE_NONE));
+
+        mFlexibilityController.maybeStartTrackingJobLocked(connJs, null);
+        mFlexibilityController.maybeStartTrackingJobLocked(nonConnJs, null);
+
+        assertEquals(4, connJs.getNumAppliedFlexibleConstraints());
+        assertEquals(3, nonConnJs.getNumAppliedFlexibleConstraints());
+
+        mFlexibilityController.setConstraintSatisfied(
+                CONSTRAINT_BATTERY_NOT_LOW, true,
+                JobSchedulerService.sElapsedRealtimeClock.millis() - HOUR_IN_MILLIS);
+        mFlexibilityController.setConstraintSatisfied(
+                CONSTRAINT_CHARGING, false,
+                JobSchedulerService.sElapsedRealtimeClock.millis() - HOUR_IN_MILLIS);
+        mFlexibilityController.setConstraintSatisfied(
+                CONSTRAINT_IDLE, false,
+                JobSchedulerService.sElapsedRealtimeClock.millis() - HOUR_IN_MILLIS);
+        mFlexibilityController.setConstraintSatisfied(
+                CONSTRAINT_CONNECTIVITY, true,
+                JobSchedulerService.sElapsedRealtimeClock.millis() - HOUR_IN_MILLIS);
+        connJs.setTransportAffinitiesSatisfied(true);
+
+        synchronized (mFlexibilityController.mLock) {
+            assertFalse(mFlexibilityController.hasEnoughSatisfiedConstraintsLocked(connJs));
+            assertFalse(mFlexibilityController.hasEnoughSatisfiedConstraintsLocked(nonConnJs));
+        }
+
+        setDeviceConfigInt(KEY_APPLIED_CONSTRAINTS,
+                CONSTRAINT_BATTERY_NOT_LOW | CONSTRAINT_CONNECTIVITY);
+        waitForQuietModuleThread();
+
+        // Only battery-not-low (which is satisfied) applies to the non-connectivity job, so it
+        // should be able to run.
+        assertEquals(2, connJs.getNumAppliedFlexibleConstraints());
+        assertEquals(1, nonConnJs.getNumAppliedFlexibleConstraints());
+        synchronized (mFlexibilityController.mLock) {
+            assertTrue(mFlexibilityController.hasEnoughSatisfiedConstraintsLocked(connJs));
+            assertTrue(mFlexibilityController.hasEnoughSatisfiedConstraintsLocked(nonConnJs));
+        }
+
+        setDeviceConfigInt(KEY_APPLIED_CONSTRAINTS, CONSTRAINT_BATTERY_NOT_LOW);
+        waitForQuietModuleThread();
+
+        assertEquals(1, connJs.getNumAppliedFlexibleConstraints());
+        assertEquals(1, nonConnJs.getNumAppliedFlexibleConstraints());
+        synchronized (mFlexibilityController.mLock) {
+            assertTrue(mFlexibilityController.hasEnoughSatisfiedConstraintsLocked(connJs));
+            assertTrue(mFlexibilityController.hasEnoughSatisfiedConstraintsLocked(nonConnJs));
+        }
+
+        setDeviceConfigInt(KEY_APPLIED_CONSTRAINTS, CONSTRAINT_CONNECTIVITY);
+        waitForQuietModuleThread();
+
+        // No constraints apply to the non-connectivity job, so it should be able to run.
+        assertEquals(1, connJs.getNumAppliedFlexibleConstraints());
+        assertEquals(0, nonConnJs.getNumAppliedFlexibleConstraints());
+        synchronized (mFlexibilityController.mLock) {
+            assertTrue(mFlexibilityController.hasEnoughSatisfiedConstraintsLocked(connJs));
+            assertTrue(mFlexibilityController.hasEnoughSatisfiedConstraintsLocked(nonConnJs));
+        }
+
+        setDeviceConfigInt(KEY_APPLIED_CONSTRAINTS, CONSTRAINT_CHARGING);
+        waitForQuietModuleThread();
+
+        assertEquals(1, connJs.getNumAppliedFlexibleConstraints());
+        assertEquals(1, nonConnJs.getNumAppliedFlexibleConstraints());
+        synchronized (mFlexibilityController.mLock) {
+            assertFalse(mFlexibilityController.hasEnoughSatisfiedConstraintsLocked(connJs));
+            assertFalse(mFlexibilityController.hasEnoughSatisfiedConstraintsLocked(nonConnJs));
+        }
+
+        setDeviceConfigInt(KEY_APPLIED_CONSTRAINTS, 0);
+        waitForQuietModuleThread();
+
+        // No constraints apply, so they should be able to run.
+        assertEquals(0, connJs.getNumAppliedFlexibleConstraints());
+        assertEquals(0, nonConnJs.getNumAppliedFlexibleConstraints());
+        synchronized (mFlexibilityController.mLock) {
+            assertTrue(mFlexibilityController.hasEnoughSatisfiedConstraintsLocked(connJs));
+            assertTrue(mFlexibilityController.hasEnoughSatisfiedConstraintsLocked(nonConnJs));
+        }
+
+        // Invalid constraint to apply.
+        setDeviceConfigInt(KEY_APPLIED_CONSTRAINTS, CONSTRAINT_CONTENT_TRIGGER);
+        waitForQuietModuleThread();
+
+        // No constraints apply, so they should be able to run.
+        assertEquals(0, connJs.getNumAppliedFlexibleConstraints());
+        assertEquals(0, nonConnJs.getNumAppliedFlexibleConstraints());
+        synchronized (mFlexibilityController.mLock) {
+            assertTrue(mFlexibilityController.hasEnoughSatisfiedConstraintsLocked(connJs));
+            assertTrue(mFlexibilityController.hasEnoughSatisfiedConstraintsLocked(nonConnJs));
+        }
+    }
+
+    @Test
+    public void testOnConstantsUpdated_AppliedConstraints() {
         JobStatus js = createJobStatus("testDefaultFlexibilityConfig", createJob(0));
-        assertFalse(mFlexibilityController.isFlexibilitySatisfiedLocked(js));
-        setDeviceConfigBoolean(KEY_FLEXIBILITY_ENABLED, false);
+        setDeviceConfigInt(KEY_APPLIED_CONSTRAINTS, 0);
         assertTrue(mFlexibilityController.isFlexibilitySatisfiedLocked(js));
-        setDeviceConfigBoolean(KEY_FLEXIBILITY_ENABLED, true);
+        setDeviceConfigInt(KEY_APPLIED_CONSTRAINTS, FLEXIBLE_CONSTRAINTS);
         assertFalse(mFlexibilityController.isFlexibilitySatisfiedLocked(js));
     }
 
@@ -261,10 +372,10 @@
                 new int[] {10, 20, 30, 40});
         assertEquals(FROZEN_TIME + MIN_WINDOW_FOR_FLEXIBILITY_MS / 10,
                 mFlexibilityController.getNextConstraintDropTimeElapsedLocked(js));
-        js.adjustNumRequiredFlexibleConstraints(-1);
+        js.setNumDroppedFlexibleConstraints(1);
         assertEquals(FROZEN_TIME + MIN_WINDOW_FOR_FLEXIBILITY_MS / 10 * 2,
                 mFlexibilityController.getNextConstraintDropTimeElapsedLocked(js));
-        js.adjustNumRequiredFlexibleConstraints(-1);
+        js.setNumDroppedFlexibleConstraints(2);
         assertEquals(FROZEN_TIME + MIN_WINDOW_FOR_FLEXIBILITY_MS / 10 * 3,
                 mFlexibilityController.getNextConstraintDropTimeElapsedLocked(js));
     }
@@ -303,12 +414,12 @@
                 .getNextConstraintDropTimeElapsedLocked(js);
         assertEquals(FROZEN_TIME + MIN_WINDOW_FOR_FLEXIBILITY_MS / 10 * 5,
                 nextTimeToDropNumConstraints);
-        js.adjustNumRequiredFlexibleConstraints(-1);
+        js.setNumDroppedFlexibleConstraints(1);
         nextTimeToDropNumConstraints = mFlexibilityController
                 .getNextConstraintDropTimeElapsedLocked(js);
         assertEquals(FROZEN_TIME + MIN_WINDOW_FOR_FLEXIBILITY_MS / 10 * 6,
                 nextTimeToDropNumConstraints);
-        js.adjustNumRequiredFlexibleConstraints(-1);
+        js.setNumDroppedFlexibleConstraints(2);
         nextTimeToDropNumConstraints = mFlexibilityController
                 .getNextConstraintDropTimeElapsedLocked(js);
         assertEquals(FROZEN_TIME + MIN_WINDOW_FOR_FLEXIBILITY_MS / 10 * 7,
@@ -321,11 +432,11 @@
         nextTimeToDropNumConstraints = mFlexibilityController
                 .getNextConstraintDropTimeElapsedLocked(js);
         assertEquals(130400100, nextTimeToDropNumConstraints);
-        js.adjustNumRequiredFlexibleConstraints(-1);
+        js.setNumDroppedFlexibleConstraints(1);
         nextTimeToDropNumConstraints = mFlexibilityController
                 .getNextConstraintDropTimeElapsedLocked(js);
         assertEquals(156320100L, nextTimeToDropNumConstraints);
-        js.adjustNumRequiredFlexibleConstraints(-1);
+        js.setNumDroppedFlexibleConstraints(2);
         nextTimeToDropNumConstraints = mFlexibilityController
                 .getNextConstraintDropTimeElapsedLocked(js);
         assertEquals(182240100L, nextTimeToDropNumConstraints);
@@ -337,11 +448,11 @@
         nextTimeToDropNumConstraints = mFlexibilityController
                 .getNextConstraintDropTimeElapsedLocked(js);
         assertEquals(129600100, nextTimeToDropNumConstraints);
-        js.adjustNumRequiredFlexibleConstraints(-1);
+        js.setNumDroppedFlexibleConstraints(1);
         nextTimeToDropNumConstraints = mFlexibilityController
                 .getNextConstraintDropTimeElapsedLocked(js);
         assertEquals(155520100L, nextTimeToDropNumConstraints);
-        js.adjustNumRequiredFlexibleConstraints(-1);
+        js.setNumDroppedFlexibleConstraints(2);
         nextTimeToDropNumConstraints = mFlexibilityController
                 .getNextConstraintDropTimeElapsedLocked(js);
         assertEquals(181440100L, nextTimeToDropNumConstraints);
@@ -357,12 +468,12 @@
                 .getNextConstraintDropTimeElapsedLocked(js);
         assertEquals(windowStart + MIN_WINDOW_FOR_FLEXIBILITY_MS / 10 * 5,
                 nextTimeToDropNumConstraints);
-        js.adjustNumRequiredFlexibleConstraints(-1);
+        js.setNumDroppedFlexibleConstraints(1);
         nextTimeToDropNumConstraints = mFlexibilityController
                 .getNextConstraintDropTimeElapsedLocked(js);
         assertEquals(windowStart + MIN_WINDOW_FOR_FLEXIBILITY_MS / 10 * 6,
                 nextTimeToDropNumConstraints);
-        js.adjustNumRequiredFlexibleConstraints(-1);
+        js.setNumDroppedFlexibleConstraints(2);
         nextTimeToDropNumConstraints = mFlexibilityController
                 .getNextConstraintDropTimeElapsedLocked(js);
         assertEquals(windowStart + MIN_WINDOW_FOR_FLEXIBILITY_MS / 10 * 7,
@@ -580,10 +691,10 @@
     }
 
     @Test
-    public void testWontStopJobFromRunning() {
-        JobStatus js = createJobStatus("testWontStopJobFromRunning", createJob(101));
+    public void testWontStopAlreadyRunningJob() {
+        JobStatus js = createJobStatus("testWontStopAlreadyRunningJob", createJob(101));
         // Stop satisfied constraints from causing a false positive.
-        js.adjustNumRequiredFlexibleConstraints(100);
+        js.setNumAppliedFlexibleConstraints(100);
         synchronized (mFlexibilityController.mLock) {
             when(mJobSchedulerService.isCurrentlyRunningLocked(js)).thenReturn(true);
             assertTrue(mFlexibilityController.isFlexibilitySatisfiedLocked(js));
@@ -593,10 +704,9 @@
     @Test
     public void testFlexibilityTracker() {
         FlexibilityController.FlexibilityTracker flexTracker =
-                mFlexibilityController.new
-                        FlexibilityTracker(NUM_FLEXIBLE_CONSTRAINTS);
+                mFlexibilityController.new FlexibilityTracker(4);
         // Plus one for jobs with 0 required constraint.
-        assertEquals(NUM_FLEXIBLE_CONSTRAINTS + 1, flexTracker.size());
+        assertEquals(4 + 1, flexTracker.size());
         JobStatus[] jobs = new JobStatus[4];
         JobInfo.Builder jb;
         for (int i = 0; i < jobs.length; i++) {
@@ -622,21 +732,21 @@
             assertEquals(3, trackedJobs.get(3).size());
             assertEquals(0, trackedJobs.get(4).size());
 
-            flexTracker.adjustJobsRequiredConstraints(jobs[0], -1, FROZEN_TIME);
+            flexTracker.setNumDroppedFlexibleConstraints(jobs[0], 1);
             assertEquals(1, trackedJobs.get(0).size());
             assertEquals(0, trackedJobs.get(1).size());
             assertEquals(1, trackedJobs.get(2).size());
             assertEquals(2, trackedJobs.get(3).size());
             assertEquals(0, trackedJobs.get(4).size());
 
-            flexTracker.adjustJobsRequiredConstraints(jobs[0], -1, FROZEN_TIME);
+            flexTracker.setNumDroppedFlexibleConstraints(jobs[0], 2);
             assertEquals(1, trackedJobs.get(0).size());
             assertEquals(1, trackedJobs.get(1).size());
             assertEquals(0, trackedJobs.get(2).size());
             assertEquals(2, trackedJobs.get(3).size());
             assertEquals(0, trackedJobs.get(4).size());
 
-            flexTracker.adjustJobsRequiredConstraints(jobs[0], -1, FROZEN_TIME);
+            flexTracker.setNumDroppedFlexibleConstraints(jobs[0], 3);
             assertEquals(2, trackedJobs.get(0).size());
             assertEquals(0, trackedJobs.get(1).size());
             assertEquals(0, trackedJobs.get(2).size());
@@ -650,14 +760,14 @@
             assertEquals(1, trackedJobs.get(3).size());
             assertEquals(0, trackedJobs.get(4).size());
 
-            flexTracker.resetJobNumDroppedConstraints(jobs[0], FROZEN_TIME);
+            flexTracker.calculateNumDroppedConstraints(jobs[0], FROZEN_TIME);
             assertEquals(1, trackedJobs.get(0).size());
             assertEquals(0, trackedJobs.get(1).size());
             assertEquals(0, trackedJobs.get(2).size());
             assertEquals(2, trackedJobs.get(3).size());
             assertEquals(0, trackedJobs.get(4).size());
 
-            flexTracker.adjustJobsRequiredConstraints(jobs[0], -2, FROZEN_TIME);
+            flexTracker.setNumDroppedFlexibleConstraints(jobs[0], 2);
             assertEquals(1, trackedJobs.get(0).size());
             assertEquals(1, trackedJobs.get(1).size());
             assertEquals(0, trackedJobs.get(2).size());
@@ -669,7 +779,7 @@
             JobSchedulerService.sElapsedRealtimeClock =
                     Clock.fixed(Instant.ofEpochMilli(nowElapsed), ZoneOffset.UTC);
 
-            flexTracker.resetJobNumDroppedConstraints(jobs[0], nowElapsed);
+            flexTracker.calculateNumDroppedConstraints(jobs[0], nowElapsed);
             assertEquals(1, trackedJobs.get(0).size());
             assertEquals(0, trackedJobs.get(1).size());
             assertEquals(1, trackedJobs.get(2).size());
@@ -779,9 +889,9 @@
         mFlexibilityController.setConstraintSatisfied(
                 SYSTEM_WIDE_FLEXIBLE_CONSTRAINTS, false, FROZEN_TIME);
         // Require only a single constraint
-        jsAny.adjustNumRequiredFlexibleConstraints(-3);
-        jsCell.adjustNumRequiredFlexibleConstraints(-2);
-        jsWifi.adjustNumRequiredFlexibleConstraints(-2);
+        jsAny.setNumAppliedFlexibleConstraints(1);
+        jsCell.setNumAppliedFlexibleConstraints(1);
+        jsWifi.setNumAppliedFlexibleConstraints(1);
         synchronized (mFlexibilityController.mLock) {
             jsAny.setTransportAffinitiesSatisfied(false);
             jsCell.setTransportAffinitiesSatisfied(false);
@@ -1008,9 +1118,9 @@
     }
 
     @Test
-    public void testResetJobNumDroppedConstraints() {
+    public void testCalculateNumDroppedConstraints() {
         JobInfo.Builder jb = createJob(22);
-        JobStatus js = createJobStatus("testResetJobNumDroppedConstraints", jb);
+        JobStatus js = createJobStatus("testCalculateNumDroppedConstraints", jb);
         long nowElapsed = FROZEN_TIME;
 
         mFlexibilityController.mFlexibilityTracker.add(js);
@@ -1025,14 +1135,14 @@
                 Clock.fixed(Instant.ofEpochMilli(nowElapsed), ZoneOffset.UTC);
 
         mFlexibilityController.mFlexibilityTracker
-                .adjustJobsRequiredConstraints(js, -1, nowElapsed);
+                .setNumDroppedFlexibleConstraints(js, 1);
 
         assertEquals(2, js.getNumRequiredFlexibleConstraints());
         assertEquals(1, js.getNumDroppedFlexibleConstraints());
         assertEquals(1, mFlexibilityController
                 .mFlexibilityTracker.getJobsByNumRequiredConstraints(2).size());
 
-        mFlexibilityController.mFlexibilityTracker.resetJobNumDroppedConstraints(js, nowElapsed);
+        mFlexibilityController.mFlexibilityTracker.calculateNumDroppedConstraints(js, nowElapsed);
 
         assertEquals(2, js.getNumRequiredFlexibleConstraints());
         assertEquals(1, js.getNumDroppedFlexibleConstraints());
@@ -1043,7 +1153,7 @@
         JobSchedulerService.sElapsedRealtimeClock =
                 Clock.fixed(Instant.ofEpochMilli(nowElapsed), ZoneOffset.UTC);
 
-        mFlexibilityController.mFlexibilityTracker.resetJobNumDroppedConstraints(js, nowElapsed);
+        mFlexibilityController.mFlexibilityTracker.calculateNumDroppedConstraints(js, nowElapsed);
 
         assertEquals(3, js.getNumRequiredFlexibleConstraints());
         assertEquals(0, js.getNumDroppedFlexibleConstraints());
@@ -1054,7 +1164,7 @@
         JobSchedulerService.sElapsedRealtimeClock =
                 Clock.fixed(Instant.ofEpochMilli(nowElapsed), ZoneOffset.UTC);
 
-        mFlexibilityController.mFlexibilityTracker.resetJobNumDroppedConstraints(js, nowElapsed);
+        mFlexibilityController.mFlexibilityTracker.calculateNumDroppedConstraints(js, nowElapsed);
 
         assertEquals(0, js.getNumRequiredFlexibleConstraints());
         assertEquals(3, js.getNumDroppedFlexibleConstraints());
@@ -1063,7 +1173,7 @@
         JobSchedulerService.sElapsedRealtimeClock =
                 Clock.fixed(Instant.ofEpochMilli(nowElapsed), ZoneOffset.UTC);
 
-        mFlexibilityController.mFlexibilityTracker.resetJobNumDroppedConstraints(js, nowElapsed);
+        mFlexibilityController.mFlexibilityTracker.calculateNumDroppedConstraints(js, nowElapsed);
 
         assertEquals(1, js.getNumRequiredFlexibleConstraints());
         assertEquals(2, js.getNumDroppedFlexibleConstraints());
@@ -1139,20 +1249,28 @@
     }
 
     @Test
-    public void testDeviceDisabledFlexibility_Auto() {
-        when(mPackageManager.hasSystemFeature(
-                PackageManager.FEATURE_AUTOMOTIVE)).thenReturn(true);
+    public void testUnsupportedDevice_Auto() {
+        runTestUnsupportedDevice(PackageManager.FEATURE_AUTOMOTIVE);
+    }
+
+    @Test
+    public void testUnsupportedDevice_Embedded() {
+        runTestUnsupportedDevice(PackageManager.FEATURE_EMBEDDED);
+    }
+
+    private void runTestUnsupportedDevice(String feature) {
+        when(mPackageManager.hasSystemFeature(feature)).thenReturn(true);
         mFlexibilityController =
                 new FlexibilityController(mJobSchedulerService, mPrefetchController);
-        assertFalse(mFlexibilityController.mFlexibilityEnabled);
+        assertFalse(mFlexibilityController.isEnabled());
 
-        JobStatus js = createJobStatus("testIsAuto", createJob(0));
+        JobStatus js = createJobStatus("testUnsupportedDevice", createJob(0));
 
         mFlexibilityController.maybeStartTrackingJobLocked(js, null);
         assertTrue(js.isConstraintSatisfied(CONSTRAINT_FLEXIBLE));
 
-        setDeviceConfigBoolean(KEY_FLEXIBILITY_ENABLED, true);
-        assertFalse(mFlexibilityController.mFlexibilityEnabled);
+        setDeviceConfigInt(KEY_APPLIED_CONSTRAINTS, FLEXIBLE_CONSTRAINTS);
+        assertFalse(mFlexibilityController.isEnabled());
 
         ArrayList<ArraySet<JobStatus>> jobs =
                 mFlexibilityController.mFlexibilityTracker.getArrayList();
diff --git a/services/tests/mockingservicestests/src/com/android/server/job/controllers/JobStatusTest.java b/services/tests/mockingservicestests/src/com/android/server/job/controllers/JobStatusTest.java
index 8397b877..293391f 100644
--- a/services/tests/mockingservicestests/src/com/android/server/job/controllers/JobStatusTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/job/controllers/JobStatusTest.java
@@ -232,6 +232,19 @@
     }
 
     @Test
+    public void testFlexibleConstraintCounts() {
+        JobStatus js = createJobStatus(new JobInfo.Builder(101, new ComponentName("foo", "bar"))
+                .setUserInitiated(false)
+                .build());
+
+        js.setNumAppliedFlexibleConstraints(3);
+        js.setNumDroppedFlexibleConstraints(2);
+        assertEquals(3, js.getNumAppliedFlexibleConstraints());
+        assertEquals(2, js.getNumDroppedFlexibleConstraints());
+        assertEquals(1, js.getNumRequiredFlexibleConstraints());
+    }
+
+    @Test
     public void testIsUserVisibleJob() {
         JobInfo jobInfo = new JobInfo.Builder(101, new ComponentName("foo", "bar"))
                 .setUserInitiated(false)
diff --git a/services/tests/mockingservicestests/src/com/android/server/job/controllers/QuotaControllerTest.java b/services/tests/mockingservicestests/src/com/android/server/job/controllers/QuotaControllerTest.java
index a250ac7..0702764 100644
--- a/services/tests/mockingservicestests/src/com/android/server/job/controllers/QuotaControllerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/job/controllers/QuotaControllerTest.java
@@ -368,10 +368,15 @@
         }
     }
 
+    private JobInfo.Builder createJobInfoBuilder(int jobId) {
+        return new JobInfo.Builder(jobId, new ComponentName(mContext, "TestQuotaJobService"));
+    }
+
     private JobStatus createJobStatus(String testTag, int jobId) {
-        JobInfo jobInfo = new JobInfo.Builder(jobId,
-                new ComponentName(mContext, "TestQuotaJobService"))
-                .build();
+        return createJobStatus(testTag, createJobInfoBuilder(jobId).build());
+    }
+
+    private JobStatus createJobStatus(String testTag, JobInfo jobInfo) {
         return createJobStatus(testTag, SOURCE_PACKAGE, CALLING_UID, jobInfo);
     }
 
@@ -1333,39 +1338,70 @@
         mQuotaController.saveTimingSession(0, SOURCE_PACKAGE,
                 createTimingSession(sElapsedRealtimeClock.millis() - (6 * MINUTE_IN_MILLIS),
                         3 * MINUTE_IN_MILLIS, 5), false);
+        final long timeUntilQuotaConsumedMs = 7 * MINUTE_IN_MILLIS;
         JobStatus job = createJobStatus("testGetMaxJobExecutionTimeLocked", 0);
+        //noinspection deprecation
+        JobStatus jobDefIWF = createJobStatus("testGetMaxJobExecutionTimeLocked",
+                createJobInfoBuilder(1)
+                        .setImportantWhileForeground(true)
+                        .setPriority(JobInfo.PRIORITY_DEFAULT)
+                        .build());
+        JobStatus jobHigh = createJobStatus("testGetMaxJobExecutionTimeLocked",
+                createJobInfoBuilder(2).setPriority(JobInfo.PRIORITY_HIGH).build());
         setStandbyBucket(RARE_INDEX, job);
+        setStandbyBucket(RARE_INDEX, jobDefIWF);
+        setStandbyBucket(RARE_INDEX, jobHigh);
 
         setCharging();
         synchronized (mQuotaController.mLock) {
             assertEquals(JobSchedulerService.Constants.DEFAULT_RUNTIME_FREE_QUOTA_MAX_LIMIT_MS,
                     mQuotaController.getMaxJobExecutionTimeMsLocked((job)));
+            assertEquals(JobSchedulerService.Constants.DEFAULT_RUNTIME_FREE_QUOTA_MAX_LIMIT_MS,
+                    mQuotaController.getMaxJobExecutionTimeMsLocked((jobDefIWF)));
+            assertEquals(JobSchedulerService.Constants.DEFAULT_RUNTIME_FREE_QUOTA_MAX_LIMIT_MS,
+                    mQuotaController.getMaxJobExecutionTimeMsLocked((jobHigh)));
         }
 
         setDischarging();
         setProcessState(ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE);
         synchronized (mQuotaController.mLock) {
-            assertEquals(JobSchedulerService.Constants.DEFAULT_RUNTIME_FREE_QUOTA_MAX_LIMIT_MS,
+            assertEquals(timeUntilQuotaConsumedMs,
                     mQuotaController.getMaxJobExecutionTimeMsLocked((job)));
+            assertEquals(JobSchedulerService.Constants.DEFAULT_RUNTIME_FREE_QUOTA_MAX_LIMIT_MS,
+                    mQuotaController.getMaxJobExecutionTimeMsLocked((jobDefIWF)));
+            assertEquals(JobSchedulerService.Constants.DEFAULT_RUNTIME_FREE_QUOTA_MAX_LIMIT_MS,
+                    mQuotaController.getMaxJobExecutionTimeMsLocked((jobHigh)));
         }
 
         // Top-started job
         setProcessState(ActivityManager.PROCESS_STATE_TOP);
         synchronized (mQuotaController.mLock) {
-            mQuotaController.maybeStartTrackingJobLocked(job, null);
+            trackJobs(job, jobDefIWF, jobHigh);
             mQuotaController.prepareForExecutionLocked(job);
+            mQuotaController.prepareForExecutionLocked(jobDefIWF);
+            mQuotaController.prepareForExecutionLocked(jobHigh);
         }
         setProcessState(ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND);
         synchronized (mQuotaController.mLock) {
-            assertEquals(JobSchedulerService.Constants.DEFAULT_RUNTIME_FREE_QUOTA_MAX_LIMIT_MS,
+            assertEquals(timeUntilQuotaConsumedMs,
                     mQuotaController.getMaxJobExecutionTimeMsLocked((job)));
+            assertEquals(JobSchedulerService.Constants.DEFAULT_RUNTIME_FREE_QUOTA_MAX_LIMIT_MS,
+                    mQuotaController.getMaxJobExecutionTimeMsLocked((jobDefIWF)));
+            assertEquals(JobSchedulerService.Constants.DEFAULT_RUNTIME_FREE_QUOTA_MAX_LIMIT_MS,
+                    mQuotaController.getMaxJobExecutionTimeMsLocked((jobHigh)));
             mQuotaController.maybeStopTrackingJobLocked(job, null);
+            mQuotaController.maybeStopTrackingJobLocked(jobDefIWF, null);
+            mQuotaController.maybeStopTrackingJobLocked(jobHigh, null);
         }
 
         setProcessState(ActivityManager.PROCESS_STATE_RECEIVER);
         synchronized (mQuotaController.mLock) {
-            assertEquals(7 * MINUTE_IN_MILLIS,
+            assertEquals(timeUntilQuotaConsumedMs,
                     mQuotaController.getMaxJobExecutionTimeMsLocked(job));
+            assertEquals(timeUntilQuotaConsumedMs,
+                    mQuotaController.getMaxJobExecutionTimeMsLocked(jobDefIWF));
+            assertEquals(timeUntilQuotaConsumedMs,
+                    mQuotaController.getMaxJobExecutionTimeMsLocked(jobHigh));
         }
     }
 
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/DistractingPackageHelperTest.kt b/services/tests/mockingservicestests/src/com/android/server/pm/DistractingPackageHelperTest.kt
index cf81f0a..e131a98 100644
--- a/services/tests/mockingservicestests/src/com/android/server/pm/DistractingPackageHelperTest.kt
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/DistractingPackageHelperTest.kt
@@ -16,12 +16,14 @@
 
 package com.android.server.pm
 
+import android.app.AppOpsManager
 import android.content.Intent
 import android.content.pm.PackageManager
 import android.os.Binder
 import com.android.server.testutils.any
 import com.android.server.testutils.eq
 import com.android.server.testutils.nullable
+import com.android.server.testutils.whenever
 import com.google.common.truth.Truth.assertThat
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -31,6 +33,7 @@
 import org.mockito.Mockito.never
 import org.mockito.Mockito.verify
 
+
 @RunWith(JUnit4::class)
 class DistractingPackageHelperTest : PackageHelperTestBase() {
 
@@ -40,6 +43,9 @@
         super.setup()
         distractingPackageHelper = DistractingPackageHelper(
                 pms, broadcastHelper, suspendPackageHelper)
+        whenever(rule.mocks().appOpsManager.checkOpNoThrow(
+                eq(AppOpsManager.OP_SYSTEM_EXEMPT_FROM_SUSPENSION), any(), any()))
+                .thenReturn(AppOpsManager.MODE_DEFAULT)
     }
 
     @Test
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/MockSystem.kt b/services/tests/mockingservicestests/src/com/android/server/pm/MockSystem.kt
index 46806f3..28bd987 100644
--- a/services/tests/mockingservicestests/src/com/android/server/pm/MockSystem.kt
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/MockSystem.kt
@@ -15,6 +15,7 @@
  */
 package com.android.server.pm
 
+import android.app.AppOpsManager
 import android.app.PropertyInvalidatedCache
 import android.content.Context
 import android.content.Intent
@@ -151,7 +152,7 @@
                 .mockStatic(EventLog::class.java)
                 .mockStatic(LocalServices::class.java)
                 .mockStatic(LocalManagerRegistry::class.java)
-                .mockStatic(DeviceConfig::class.java)
+                .mockStatic(DeviceConfig::class.java, Mockito.CALLS_REAL_METHODS)
                 .mockStatic(HexEncoding::class.java)
                 .apply(withSession)
         session = apply.startMocking()
@@ -192,6 +193,7 @@
         val userManagerService: UserManagerService = mock()
         val componentResolver: ComponentResolver = mock()
         val permissionManagerInternal: PermissionManagerServiceInternal = mock()
+        val appOpsManager: AppOpsManager = mock()
         val incrementalManager: IncrementalManager = mock()
         val platformCompat: PlatformCompat = mock()
         val settings: Settings = mock()
@@ -304,6 +306,7 @@
         whenever(mocks.injector.defaultAppProvider) { mocks.defaultAppProvider }
         whenever(mocks.injector.backgroundHandler) { mocks.backgroundHandler }
         whenever(mocks.injector.updateOwnershipHelper) { mocks.updateOwnershipHelper }
+        whenever(mocks.injector.getSystemService(AppOpsManager::class.java)) { mocks.appOpsManager }
         wheneverStatic { SystemConfig.getInstance() }.thenReturn(mocks.systemConfig)
         whenever(mocks.systemConfig.availableFeatures).thenReturn(DEFAULT_AVAILABLE_FEATURES_MAP)
         whenever(mocks.systemConfig.sharedLibraries).thenReturn(DEFAULT_SHARED_LIBRARIES_LIST)
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/SuspendPackageHelperTest.kt b/services/tests/mockingservicestests/src/com/android/server/pm/SuspendPackageHelperTest.kt
index 7b381ce..1473033 100644
--- a/services/tests/mockingservicestests/src/com/android/server/pm/SuspendPackageHelperTest.kt
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/SuspendPackageHelperTest.kt
@@ -16,12 +16,14 @@
 
 package com.android.server.pm
 
+import android.app.AppOpsManager
 import android.content.Intent
 import android.content.pm.SuspendDialogInfo
 import android.os.Binder
 import android.os.PersistableBundle
 import com.android.server.testutils.any
 import com.android.server.testutils.eq
+import com.android.server.testutils.whenever
 import com.google.common.truth.Truth.assertThat
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -32,6 +34,12 @@
 
 @RunWith(JUnit4::class)
 class SuspendPackageHelperTest : PackageHelperTestBase() {
+    override fun setup() {
+        super.setup()
+        whenever(rule.mocks().appOpsManager.checkOpNoThrow(
+                eq(AppOpsManager.OP_SYSTEM_EXEMPT_FROM_SUSPENSION), any(), any()))
+                .thenReturn(AppOpsManager.MODE_DEFAULT)
+    }
 
     @Test
     fun setPackagesSuspended() {
diff --git a/services/tests/powerstatstests/Android.bp b/services/tests/powerstatstests/Android.bp
index f02e5a5..07197b1 100644
--- a/services/tests/powerstatstests/Android.bp
+++ b/services/tests/powerstatstests/Android.bp
@@ -12,6 +12,7 @@
     ],
 
     exclude_srcs: [
+        "src/com/android/server/power/stats/MultiStateStatsTest.java",
         "src/com/android/server/power/stats/PowerStatsStoreTest.java",
     ],
 
@@ -62,13 +63,12 @@
     static_libs: [
         "services.core",
         "modules-utils-binary-xml",
-
         "androidx.annotation_annotation",
         "androidx.test.rules",
     ],
     srcs: [
+        "src/com/android/server/power/stats/MultiStateStatsTest.java",
         "src/com/android/server/power/stats/PowerStatsStoreTest.java",
     ],
-    sdk_version: "test_current",
     auto_gen_config: true,
 }
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/AggregatePowerStatsProcessorTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/AggregatePowerStatsProcessorTest.java
index 48e2dd7..6d61dc8 100644
--- a/services/tests/powerstatstests/src/com/android/server/power/stats/AggregatePowerStatsProcessorTest.java
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/AggregatePowerStatsProcessorTest.java
@@ -26,8 +26,6 @@
 import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
 
-import com.android.internal.os.MultiStateStats;
-
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/CpuAggregatedPowerStatsProcessorTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/CpuAggregatedPowerStatsProcessorTest.java
index 8ca4ff6..993d834 100644
--- a/services/tests/powerstatstests/src/com/android/server/power/stats/CpuAggregatedPowerStatsProcessorTest.java
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/CpuAggregatedPowerStatsProcessorTest.java
@@ -38,7 +38,6 @@
 import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
 
-import com.android.internal.os.MultiStateStats;
 import com.android.internal.os.PowerProfile;
 import com.android.internal.os.PowerStats;
 
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/MultiStateStatsTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/MultiStateStatsTest.java
index eb03a6c..e8f46b3 100644
--- a/services/tests/powerstatstests/src/com/android/server/power/stats/MultiStateStatsTest.java
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/MultiStateStatsTest.java
@@ -22,12 +22,12 @@
 import static org.junit.Assert.assertThrows;
 
 import android.os.BatteryConsumer;
+import android.platform.test.ravenwood.RavenwoodRule;
 
 import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
 
-import com.android.internal.os.MultiStateStats;
-
+import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
@@ -39,6 +39,10 @@
 @SmallTest
 public class MultiStateStatsTest {
 
+    @Rule
+    public final RavenwoodRule mRavenwood = new RavenwoodRule.Builder()
+            .build();
+
     public static final int DIMENSION_COUNT = 2;
 
     @Test
diff --git a/services/tests/servicestests/Android.bp b/services/tests/servicestests/Android.bp
index 2ece8c7..9b84190 100644
--- a/services/tests/servicestests/Android.bp
+++ b/services/tests/servicestests/Android.bp
@@ -79,6 +79,7 @@
         "coretests-aidl",
         "securebox",
         "flag-junit",
+        "ravenwood-junit",
     ],
 
     libs: [
@@ -140,6 +141,23 @@
     resource_zips: [":FrameworksServicesTests_apks_as_resources"],
 }
 
+android_ravenwood_test {
+    name: "FrameworksServicesTestsRavenwood",
+    libs: [
+        "android.test.mock",
+    ],
+    static_libs: [
+        "androidx.annotation_annotation",
+        "androidx.test.rules",
+        "mockito_ravenwood",
+        "services.core",
+    ],
+    srcs: [
+        "src/com/android/server/uri/**/*.java",
+    ],
+    auto_gen_config: true,
+}
+
 java_library {
     name: "servicestests-core-utils",
     srcs: [
diff --git a/services/tests/servicestests/res/xml/usertypes_test_profile.xml b/services/tests/servicestests/res/xml/usertypes_test_profile.xml
index e89199d..6537d47 100644
--- a/services/tests/servicestests/res/xml/usertypes_test_profile.xml
+++ b/services/tests/servicestests/res/xml/usertypes_test_profile.xml
@@ -40,11 +40,13 @@
             mediaSharedWithParent='true'
             credentialShareableWithParent='false'
             authAlwaysRequiredToDisableQuietMode='true'
+            allowStoppingUserWithDelayedLocking='true'
             showInSettings='23'
             hideInSettingsInQuietMode='true'
             inheritDevicePolicy='450'
             deleteAppWithParent='false'
             alwaysVisible='true'
+            crossProfileContentSharingStrategy='0'
         />
     </profile-type>
     <profile-type name='custom.test.1' max-allowed-per-parent='14' />
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandlerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandlerTest.java
index 9114027..a9967f6 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandlerTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandlerTest.java
@@ -717,6 +717,45 @@
     }
 
     @Test
+    @RequiresFlagsEnabled(Flags.FLAG_ENABLE_MAGNIFICATION_MULTIPLE_FINGER_MULTIPLE_TAP_GESTURE)
+    public void testSecondFingerSwipe_twoPointerDownAndActivatedState_shouldInPanningState() {
+        goFromStateIdleTo(STATE_ACTIVATED);
+        PointF pointer1 = DEFAULT_POINT;
+        PointF pointer2 = new PointF(DEFAULT_X * 1.5f, DEFAULT_Y);
+
+        send(downEvent());
+        send(pointerEvent(ACTION_POINTER_DOWN, new PointF[] {pointer1, pointer2}, 1));
+        //The minimum movement to transit to panningState.
+        final float sWipeMinDistance = ViewConfiguration.get(mContext).getScaledTouchSlop();
+        pointer2.offset(sWipeMinDistance + 1, 0);
+        send(pointerEvent(ACTION_MOVE, new PointF[] {pointer1, pointer2}, 1));
+        fastForward(ViewConfiguration.getTapTimeout());
+        assertIn(STATE_PANNING);
+
+        returnToNormalFrom(STATE_PANNING);
+    }
+
+    @Test
+    @RequiresFlagsEnabled(Flags.FLAG_ENABLE_MAGNIFICATION_MULTIPLE_FINGER_MULTIPLE_TAP_GESTURE)
+    public void testTowFingerSwipe_twoPointerDownAndShortcutTriggeredState_shouldInPanningState() {
+        goFromStateIdleTo(STATE_SHORTCUT_TRIGGERED);
+        PointF pointer1 = DEFAULT_POINT;
+        PointF pointer2 = new PointF(DEFAULT_X * 1.5f, DEFAULT_Y);
+
+        send(downEvent());
+        send(pointerEvent(ACTION_POINTER_DOWN, new PointF[] {pointer1, pointer2}, 1));
+        //The minimum movement to transit to panningState.
+        final float sWipeMinDistance = ViewConfiguration.get(mContext).getScaledTouchSlop();
+        pointer2.offset(sWipeMinDistance + 1, 0);
+        send(pointerEvent(ACTION_MOVE, new PointF[] {pointer1, pointer2}, 1));
+        fastForward(ViewConfiguration.getTapTimeout());
+        assertIn(STATE_PANNING);
+
+        returnToNormalFrom(STATE_PANNING);
+    }
+
+    @Test
+    @RequiresFlagsDisabled(Flags.FLAG_ENABLE_MAGNIFICATION_MULTIPLE_FINGER_MULTIPLE_TAP_GESTURE)
     public void testSecondFingerSwipe_twoPointerDownAndActivatedState_panningState() {
         goFromStateIdleTo(STATE_ACTIVATED);
         PointF pointer1 = DEFAULT_POINT;
@@ -734,6 +773,7 @@
     }
 
     @Test
+    @RequiresFlagsDisabled(Flags.FLAG_ENABLE_MAGNIFICATION_MULTIPLE_FINGER_MULTIPLE_TAP_GESTURE)
     public void testSecondFingerSwipe_twoPointerDownAndShortcutTriggeredState_panningState() {
         goFromStateIdleTo(STATE_SHORTCUT_TRIGGERED);
         PointF pointer1 = DEFAULT_POINT;
diff --git a/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java b/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java
index d26d671..77b1455 100644
--- a/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java
@@ -92,6 +92,7 @@
 import android.os.UserManager;
 import android.os.storage.IStorageManager;
 import android.platform.test.annotations.Presubmit;
+import android.platform.test.flag.junit.SetFlagsRule;
 import android.util.Log;
 import android.view.Display;
 
@@ -104,11 +105,14 @@
 import com.android.server.pm.UserJourneyLogger;
 import com.android.server.pm.UserManagerInternal;
 import com.android.server.pm.UserManagerService;
+import com.android.server.pm.UserTypeDetails;
+import com.android.server.pm.UserTypeFactory;
 import com.android.server.wm.ActivityTaskManagerInternal;
 import com.android.server.wm.WindowManagerService;
 
 import org.junit.After;
 import org.junit.Before;
+import org.junit.Rule;
 import org.junit.Test;
 import org.mockito.ArgumentCaptor;
 
@@ -175,6 +179,9 @@
             USER_START_MSG,
             REPORT_LOCKED_BOOT_COMPLETE_MSG);
 
+    @Rule
+    public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
+
     @Before
     public void setUp() throws Exception {
         runWithDexmakerShareClassLoader(() -> {
@@ -789,30 +796,101 @@
     }
 
     @Test
-    public void testStartProfile() throws Exception {
-        setUpAndStartProfileInBackground(TEST_USER_ID1);
+    public void testStartManagedProfile() throws Exception {
+        setUpAndStartProfileInBackground(TEST_USER_ID1, UserManager.USER_TYPE_PROFILE_MANAGED);
 
         startBackgroundUserAssertions();
         verifyUserAssignedToDisplay(TEST_USER_ID1, Display.DEFAULT_DISPLAY);
     }
 
     @Test
-    public void testStartProfile_whenUsersOnSecondaryDisplaysIsEnabled() throws Exception {
+    public void testStartManagedProfile_whenUsersOnSecondaryDisplaysIsEnabled() throws Exception {
         mockIsUsersOnSecondaryDisplaysEnabled(true);
 
-        setUpAndStartProfileInBackground(TEST_USER_ID1);
+        setUpAndStartProfileInBackground(TEST_USER_ID1, UserManager.USER_TYPE_PROFILE_MANAGED);
 
         startBackgroundUserAssertions();
         verifyUserAssignedToDisplay(TEST_USER_ID1, Display.DEFAULT_DISPLAY);
     }
 
     @Test
-    public void testStopProfile() throws Exception {
-        setUpAndStartProfileInBackground(TEST_USER_ID1);
+    public void testStopManagedProfile() throws Exception {
+        setUpAndStartProfileInBackground(TEST_USER_ID1, UserManager.USER_TYPE_PROFILE_MANAGED);
         assertProfileLockedOrUnlockedAfterStopping(TEST_USER_ID1, /* expectLocking= */ true);
         verifyUserUnassignedFromDisplay(TEST_USER_ID1);
     }
 
+    @Test
+    public void testStopPrivateProfile() throws Exception {
+        mUserController.setInitialConfig(/* mUserSwitchUiEnabled */ true,
+                /* maxRunningUsers= */ 3, /* delayUserDataLocking= */ false);
+        mSetFlagsRule.enableFlags(android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE,
+                android.multiuser.Flags.FLAG_ENABLE_BIOMETRICS_TO_UNLOCK_PRIVATE_SPACE);
+        setUpAndStartProfileInBackground(TEST_USER_ID1, UserManager.USER_TYPE_PROFILE_PRIVATE);
+        assertProfileLockedOrUnlockedAfterStopping(TEST_USER_ID1, /* expectLocking= */ true);
+        verifyUserUnassignedFromDisplay(TEST_USER_ID1);
+
+        mSetFlagsRule.disableFlags(
+                android.multiuser.Flags.FLAG_ENABLE_BIOMETRICS_TO_UNLOCK_PRIVATE_SPACE);
+        setUpAndStartProfileInBackground(TEST_USER_ID2, UserManager.USER_TYPE_PROFILE_PRIVATE);
+        assertProfileLockedOrUnlockedAfterStopping(TEST_USER_ID2, /* expectLocking= */ true);
+        verifyUserUnassignedFromDisplay(TEST_USER_ID2);
+    }
+
+    @Test
+    public void testStopPrivateProfileWithDelayedLocking() throws Exception {
+        mUserController.setInitialConfig(/* mUserSwitchUiEnabled */ true,
+                /* maxRunningUsers= */ 3, /* delayUserDataLocking= */ false);
+        mSetFlagsRule.enableFlags(android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE,
+                android.multiuser.Flags.FLAG_ENABLE_BIOMETRICS_TO_UNLOCK_PRIVATE_SPACE);
+        setUpAndStartProfileInBackground(TEST_USER_ID1, UserManager.USER_TYPE_PROFILE_PRIVATE);
+        assertUserLockedOrUnlockedAfterStopping(TEST_USER_ID1, /* delayedLocking= */ true,
+                /* keyEvictedCallback */ null, /* expectLocking= */ false);
+    }
+
+    @Test
+    public void testStopPrivateProfileWithDelayedLocking_flagDisabled() throws Exception {
+        mUserController.setInitialConfig(/* mUserSwitchUiEnabled */ true,
+                /* maxRunningUsers= */ 3, /* delayUserDataLocking= */ false);
+        mSetFlagsRule.enableFlags(android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE);
+        mSetFlagsRule.disableFlags(
+                android.multiuser.Flags.FLAG_ENABLE_BIOMETRICS_TO_UNLOCK_PRIVATE_SPACE);
+        setUpAndStartProfileInBackground(TEST_USER_ID1, UserManager.USER_TYPE_PROFILE_PRIVATE);
+        assertUserLockedOrUnlockedAfterStopping(TEST_USER_ID1, /* delayedLocking= */ true,
+                /* keyEvictedCallback */ null, /* expectLocking= */ true);
+
+        mSetFlagsRule.disableFlags(android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE);
+        mSetFlagsRule.enableFlags(
+                android.multiuser.Flags.FLAG_ENABLE_BIOMETRICS_TO_UNLOCK_PRIVATE_SPACE);
+        setUpAndStartProfileInBackground(TEST_USER_ID2, UserManager.USER_TYPE_PROFILE_PRIVATE);
+        assertUserLockedOrUnlockedAfterStopping(TEST_USER_ID2, /* delayedLocking= */ true,
+                /* keyEvictedCallback */ null, /* expectLocking= */ true);
+    }
+
+    @Test
+    public void testStopPrivateProfileWithDelayedLocking_maxRunningUsersBreached()
+            throws Exception {
+        mUserController.setInitialConfig(/* mUserSwitchUiEnabled */ true,
+                /* maxRunningUsers= */ 1, /* delayUserDataLocking= */ false);
+        mSetFlagsRule.enableFlags(android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE,
+                android.multiuser.Flags.FLAG_ENABLE_BIOMETRICS_TO_UNLOCK_PRIVATE_SPACE);
+        setUpAndStartProfileInBackground(TEST_USER_ID1, UserManager.USER_TYPE_PROFILE_PRIVATE);
+        setUpAndStartProfileInBackground(TEST_USER_ID2, UserManager.USER_TYPE_PROFILE_MANAGED);
+        assertUserLockedOrUnlockedAfterStopping(TEST_USER_ID1, /* delayedLocking= */ true,
+                /* keyEvictedCallback */ null, /* expectLocking= */ true);
+    }
+
+    @Test
+    public void testStopManagedProfileWithDelayedLocking() throws Exception {
+        mUserController.setInitialConfig(/* mUserSwitchUiEnabled */ true,
+                /* maxRunningUsers= */ 3, /* delayUserDataLocking= */ false);
+        mSetFlagsRule.enableFlags(android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE,
+                android.multiuser.Flags.FLAG_ENABLE_BIOMETRICS_TO_UNLOCK_PRIVATE_SPACE);
+        setUpAndStartProfileInBackground(TEST_USER_ID1, UserManager.USER_TYPE_PROFILE_MANAGED);
+        assertUserLockedOrUnlockedAfterStopping(TEST_USER_ID1, /* delayedLocking= */ true,
+                /* keyEvictedCallback */ null, /* expectLocking= */ true);
+    }
+
     /** Tests handleIncomingUser() for a variety of permissions and situations. */
     @Test
     public void testHandleIncomingUser() throws Exception {
@@ -1001,8 +1079,8 @@
         mUserStates.put(userId, mUserController.getStartedUserState(userId));
     }
 
-    private void setUpAndStartProfileInBackground(int userId) throws Exception {
-        setUpUser(userId, UserInfo.FLAG_PROFILE, false, UserManager.USER_TYPE_PROFILE_MANAGED);
+    private void setUpAndStartProfileInBackground(int userId, String userType) throws Exception {
+        setUpUser(userId, UserInfo.FLAG_PROFILE, false, userType);
         assertThat(mUserController.startProfile(userId, /* evenWhenDisabled=*/ false,
                 /* unlockListener= */ null)).isTrue();
 
@@ -1070,6 +1148,11 @@
         userInfo.preCreated = preCreated;
         when(mInjector.mUserManagerMock.getUserInfo(eq(userId))).thenReturn(userInfo);
         when(mInjector.mUserManagerMock.isPreCreated(userId)).thenReturn(preCreated);
+
+        UserTypeDetails userTypeDetails = UserTypeFactory.getUserTypes().get(userType);
+        assertThat(userTypeDetails).isNotNull();
+        when(mInjector.mUserManagerInternalMock.getUserProperties(eq(userId)))
+                .thenReturn(userTypeDetails.getDefaultUserPropertiesReference());
     }
 
     private static List<String> getActions(List<Intent> intents) {
diff --git a/services/tests/servicestests/src/com/android/server/audio/MusicFxHelperTest.java b/services/tests/servicestests/src/com/android/server/audio/MusicFxHelperTest.java
new file mode 100644
index 0000000..472a82c
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/audio/MusicFxHelperTest.java
@@ -0,0 +1,642 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.server.audio;
+
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyObject;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.ActivityInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.media.audiofx.AudioEffect;
+import android.os.Message;
+import android.util.Log;
+
+import androidx.test.filters.MediumTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.ArrayList;
+import java.util.List;
+
+@MediumTest
+@RunWith(AndroidJUnit4.class)
+public class MusicFxHelperTest {
+    private static final String TAG = "MusicFxHelperTest";
+
+    @Mock private AudioService mMockAudioService;
+    @Mock private Context mMockContext;
+    @Mock private PackageManager mMockPackageManager;
+
+    private ResolveInfo mResolveInfo1 = new ResolveInfo();
+    private ResolveInfo mResolveInfo2 = new ResolveInfo();
+    private final String mTestPkg1 = "testPkg1", mTestPkg2 = "testPkg2", mTestPkg3 = "testPkg3";
+    private final String mMusicFxPkgName = "com.android.musicfx";
+    private final int mTestUid1 = 1, mTestUid2 = 2, mTestUid3 = 3, mMusicFxUid = 78;
+    private final int mTestSession1 = 11, mTestSession2 = 22, mTestSession3 = 33;
+
+    private List<ResolveInfo> mEmptyList = new ArrayList<>();
+    private List<ResolveInfo> mSingleList = new ArrayList<>();
+    private List<ResolveInfo> mDoubleList = new ArrayList<>();
+
+    // the class being unit-tested here
+    @InjectMocks private MusicFxHelper mMusicFxHelper;
+
+    @Before
+    @SuppressWarnings("DirectInvocationOnMock")
+    public void setUp() throws Exception {
+        mMockAudioService = mock(AudioService.class);
+        mMusicFxHelper = mMockAudioService.getMusicFxHelper();
+        MockitoAnnotations.initMocks(this);
+
+        mResolveInfo1.activityInfo = new ActivityInfo();
+        mResolveInfo1.activityInfo.packageName = mTestPkg1;
+        mResolveInfo2.activityInfo = new ActivityInfo();
+        mResolveInfo2.activityInfo.packageName = mTestPkg2;
+
+        mSingleList.add(mResolveInfo1);
+        mDoubleList.add(mResolveInfo1);
+        mDoubleList.add(mResolveInfo2);
+
+        Assert.assertNotNull(mMusicFxHelper);
+    }
+
+    private Intent newIntent(String action, String packageName, int sessionId) {
+        Intent intent = new Intent(action);
+        if (packageName != null) {
+            intent.putExtra(AudioEffect.EXTRA_PACKAGE_NAME, packageName);
+        }
+        intent.putExtra(AudioEffect.EXTRA_AUDIO_SESSION, sessionId);
+        return intent;
+    }
+
+    /**
+     * Helper function to send ACTION_OPEN_AUDIO_EFFECT_CONTROL_SESSION intent with verification.
+     *
+     * @throws NameNotFoundException if no such package is available to the caller.
+     */
+    private void openSessionWithResList(
+            List<ResolveInfo> list, int bind, int broadcast, String packageName, int audioSession,
+            int uid) {
+        doReturn(mMockPackageManager).when(mMockContext).getPackageManager();
+        doReturn(list).when(mMockPackageManager).queryBroadcastReceivers(anyObject(), anyInt());
+        if (list != null && list.size() != 0) {
+            try {
+                doReturn(uid).when(mMockPackageManager)
+                        .getPackageUidAsUser(eq(packageName), anyObject(), anyInt());
+                doReturn(mMusicFxUid).when(mMockPackageManager)
+                        .getPackageUidAsUser(eq(mMusicFxPkgName), anyObject(), anyInt());
+            } catch (PackageManager.NameNotFoundException e) {
+                Log.e(TAG, "NameNotFoundException: " + e);
+            }
+        }
+
+        Intent intent = newIntent(AudioEffect.ACTION_OPEN_AUDIO_EFFECT_CONTROL_SESSION,
+                packageName, audioSession);
+        mMusicFxHelper.handleAudioEffectBroadcast(mMockContext, intent);
+        verify(mMockContext, times(bind))
+                .bindServiceAsUser(anyObject(), anyObject(), anyInt(), anyObject());
+        verify(mMockContext, times(broadcast)).sendBroadcastAsUser(anyObject(), anyObject());
+    }
+
+    /**
+     * Helper function to send ACTION_CLOSE_AUDIO_EFFECT_CONTROL_SESSION intent with verification.
+     *
+     * @throws NameNotFoundException if no such package is available to the caller.
+     */
+    private void closeSessionWithResList(
+                List<ResolveInfo> list, int unBind, int broadcast, String packageName,
+                int audioSession, int uid) {
+        doReturn(mMockPackageManager).when(mMockContext).getPackageManager();
+        doReturn(list).when(mMockPackageManager).queryBroadcastReceivers(anyObject(), anyInt());
+        if (list != null && list.size() != 0) {
+            try {
+                doReturn(uid).when(mMockPackageManager)
+                        .getPackageUidAsUser(eq(packageName), anyObject(), anyInt());
+                doReturn(mMusicFxUid).when(mMockPackageManager)
+                        .getPackageUidAsUser(eq(mMusicFxPkgName), anyObject(), anyInt());
+            } catch (PackageManager.NameNotFoundException e) {
+                Log.e(TAG, "NameNotFoundException: " + e);
+            }
+        }
+
+        Intent intent = newIntent(AudioEffect.ACTION_CLOSE_AUDIO_EFFECT_CONTROL_SESSION,
+                                packageName, audioSession);
+        mMusicFxHelper.handleAudioEffectBroadcast(mMockContext, intent);
+        verify(mMockContext, times(unBind)).unbindService(anyObject());
+        verify(mMockContext, times(broadcast)).sendBroadcastAsUser(anyObject(), anyObject());
+    }
+
+    /**
+     * Helper function to send MSG_EFFECT_CLIENT_GONE message with verification.
+     */
+    private void sendMessage(int msgId, int uid, int unBinds, int broadcasts) {
+        mMusicFxHelper.handleMessage(Message.obtain(null, msgId, uid /* arg1 */, 0 /* arg2 */));
+        verify(mMockContext, times(broadcasts)).sendBroadcastAsUser(anyObject(), anyObject());
+        verify(mMockContext, times(unBinds)).unbindService(anyObject());
+    }
+
+    /**
+     * Send invalid message to MusicFxHelper.
+     */
+    @Test
+    public void testInvalidMessage() {
+        Log.i(TAG, "running testInvalidMessage");
+
+        sendMessage(MusicFxHelper.MSG_EFFECT_CLIENT_GONE - 1, 0, 0, 0);
+        sendMessage(MusicFxHelper.MSG_EFFECT_CLIENT_GONE + 1, 0, 0, 0);
+    }
+
+    /**
+     * Send client gone message to MusicFxHelper when no client exist.
+     */
+    @Test
+    public void testGoneMessageWhenNoClient() {
+        Log.i(TAG, "running testGoneMessageWhenNoClient");
+
+        sendMessage(MusicFxHelper.MSG_EFFECT_CLIENT_GONE, 0, 0, 0);
+    }
+
+    /**
+     * Send ACTION_CLOSE_AUDIO_EFFECT_CONTROL_SESSION intent to MusicFxHelper when no session exist.
+     */
+    @Test
+    public void testCloseBroadcastIntent() {
+        Log.i(TAG, "running testCloseBroadcastIntent");
+
+        closeSessionWithResList(null, 0, 0, null, mTestSession1, mTestUid1);
+    }
+
+    /**
+     * OPEN/CLOSE AUDIO_EFFECT_CONTROL_SESSION intent when target application package was set.
+     * When the target application package was set for an intent, it means this intent is limited
+     * to a specific target application, as a result MusicFxHelper will not handle this intent.
+     */
+    @Test
+    public void testBroadcastIntentWithPackage() {
+        Log.i(TAG, "running testBroadcastIntentWithPackage");
+
+        Intent intent = newIntent(AudioEffect.ACTION_OPEN_AUDIO_EFFECT_CONTROL_SESSION, null, 1);
+        intent.setPackage(mTestPkg1);
+        mMusicFxHelper.handleAudioEffectBroadcast(mMockContext, intent);
+        verify(mMockContext, times(0))
+                .bindServiceAsUser(anyObject(), anyObject(), anyInt(), anyObject());
+        verify(mMockContext, times(0)).sendBroadcastAsUser(anyObject(), anyObject());
+
+        intent = newIntent(AudioEffect.ACTION_CLOSE_AUDIO_EFFECT_CONTROL_SESSION, null, 1);
+        intent.setPackage(mTestPkg2);
+        mMusicFxHelper.handleAudioEffectBroadcast(mMockContext, intent);
+        verify(mMockContext, times(0))
+                .bindServiceAsUser(anyObject(), anyObject(), anyInt(), anyObject());
+        verify(mMockContext, times(0)).sendBroadcastAsUser(anyObject(), anyObject());
+    }
+
+    /**
+     * OPEN/CLOSE AUDIO_EFFECT_CONTROL_SESSION with no broadcast receiver.
+     */
+    @Test
+    public void testBroadcastIntentWithNoPackageAndNoBroadcastReceiver() {
+        Log.i(TAG, "running testBroadcastIntentWithNoPackageAndNoBroadcastReceiver");
+
+        openSessionWithResList(mEmptyList, 0, 0, null, mTestSession1, mTestUid1);
+        closeSessionWithResList(mEmptyList, 0, 0, null, mTestSession1, mTestUid1);
+    }
+
+    /**
+     * OPEN/CLOSE AUDIO_EFFECT_CONTROL_SESSION with one broadcast receiver.
+     */
+    @Test
+    public void testBroadcastIntentWithNoPackageAndOneBroadcastReceiver() {
+        Log.i(TAG, "running testBroadcastIntentWithNoPackageAndOneBroadcastReceiver");
+
+        int broadcasts = 1, bind = 1, unbind = 1;
+        openSessionWithResList(mSingleList, bind, broadcasts, null, mTestSession1, mTestUid1);
+        broadcasts = broadcasts + 1;
+        closeSessionWithResList(mSingleList, unbind, broadcasts, null, mTestSession1, mTestUid1);
+
+        // repeat with different session ID
+        broadcasts = broadcasts + 1;
+        bind = bind + 1;
+        unbind = unbind + 1;
+        openSessionWithResList(mSingleList, bind, broadcasts, null, mTestSession2, mTestUid1);
+        broadcasts = broadcasts + 1;
+        closeSessionWithResList(mSingleList, unbind, broadcasts, null, mTestSession2, mTestUid1);
+
+        // repeat with different UID
+        broadcasts = broadcasts + 1;
+        bind = bind + 1;
+        unbind = unbind + 1;
+        openSessionWithResList(mSingleList, bind, broadcasts, null, mTestSession1, mTestUid2);
+        broadcasts = broadcasts + 1;
+        closeSessionWithResList(mSingleList, unbind, broadcasts, null, mTestSession1, mTestUid2);
+    }
+
+    /**
+     * OPEN/CLOSE AUDIO_EFFECT_CONTROL_SESSION with two broadcast receivers.
+     */
+    @Test
+    public void testBroadcastIntentWithNoPackageAndTwoBroadcastReceivers() {
+        Log.i(TAG, "running testBroadcastIntentWithNoPackageAndTwoBroadcastReceivers");
+
+        openSessionWithResList(mDoubleList, 1, 1, null, mTestSession1, mTestUid1);
+        closeSessionWithResList(mDoubleList, 1, 2, null, mTestSession1, mTestUid1);
+    }
+
+    /**
+     * Open/close session UID not matching.
+     * No broadcast for mismatching sessionID/UID/packageName.
+     */
+    @Test
+    public void testBroadcastBadIntents() {
+        Log.i(TAG, "running testBroadcastBadIntents");
+
+        int broadcasts = 1;
+        openSessionWithResList(mSingleList, 1, broadcasts, mTestPkg1, mTestSession1, mTestUid1);
+        // mismatch UID
+        closeSessionWithResList(mSingleList, 0, broadcasts, mTestPkg1, mTestSession1, mTestUid2);
+        // mismatch AudioSession
+        closeSessionWithResList(mSingleList, 0, broadcasts, mTestPkg1, mTestSession2, mTestUid1);
+        // mismatch packageName
+        closeSessionWithResList(mSingleList, 0, broadcasts, mTestPkg2, mTestSession1, mTestUid1);
+
+        // cleanup with correct UID and session ID
+        broadcasts = broadcasts + 1;
+        closeSessionWithResList(mSingleList, 1, broadcasts, mTestPkg1, mTestSession1, mTestUid1);
+    }
+
+    /**
+     * Open/close sessions with one UID, some with correct intents some with illegal intents.
+     * No broadcast for mismatching sessionID/UID/packageName.
+     */
+    @Test
+    public void testBroadcastGoodAndBadIntents() {
+        Log.i(TAG, "running testBroadcastGoodAndBadIntents");
+
+        int broadcasts = 1, bind = 1, unbind = 0;
+        openSessionWithResList(mSingleList, bind, broadcasts, mTestPkg1, mTestSession1, mTestUid1);
+        // mismatch packageName, session ID and UID
+        closeSessionWithResList(mSingleList, unbind, broadcasts, mTestPkg2, mTestSession2,
+                                mTestUid2);
+        // mismatch session ID and mismatch UID
+        closeSessionWithResList(mSingleList, unbind, broadcasts, mTestPkg1, mTestSession2,
+                                mTestUid2);
+        // mismatch packageName and mismatch UID
+        closeSessionWithResList(mSingleList, unbind, broadcasts, mTestPkg2, mTestSession1,
+                                mTestUid2);
+        // mismatch packageName and sessionID
+        closeSessionWithResList(mSingleList, unbind, broadcasts, mTestPkg2, mTestSession2,
+                                mTestUid1);
+        // inconsistency package name for same UID
+        openSessionWithResList(mSingleList, bind, broadcasts, mTestPkg2, mTestSession2, mTestUid1);
+        // open session2 with good intent
+        broadcasts = broadcasts + 1;
+        openSessionWithResList(mSingleList, bind, broadcasts, mTestPkg1, mTestSession2, mTestUid1);
+
+        // cleanup with correct UID and session ID
+        broadcasts = broadcasts + 1;
+        closeSessionWithResList(mSingleList, unbind, broadcasts, mTestPkg1, mTestSession1,
+                                mTestUid1);
+        broadcasts = broadcasts + 1;
+        unbind = unbind + 1;
+        closeSessionWithResList(mSingleList, unbind, broadcasts, mTestPkg1, mTestSession2,
+                                mTestUid1);
+    }
+
+    /**
+     * Send ACTION_OPEN_AUDIO_EFFECT_CONTROL_SESSION when there is no listener.
+     */
+    @Test
+    public void testBroadcastOpenSessionWithValidPackageNameAndNoListener() {
+        Log.i(TAG, "running testBroadcastOpenSessionWithValidPackageNameAndNoListener");
+
+        // null listener list should not trigger any action
+        openSessionWithResList(null, 0, 0, mTestPkg1, mTestSession1, mTestUid1);
+        // empty listener list should not trigger any action
+        openSessionWithResList(mEmptyList, 0, 0, mTestPkg1, mTestSession1, mTestUid1);
+    }
+
+    /**
+     * One MusicFx client, open session and close.
+     */
+    @Test
+    public void testOpenCloseAudioSession() {
+        Log.i(TAG, "running testOpenCloseAudioSession");
+
+        int broadcasts = 1;
+        openSessionWithResList(mDoubleList, 1, broadcasts, mTestPkg1, mTestSession1, mTestUid1);
+        broadcasts = broadcasts + 1;
+        closeSessionWithResList(mDoubleList, 1, broadcasts, mTestPkg1, mTestSession1, mTestUid1);
+    }
+
+    /**
+     * One MusicFx client, open session and close, then gone.
+     */
+    @Test
+    public void testOpenCloseAudioSessionAndGone() {
+        Log.i(TAG, "running testOpenCloseAudioSessionAndGone");
+
+        int broadcasts = 1;
+        openSessionWithResList(mDoubleList, 1, broadcasts, mTestPkg1, mTestSession1, mTestUid1);
+        broadcasts = broadcasts + 1;
+        openSessionWithResList(mDoubleList, 1, broadcasts, mTestPkg1, mTestSession2, mTestUid1);
+        broadcasts = broadcasts + 1;
+        closeSessionWithResList(mDoubleList, 0, broadcasts, mTestPkg1, mTestSession1, mTestUid1);
+
+        broadcasts = broadcasts + 1; // 1 open session left
+        sendMessage(MusicFxHelper.MSG_EFFECT_CLIENT_GONE, mTestUid1, 1, broadcasts);
+    }
+
+    /**
+     * One MusicFx client, open session, then UID gone without close.
+     */
+    @Test
+    public void testOpenOneSessionAndGo() {
+        Log.i(TAG, "running testOpenOneSessionAndGo");
+
+        int broadcasts = 1;
+        openSessionWithResList(mDoubleList, 1, broadcasts, mTestPkg1, mTestSession1, mTestUid1);
+
+        broadcasts = broadcasts + 1;
+        sendMessage(MusicFxHelper.MSG_EFFECT_CLIENT_GONE, mTestUid1, 1, broadcasts);
+    }
+
+    /**
+     * Two MusicFx clients open and close sessions.
+     */
+    @Test
+    public void testOpenTwoSessionsAndClose() {
+        Log.i(TAG, "running testOpenTwoSessionsAndClose");
+
+        int broadcasts = 1, bind = 1, unbind = 0;
+        openSessionWithResList(mDoubleList, bind, broadcasts, mTestPkg1, mTestSession1, mTestUid1);
+        broadcasts = broadcasts + 1;
+        openSessionWithResList(mDoubleList, bind, broadcasts, mTestPkg2, mTestSession2, mTestUid2);
+
+        broadcasts = broadcasts + 1;
+        closeSessionWithResList(mDoubleList, unbind, broadcasts, mTestPkg2, mTestSession2,
+                mTestUid2);
+        broadcasts = broadcasts + 1;
+        unbind = unbind + 1;
+        closeSessionWithResList(mDoubleList, unbind, broadcasts, mTestPkg1, mTestSession1,
+                mTestUid1);
+
+        broadcasts = broadcasts + 1;
+        bind = bind + 1;
+        openSessionWithResList(mDoubleList, bind, broadcasts, mTestPkg2, mTestSession2, mTestUid2);
+        broadcasts = broadcasts + 1;
+        openSessionWithResList(mDoubleList, bind, broadcasts, mTestPkg1, mTestSession1, mTestUid1);
+
+        broadcasts = broadcasts + 1;
+        closeSessionWithResList(mDoubleList, unbind, broadcasts, mTestPkg1, mTestSession1,
+                mTestUid1);
+        broadcasts = broadcasts + 1;
+        unbind = unbind + 1;
+        closeSessionWithResList(mDoubleList, unbind, broadcasts, mTestPkg2, mTestSession2,
+                mTestUid2);
+    }
+
+    /**
+     * Two MusicFx clients open sessions, then both UID gone without close.
+     */
+    @Test
+    public void testOpenTwoSessionsAndGo() {
+        Log.i(TAG, "running testOpenTwoSessionsAndGo");
+
+        int broadcasts = 1, bind = 1, unbind = 0;
+        openSessionWithResList(mDoubleList, bind, broadcasts, mTestPkg1, mTestSession1, mTestUid1);
+        broadcasts = broadcasts + 1;
+        openSessionWithResList(mDoubleList, bind, broadcasts, mTestPkg2, mTestSession2, mTestUid2);
+
+        broadcasts = broadcasts + 1;
+        sendMessage(MusicFxHelper.MSG_EFFECT_CLIENT_GONE, mTestUid1, unbind, broadcasts);
+
+        broadcasts = broadcasts + 1;
+        unbind = unbind + 1;
+        sendMessage(MusicFxHelper.MSG_EFFECT_CLIENT_GONE, mTestUid2, unbind, broadcasts);
+    }
+
+    /**
+     * Two MusicFx clients open sessions, one close but not gone, the other one gone without close.
+     */
+    @Test
+    public void testTwoSessionsOpenOneCloseOneGo() {
+        Log.i(TAG, "running testTwoSessionsOpneAndOneCloseOneGo");
+
+        int broadcasts = 1, bind = 1, unbind = 0;
+        openSessionWithResList(mDoubleList, bind, broadcasts, mTestPkg1, mTestSession1, mTestUid1);
+        broadcasts = broadcasts + 1;
+        openSessionWithResList(mDoubleList, bind, broadcasts, mTestPkg2, mTestSession2, mTestUid2);
+
+        broadcasts = broadcasts + 1;
+        closeSessionWithResList(mDoubleList, unbind, broadcasts, mTestPkg1, mTestSession1,
+                                mTestUid1);
+
+        broadcasts = broadcasts + 1;
+        unbind = unbind + 1;
+        sendMessage(MusicFxHelper.MSG_EFFECT_CLIENT_GONE, mTestUid2, unbind, broadcasts);
+    }
+
+    /**
+     * One MusicFx client, open multiple audio sessions, and close all sessions.
+     */
+    @Test
+    public void testTwoSessionsInSameUidOpenClose() {
+        Log.i(TAG, "running testTwoSessionsOpneAndOneCloseOneGo");
+
+        int broadcasts = 1, bind = 1, unbind = 0;
+        openSessionWithResList(mDoubleList, bind, broadcasts, mTestPkg1, mTestSession1, mTestUid1);
+        broadcasts = broadcasts + 1;
+        openSessionWithResList(mDoubleList, bind, broadcasts, mTestPkg1, mTestSession2, mTestUid1);
+
+        broadcasts = broadcasts + 1;
+        closeSessionWithResList(mDoubleList, unbind, broadcasts, mTestPkg1, mTestSession1,
+                                mTestUid1);
+        broadcasts = broadcasts + 1;
+        unbind = unbind + 1;
+        closeSessionWithResList(mDoubleList, unbind, broadcasts, mTestPkg1, mTestSession2,
+                                mTestUid1);
+    }
+
+    /**
+     * Three MusicFx clients, each with multiple audio sessions, and close all sessions.
+     */
+    @Test
+    public void testThreeSessionsInThreeUidOpenClose() {
+        Log.i(TAG, "running testThreeSessionsInThreeUidOpenClose");
+
+        int broadcasts = 1, bind = 1, unbind = 0;
+        //client1
+        openSessionWithResList(mDoubleList, bind, broadcasts, mTestPkg1, mTestSession1, mTestUid1);
+        broadcasts = broadcasts + 1;
+        openSessionWithResList(mDoubleList, bind, broadcasts, mTestPkg1, mTestSession2, mTestUid1);
+        // client2
+        broadcasts = broadcasts + 1;
+        openSessionWithResList(mDoubleList, bind, broadcasts, mTestPkg2, mTestSession3, mTestUid2);
+        broadcasts = broadcasts + 1;
+        openSessionWithResList(mDoubleList, bind, broadcasts, mTestPkg2, mTestSession2, mTestUid2);
+        // client3
+        broadcasts = broadcasts + 1;
+        openSessionWithResList(mDoubleList, bind, broadcasts, mTestPkg3, mTestSession1, mTestUid3);
+        broadcasts = broadcasts + 1;
+        openSessionWithResList(mDoubleList, bind, broadcasts, mTestPkg3, mTestSession3, mTestUid3);
+
+        broadcasts = broadcasts + 1;
+        closeSessionWithResList(mDoubleList, unbind, broadcasts, mTestPkg1, mTestSession1,
+                                mTestUid1);
+        broadcasts = broadcasts + 1;
+        closeSessionWithResList(mDoubleList, unbind, broadcasts, mTestPkg3, mTestSession3,
+                                mTestUid3);
+        broadcasts = broadcasts + 1;
+        closeSessionWithResList(mDoubleList, unbind, broadcasts, mTestPkg2, mTestSession2,
+                                mTestUid2);
+        // all sessions of client1 closed
+        broadcasts = broadcasts + 1;
+        closeSessionWithResList(mDoubleList, unbind, broadcasts, mTestPkg1, mTestSession2,
+                                mTestUid1);
+        // all sessions of client3 closed
+        broadcasts = broadcasts + 1;
+        closeSessionWithResList(mDoubleList, unbind, broadcasts, mTestPkg3, mTestSession1,
+                                mTestUid3);
+        // all sessions of client2 closed
+        broadcasts = broadcasts + 1;
+        // now expect unbind to happen
+        unbind = unbind + 1;
+        closeSessionWithResList(mDoubleList, unbind, broadcasts, mTestPkg2, mTestSession3,
+                                mTestUid2);
+    }
+
+    /**
+     * Two MusicFx clients, with multiple audio sessions, one close all sessions, and other gone.
+     */
+    @Test
+    public void testTwoUidOneCloseOneGo() {
+        Log.i(TAG, "running testTwoUidOneCloseOneGo");
+
+        int broadcasts = 1, bind = 1, unbind = 0;
+        //client1
+        openSessionWithResList(mDoubleList, bind, broadcasts, mTestPkg1, mTestSession1, mTestUid1);
+        broadcasts = broadcasts + 1;
+        openSessionWithResList(mDoubleList, bind, broadcasts, mTestPkg1, mTestSession2, mTestUid1);
+        // client2
+        broadcasts = broadcasts + 1;
+        openSessionWithResList(mDoubleList, bind, broadcasts, mTestPkg2, mTestSession1, mTestUid2);
+        broadcasts = broadcasts + 1;
+        openSessionWithResList(mDoubleList, bind, broadcasts, mTestPkg2, mTestSession2, mTestUid2);
+
+        broadcasts = broadcasts + 1;
+        closeSessionWithResList(mDoubleList, unbind, broadcasts, mTestPkg1, mTestSession1,
+                                mTestUid1);
+        // client2 gone
+        broadcasts = broadcasts + 2;
+        sendMessage(MusicFxHelper.MSG_EFFECT_CLIENT_GONE, mTestUid2, unbind, broadcasts);
+        // client 1 close all sessions
+        broadcasts = broadcasts + 1;
+        unbind = unbind + 1;
+        closeSessionWithResList(mDoubleList, unbind, broadcasts, mTestPkg1, mTestSession2,
+                                mTestUid1);
+    }
+
+    /**
+     * Three MusicFx clients, with multiple audio sessions, all UID gone.
+     */
+    @Test
+    public void testThreeUidAllGo() {
+        Log.i(TAG, "running testThreeUidAllGo");
+
+        int broadcasts = 1, bind = 1, unbind = 0;
+        //client1
+        openSessionWithResList(mDoubleList, bind, broadcasts, mTestPkg1, mTestSession1, mTestUid1);
+        broadcasts = broadcasts + 1;
+        openSessionWithResList(mDoubleList, bind, broadcasts, mTestPkg1, mTestSession2, mTestUid1);
+        // client2
+        broadcasts = broadcasts + 1;
+        openSessionWithResList(mDoubleList, bind, broadcasts, mTestPkg2, mTestSession2, mTestUid2);
+        broadcasts = broadcasts + 1;
+        openSessionWithResList(mDoubleList, bind, broadcasts, mTestPkg2, mTestSession3, mTestUid2);
+        // client3
+        broadcasts = broadcasts + 1;
+        openSessionWithResList(mDoubleList, bind, broadcasts, mTestPkg3, mTestSession3, mTestUid3);
+        broadcasts = broadcasts + 1;
+        openSessionWithResList(mDoubleList, bind, broadcasts, mTestPkg3, mTestSession1, mTestUid3);
+
+        // client2 gone
+        broadcasts = broadcasts + 2;
+        sendMessage(MusicFxHelper.MSG_EFFECT_CLIENT_GONE, mTestUid2, unbind, broadcasts);
+        // client3 gone
+        broadcasts = broadcasts + 2;
+        sendMessage(MusicFxHelper.MSG_EFFECT_CLIENT_GONE, mTestUid3, unbind, broadcasts);
+        // client 1 gone
+        broadcasts = broadcasts + 2;
+        // now expect unbindService to happen
+        unbind = unbind + 1;
+        sendMessage(MusicFxHelper.MSG_EFFECT_CLIENT_GONE, mTestUid1, unbind, broadcasts);
+    }
+
+    /**
+     * Three MusicFx clients, multiple audio sessions, open and UID gone in difference sequence.
+     */
+    @Test
+    public void testThreeUidDiffSequence() {
+        Log.i(TAG, "running testThreeUidDiffSequence");
+
+        int broadcasts = 1, bind = 1, unbind = 0;
+        //client1
+        openSessionWithResList(mDoubleList, bind, broadcasts, mTestPkg1, mTestSession1, mTestUid1);
+        broadcasts = broadcasts + 1;
+        openSessionWithResList(mDoubleList, bind, broadcasts, mTestPkg1, mTestSession2, mTestUid1);
+        // client2
+        broadcasts = broadcasts + 1;
+        openSessionWithResList(mDoubleList, bind, broadcasts, mTestPkg2, mTestSession2, mTestUid2);
+        // client1 close one session
+        broadcasts = broadcasts + 1;
+        closeSessionWithResList(mDoubleList, unbind, broadcasts, mTestPkg1, mTestSession1,
+                                mTestUid1);
+        // client2 open another session
+        broadcasts = broadcasts + 1;
+        openSessionWithResList(mDoubleList, bind, broadcasts, mTestPkg2, mTestSession3, mTestUid2);
+        // client3 open one session
+        broadcasts = broadcasts + 1;
+        openSessionWithResList(mDoubleList, bind, broadcasts, mTestPkg3, mTestSession3, mTestUid3);
+        // client2 gone
+        broadcasts = broadcasts + 2;
+        sendMessage(MusicFxHelper.MSG_EFFECT_CLIENT_GONE, mTestUid2, unbind, broadcasts);
+        // client3 open another session
+        broadcasts = broadcasts + 1;
+        openSessionWithResList(mDoubleList, bind, broadcasts, mTestPkg3, mTestSession1, mTestUid3);
+        // client1 close another session, and gone
+        broadcasts = broadcasts + 1;
+        closeSessionWithResList(mDoubleList, unbind, broadcasts, mTestPkg1, mTestSession2,
+                                mTestUid1);
+        // last UID client3 gone, unbind
+        broadcasts = broadcasts + 2;
+        unbind = unbind + 1;
+        sendMessage(MusicFxHelper.MSG_EFFECT_CLIENT_GONE, mTestUid3, unbind, broadcasts);
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/companion/virtual/GenericWindowPolicyControllerTest.java b/services/tests/servicestests/src/com/android/server/companion/virtual/GenericWindowPolicyControllerTest.java
index 3355910..18e6f0a 100644
--- a/services/tests/servicestests/src/com/android/server/companion/virtual/GenericWindowPolicyControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/companion/virtual/GenericWindowPolicyControllerTest.java
@@ -151,6 +151,19 @@
     }
 
     @Test
+    public void userNotAllowlisted_launchIsBlocked() {
+        GenericWindowPolicyController gwpc = createGwpcWithNoAllowedUsers();
+        gwpc.setDisplayId(DISPLAY_ID, /* isMirrorDisplay= */ false);
+
+        ActivityInfo activityInfo = getActivityInfo(
+                NONBLOCKED_APP_PACKAGE_NAME,
+                NONBLOCKED_APP_PACKAGE_NAME,
+                /* displayOnRemoteDevices */ true,
+                /* targetDisplayCategory */ null);
+        assertActivityIsBlocked(gwpc, activityInfo);
+    }
+
+    @Test
     public void openNonBlockedAppOnVirtualDisplay_isNotBlocked() {
         GenericWindowPolicyController gwpc = createGwpc();
         gwpc.setDisplayId(DISPLAY_ID, /* isMirrorDisplay= */ false);
@@ -702,6 +715,26 @@
                 /* customHomeComponent= */ null);
     }
 
+    private GenericWindowPolicyController createGwpcWithNoAllowedUsers() {
+        return new GenericWindowPolicyController(
+                0,
+                0,
+                /* allowedUsers= */ new ArraySet<>(),
+                /* activityLaunchAllowedByDefault= */ true,
+                /* activityPolicyExemptions= */ new ArraySet<>(),
+                /* crossTaskNavigationAllowedByDefault= */ true,
+                /* crossTaskNavigationExemptions= */ new ArraySet<>(),
+                /* permissionDialogComponent= */ null,
+                /* activityListener= */ mActivityListener,
+                /* pipBlockedCallback= */ mPipBlockedCallback,
+                /* activityBlockedCallback= */ mActivityBlockedCallback,
+                /* secureWindowCallback= */ mSecureWindowCallback,
+                /* intentListenerCallback= */ mIntentListenerCallback,
+                /* displayCategories= */ new ArraySet<>(),
+                /* showTasksInHostDeviceRecents= */ true,
+                /* customHomeComponent= */ null);
+    }
+
     private GenericWindowPolicyController createGwpcWithCustomHomeComponent(
             ComponentName homeComponent) {
         return new GenericWindowPolicyController(
diff --git a/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java
index 9213601a..995d1f4 100644
--- a/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java
@@ -444,18 +444,27 @@
     }
 
     @Test
+    public void isDeviceIdValid_invalidDeviceId_returnsFalse() {
+        assertThat(mVdm.isValidVirtualDeviceId(DEVICE_ID_INVALID)).isFalse();
+        assertThat(mLocalService.isValidVirtualDeviceId(DEVICE_ID_INVALID)).isFalse();
+    }
+
+    @Test
     public void isDeviceIdValid_defaultDeviceId_returnsFalse() {
         assertThat(mVdm.isValidVirtualDeviceId(DEVICE_ID_DEFAULT)).isFalse();
+        assertThat(mLocalService.isValidVirtualDeviceId(DEVICE_ID_DEFAULT)).isFalse();
     }
 
     @Test
     public void isDeviceIdValid_validVirtualDeviceId_returnsTrue() {
         assertThat(mVdm.isValidVirtualDeviceId(mDeviceImpl.getDeviceId())).isTrue();
+        assertThat(mLocalService.isValidVirtualDeviceId(mDeviceImpl.getDeviceId())).isTrue();
     }
 
     @Test
     public void isDeviceIdValid_nonExistentDeviceId_returnsFalse() {
         assertThat(mVdm.isValidVirtualDeviceId(mDeviceImpl.getDeviceId() + 1)).isFalse();
+        assertThat(mLocalService.isValidVirtualDeviceId(mDeviceImpl.getDeviceId() + 1)).isFalse();
     }
 
     @Test
diff --git a/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceUserPropertiesTest.java b/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceUserPropertiesTest.java
index d70a4fd..d7ed7c2 100644
--- a/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceUserPropertiesTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceUserPropertiesTest.java
@@ -69,8 +69,10 @@
                 .setMediaSharedWithParent(false)
                 .setCredentialShareableWithParent(true)
                 .setAuthAlwaysRequiredToDisableQuietMode(false)
+                .setAllowStoppingUserWithDelayedLocking(false)
                 .setDeleteAppWithParent(false)
                 .setAlwaysVisible(false)
+                .setCrossProfileContentSharingStrategy(0)
                 .build();
         final UserProperties actualProps = new UserProperties(defaultProps);
         actualProps.setShowInLauncher(14);
@@ -84,8 +86,10 @@
         actualProps.setMediaSharedWithParent(true);
         actualProps.setCredentialShareableWithParent(false);
         actualProps.setAuthAlwaysRequiredToDisableQuietMode(true);
+        actualProps.setAllowStoppingUserWithDelayedLocking(true);
         actualProps.setDeleteAppWithParent(true);
         actualProps.setAlwaysVisible(true);
+        actualProps.setCrossProfileContentSharingStrategy(1);
 
         // Write the properties to xml.
         final ByteArrayOutputStream baos = new ByteArrayOutputStream();
@@ -128,6 +132,7 @@
                 .setMediaSharedWithParent(true)
                 .setDeleteAppWithParent(true)
                 .setAuthAlwaysRequiredToDisableQuietMode(false)
+                .setAllowStoppingUserWithDelayedLocking(false)
                 .setAlwaysVisible(true)
                 .build();
         final UserProperties orig = new UserProperties(defaultProps);
@@ -137,6 +142,7 @@
         orig.setInheritDevicePolicy(9456);
         orig.setDeleteAppWithParent(false);
         orig.setAuthAlwaysRequiredToDisableQuietMode(true);
+        orig.setAllowStoppingUserWithDelayedLocking(true);
         orig.setAlwaysVisible(false);
 
         // Test every permission level. (Currently, it's linear so it's easy.)
@@ -182,6 +188,8 @@
         assertEqualGetterOrThrows(orig::getDeleteAppWithParent,
                 copy::getDeleteAppWithParent, exposeAll);
         assertEqualGetterOrThrows(orig::getAlwaysVisible, copy::getAlwaysVisible, exposeAll);
+        assertEqualGetterOrThrows(orig::getAllowStoppingUserWithDelayedLocking,
+                copy::getAllowStoppingUserWithDelayedLocking, exposeAll);
 
         // Items requiring hasManagePermission - put them here using hasManagePermission.
         assertEqualGetterOrThrows(orig::getShowInSettings, copy::getShowInSettings,
@@ -199,6 +207,8 @@
                 copy::isMediaSharedWithParent, true);
         assertEqualGetterOrThrows(orig::isCredentialShareableWithParent,
                 copy::isCredentialShareableWithParent, true);
+        assertEqualGetterOrThrows(orig::getCrossProfileContentSharingStrategy,
+                copy::getCrossProfileContentSharingStrategy, true);
     }
 
     /**
@@ -254,7 +264,11 @@
                 .isEqualTo(actual.isCredentialShareableWithParent());
         assertThat(expected.isAuthAlwaysRequiredToDisableQuietMode())
                 .isEqualTo(actual.isAuthAlwaysRequiredToDisableQuietMode());
+        assertThat(expected.getAllowStoppingUserWithDelayedLocking())
+                .isEqualTo(actual.getAllowStoppingUserWithDelayedLocking());
         assertThat(expected.getDeleteAppWithParent()).isEqualTo(actual.getDeleteAppWithParent());
         assertThat(expected.getAlwaysVisible()).isEqualTo(actual.getAlwaysVisible());
+        assertThat(expected.getCrossProfileContentSharingStrategy())
+                .isEqualTo(actual.getCrossProfileContentSharingStrategy());
     }
 }
diff --git a/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceUserTypeTest.java b/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceUserTypeTest.java
index 77f6939..7083706 100644
--- a/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceUserTypeTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceUserTypeTest.java
@@ -29,7 +29,6 @@
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertNotEquals;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertNotSame;
 import static org.junit.Assert.assertTrue;
@@ -91,12 +90,14 @@
                 .setMediaSharedWithParent(true)
                 .setCredentialShareableWithParent(false)
                 .setAuthAlwaysRequiredToDisableQuietMode(true)
+                .setAllowStoppingUserWithDelayedLocking(true)
                 .setShowInSettings(900)
                 .setShowInSharingSurfaces(20)
                 .setShowInQuietMode(30)
                 .setInheritDevicePolicy(340)
                 .setDeleteAppWithParent(true)
-                .setAlwaysVisible(true);
+                .setAlwaysVisible(true)
+                .setCrossProfileContentSharingStrategy(1);
 
         final UserTypeDetails type = new UserTypeDetails.Builder()
                 .setName("a.name")
@@ -167,6 +168,8 @@
         assertFalse(type.getDefaultUserPropertiesReference().isCredentialShareableWithParent());
         assertTrue(type.getDefaultUserPropertiesReference()
                 .isAuthAlwaysRequiredToDisableQuietMode());
+        assertTrue(type.getDefaultUserPropertiesReference()
+                .getAllowStoppingUserWithDelayedLocking());
         assertEquals(900, type.getDefaultUserPropertiesReference().getShowInSettings());
         assertEquals(20, type.getDefaultUserPropertiesReference().getShowInSharingSurfaces());
         assertEquals(30,
@@ -175,6 +178,8 @@
                 .getInheritDevicePolicy());
         assertTrue(type.getDefaultUserPropertiesReference().getDeleteAppWithParent());
         assertTrue(type.getDefaultUserPropertiesReference().getAlwaysVisible());
+        assertEquals(1, type.getDefaultUserPropertiesReference()
+                .getCrossProfileContentSharingStrategy());
 
         assertEquals(23, type.getBadgeLabel(0));
         assertEquals(24, type.getBadgeLabel(1));
@@ -231,6 +236,8 @@
         assertEquals(UserProperties.SHOW_IN_LAUNCHER_SEPARATE, props.getShowInSharingSurfaces());
         assertEquals(UserProperties.SHOW_IN_QUIET_MODE_PAUSED,
                 props.getShowInQuietMode());
+        assertEquals(UserProperties.CROSS_PROFILE_CONTENT_SHARING_NO_DELEGATION,
+                props.getCrossProfileContentSharingStrategy());
 
         assertFalse(type.hasBadge());
     }
@@ -318,12 +325,14 @@
                 .setMediaSharedWithParent(false)
                 .setCredentialShareableWithParent(true)
                 .setAuthAlwaysRequiredToDisableQuietMode(false)
+                .setAllowStoppingUserWithDelayedLocking(false)
                 .setShowInSettings(20)
                 .setInheritDevicePolicy(21)
                 .setShowInSharingSurfaces(22)
                 .setShowInQuietMode(24)
                 .setDeleteAppWithParent(true)
-                .setAlwaysVisible(false);
+                .setAlwaysVisible(false)
+                .setCrossProfileContentSharingStrategy(1);
 
         final ArrayMap<String, UserTypeDetails.Builder> builders = new ArrayMap<>();
         builders.put(userTypeAosp1, new UserTypeDetails.Builder()
@@ -362,6 +371,8 @@
                 .isCredentialShareableWithParent());
         assertFalse(aospType.getDefaultUserPropertiesReference()
                 .isAuthAlwaysRequiredToDisableQuietMode());
+        assertFalse(aospType.getDefaultUserPropertiesReference()
+                .getAllowStoppingUserWithDelayedLocking());
         assertEquals(20, aospType.getDefaultUserPropertiesReference().getShowInSettings());
         assertEquals(21, aospType.getDefaultUserPropertiesReference()
                 .getInheritDevicePolicy());
@@ -370,6 +381,8 @@
                 aospType.getDefaultUserPropertiesReference().getShowInQuietMode());
         assertTrue(aospType.getDefaultUserPropertiesReference().getDeleteAppWithParent());
         assertFalse(aospType.getDefaultUserPropertiesReference().getAlwaysVisible());
+        assertEquals(1, aospType.getDefaultUserPropertiesReference()
+                .getCrossProfileContentSharingStrategy());
 
         // userTypeAosp2 should be modified.
         aospType = builders.get(userTypeAosp2).createUserTypeDetails();
@@ -413,6 +426,8 @@
                 .isCredentialShareableWithParent());
         assertTrue(aospType.getDefaultUserPropertiesReference()
                 .isAuthAlwaysRequiredToDisableQuietMode());
+        assertTrue(aospType.getDefaultUserPropertiesReference()
+                .getAllowStoppingUserWithDelayedLocking());
         assertEquals(23, aospType.getDefaultUserPropertiesReference().getShowInSettings());
         assertEquals(22,
                 aospType.getDefaultUserPropertiesReference().getShowInSharingSurfaces());
@@ -422,6 +437,8 @@
                 .getInheritDevicePolicy());
         assertFalse(aospType.getDefaultUserPropertiesReference().getDeleteAppWithParent());
         assertTrue(aospType.getDefaultUserPropertiesReference().getAlwaysVisible());
+        assertEquals(0, aospType.getDefaultUserPropertiesReference()
+                .getCrossProfileContentSharingStrategy());
 
         // userTypeOem1 should be created.
         UserTypeDetails.Builder customType = builders.get(userTypeOem1);
diff --git a/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java b/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java
index 8933c6c..a743fff 100644
--- a/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java
@@ -23,7 +23,6 @@
 import static org.junit.Assume.assumeTrue;
 import static org.testng.Assert.assertEquals;
 import static org.testng.Assert.assertThrows;
-import static org.testng.Assert.assertTrue;
 
 import android.annotation.UserIdInt;
 import android.app.ActivityManager;
@@ -219,6 +218,8 @@
                 .isEqualTo(cloneUserProperties.isMediaSharedWithParent());
         assertThat(typeProps.isCredentialShareableWithParent())
                 .isEqualTo(cloneUserProperties.isCredentialShareableWithParent());
+        assertThat(typeProps.getCrossProfileContentSharingStrategy())
+                .isEqualTo(cloneUserProperties.getCrossProfileContentSharingStrategy());
         assertThrows(SecurityException.class, cloneUserProperties::getDeleteAppWithParent);
         assertThrows(SecurityException.class, cloneUserProperties::getAlwaysVisible);
         compareDrawables(mUserManager.getUserBadge(),
@@ -338,9 +339,15 @@
         assertThat(typeProps.isAuthAlwaysRequiredToDisableQuietMode())
                 .isEqualTo(privateProfileUserProperties
                         .isAuthAlwaysRequiredToDisableQuietMode());
+        assertThat(typeProps.getCrossProfileContentSharingStrategy())
+                .isEqualTo(privateProfileUserProperties.getCrossProfileContentSharingStrategy());
         assertThrows(SecurityException.class, privateProfileUserProperties::getDeleteAppWithParent);
+        assertThrows(SecurityException.class,
+                privateProfileUserProperties::getAllowStoppingUserWithDelayedLocking);
+
         compareDrawables(mUserManager.getUserBadge(),
                 Resources.getSystem().getDrawable(userTypeDetails.getBadgePlain()));
+
         // Verify private profile parent
         assertThat(mUserManager.getProfileParent(mainUserId)).isNull();
         UserInfo parentProfileInfo = mUserManager.getProfileParent(userInfo.id);
diff --git a/services/tests/servicestests/src/com/android/server/uri/UriGrantsManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/uri/UriGrantsManagerServiceTest.java
index e418d2f..769ec5f 100644
--- a/services/tests/servicestests/src/com/android/server/uri/UriGrantsManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/uri/UriGrantsManagerServiceTest.java
@@ -57,17 +57,22 @@
 import android.net.Uri;
 import android.os.Process;
 import android.os.UserHandle;
+import android.platform.test.ravenwood.RavenwoodRule;
 import android.util.ArraySet;
 
 import androidx.test.InstrumentationRegistry;
 
 import org.junit.Before;
+import org.junit.Rule;
 import org.junit.Test;
 
 import java.util.Arrays;
 import java.util.Set;
 
 public class UriGrantsManagerServiceTest {
+    @Rule
+    public final RavenwoodRule mRavenwood = new RavenwoodRule();
+
     private UriGrantsMockContext mContext;
     private UriGrantsManagerInternal mService;
 
@@ -79,7 +84,7 @@
 
     @Before
     public void setUp() throws Exception {
-        mContext = new UriGrantsMockContext(InstrumentationRegistry.getContext());
+        mContext = new UriGrantsMockContext();
         mService = UriGrantsManagerService.createForTest(mContext.getFilesDir()).getLocalService();
     }
 
diff --git a/services/tests/servicestests/src/com/android/server/uri/UriGrantsMockContext.java b/services/tests/servicestests/src/com/android/server/uri/UriGrantsMockContext.java
index 7eb6c97..4c11de09 100644
--- a/services/tests/servicestests/src/com/android/server/uri/UriGrantsMockContext.java
+++ b/services/tests/servicestests/src/com/android/server/uri/UriGrantsMockContext.java
@@ -21,11 +21,7 @@
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.when;
 
-import android.annotation.NonNull;
 import android.app.ActivityManagerInternal;
-import android.content.ContentResolver;
-import android.content.Context;
-import android.content.ContextWrapper;
 import android.content.Intent;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageManager;
@@ -33,18 +29,19 @@
 import android.content.pm.PathPermission;
 import android.content.pm.ProviderInfo;
 import android.net.Uri;
-import android.os.FileUtils;
 import android.os.PatternMatcher;
 import android.os.Process;
 import android.os.UserHandle;
-import android.test.mock.MockContentResolver;
+import android.test.mock.MockContext;
 import android.test.mock.MockPackageManager;
 
 import com.android.server.LocalServices;
 
 import java.io.File;
+import java.io.IOException;
+import java.nio.file.Files;
 
-public class UriGrantsMockContext extends ContextWrapper {
+public class UriGrantsMockContext extends MockContext {
     static final String TAG = "UriGrants";
 
     static final int FLAG_READ = Intent.FLAG_GRANT_READ_URI_PERMISSION;
@@ -98,19 +95,14 @@
     private final File mDir;
 
     private final MockPackageManager mPackage;
-    private final MockContentResolver mResolver;
 
     final ActivityManagerInternal mAmInternal;
     final PackageManagerInternal mPmInternal;
 
-    public UriGrantsMockContext(@NonNull Context base) {
-        super(base);
-        mDir = new File(base.getFilesDir(), TAG);
-        mDir.mkdirs();
-        FileUtils.deleteContents(mDir);
+    public UriGrantsMockContext() throws IOException {
+        mDir = Files.createTempDirectory(TAG).toFile();
 
         mPackage = new MockPackageManager();
-        mResolver = new MockContentResolver(this);
 
         mAmInternal = mock(ActivityManagerInternal.class);
         LocalServices.removeServiceForTest(ActivityManagerInternal.class);
@@ -239,11 +231,6 @@
     }
 
     @Override
-    public ContentResolver getContentResolver() {
-        return mResolver;
-    }
-
-    @Override
     public File getFilesDir() {
         return mDir;
     }
diff --git a/services/tests/servicestests/src/com/android/server/uri/UriPermissionTest.java b/services/tests/servicestests/src/com/android/server/uri/UriPermissionTest.java
index 07005a9..4d4f5ed 100644
--- a/services/tests/servicestests/src/com/android/server/uri/UriPermissionTest.java
+++ b/services/tests/servicestests/src/com/android/server/uri/UriPermissionTest.java
@@ -35,12 +35,18 @@
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
 
+import android.platform.test.ravenwood.RavenwoodRule;
+
 import org.junit.Before;
+import org.junit.Rule;
 import org.junit.Test;
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 
 public class UriPermissionTest {
+    @Rule
+    public final RavenwoodRule mRavenwood = new RavenwoodRule();
+
     @Mock
     private UriGrantsManagerInternal mService;
 
diff --git a/services/tests/servicestests/src/com/android/server/utils/AnrTimerTest.java b/services/tests/servicestests/src/com/android/server/utils/AnrTimerTest.java
index 330dbb8..861d14a 100644
--- a/services/tests/servicestests/src/com/android/server/utils/AnrTimerTest.java
+++ b/services/tests/servicestests/src/com/android/server/utils/AnrTimerTest.java
@@ -30,13 +30,19 @@
 import com.android.internal.annotations.GuardedBy;
 
 import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
 
 import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.TimeUnit;
 
 @SmallTest
 @Presubmit
+@RunWith(Parameterized.class)
 public class AnrTimerTest {
 
     // The commonly used message timeout key.
@@ -106,6 +112,16 @@
     }
 
     /**
+     * Force AnrTimer to use the test parameter for the feature flag.
+     */
+    class TestInjector extends AnrTimer.Injector {
+        @Override
+        boolean anrTimerServiceEnabled() {
+            return mEnabled;
+        }
+    }
+
+    /**
      * An instrumented AnrTimer.
      */
     private static class TestAnrTimer extends AnrTimer<TestArg> {
@@ -137,6 +153,17 @@
         assertEquals(actual.what, MSG_TIMEOUT);
     }
 
+    @Parameters(name = "featureEnabled={0}")
+    public static Collection<Object[]> data() {
+        return Arrays.asList(new Object[][] { {false}, {true} });
+    }
+
+    /** True if the feature is enabled. */
+    private boolean mEnabled;
+
+    public AnrTimerTest(boolean featureEnabled) {
+        mEnabled = featureEnabled;
+    }
 
     /**
      * Verify that a simple expiration succeeds.  The timer is started for 10ms.  The test
diff --git a/services/tests/servicestests/src/com/android/server/utils/OWNERS b/services/tests/servicestests/src/com/android/server/utils/OWNERS
index 5e24828..f5b19a1 100644
--- a/services/tests/servicestests/src/com/android/server/utils/OWNERS
+++ b/services/tests/servicestests/src/com/android/server/utils/OWNERS
@@ -1,2 +1,5 @@
 per-file EventLoggerTest.java = file:/platform/frameworks/av:/media/janitors/media_solutions_OWNERS
 per-file EventLoggerTest.java = jmtrivi@google.com
+
+# Bug component : 158088 = per-file AnrTimer*.java
+per-file AnrTimer*.java = file:/PERFORMANCE_OWNERS
diff --git a/services/tests/vibrator/src/com/android/server/vibrator/VibratorControlServiceTest.java b/services/tests/vibrator/src/com/android/server/vibrator/VibratorControlServiceTest.java
deleted file mode 100644
index 49efd1b..0000000
--- a/services/tests/vibrator/src/com/android/server/vibrator/VibratorControlServiceTest.java
+++ /dev/null
@@ -1,63 +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.vibrator;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import android.os.RemoteException;
-
-import org.junit.Before;
-import org.junit.Test;
-
-public class VibratorControlServiceTest {
-
-    private VibratorControlService mVibratorControlService;
-    private final Object mLock = new Object();
-
-    @Before
-    public void setUp() throws Exception {
-        mVibratorControlService = new VibratorControlService(new VibratorControllerHolder(), mLock);
-    }
-
-    @Test
-    public void testRegisterVibratorController() throws RemoteException {
-        FakeVibratorController fakeController = new FakeVibratorController();
-        mVibratorControlService.registerVibratorController(fakeController);
-
-        assertThat(fakeController.isLinkedToDeath).isTrue();
-    }
-
-    @Test
-    public void testUnregisterVibratorController_providingTheRegisteredController_performsRequest()
-            throws RemoteException {
-        FakeVibratorController fakeController = new FakeVibratorController();
-        mVibratorControlService.registerVibratorController(fakeController);
-        mVibratorControlService.unregisterVibratorController(fakeController);
-        assertThat(fakeController.isLinkedToDeath).isFalse();
-    }
-
-    @Test
-    public void testUnregisterVibratorController_providingAnInvalidController_ignoresRequest()
-            throws RemoteException {
-        FakeVibratorController fakeController1 = new FakeVibratorController();
-        FakeVibratorController fakeController2 = new FakeVibratorController();
-        mVibratorControlService.registerVibratorController(fakeController1);
-
-        mVibratorControlService.unregisterVibratorController(fakeController2);
-        assertThat(fakeController1.isLinkedToDeath).isTrue();
-    }
-}
diff --git a/services/tests/vibrator/src/com/android/server/vibrator/VibratorControllerHolderTest.java b/services/tests/vibrator/src/com/android/server/vibrator/VibratorControllerHolderTest.java
deleted file mode 100644
index 79abe21..0000000
--- a/services/tests/vibrator/src/com/android/server/vibrator/VibratorControllerHolderTest.java
+++ /dev/null
@@ -1,72 +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.vibrator;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import android.os.RemoteException;
-
-import org.junit.Before;
-import org.junit.Test;
-
-public class VibratorControllerHolderTest {
-
-    private final FakeVibratorController mFakeVibratorController = new FakeVibratorController();
-    private VibratorControllerHolder mVibratorControllerHolder;
-
-    @Before
-    public void setUp() throws Exception {
-        mVibratorControllerHolder = new VibratorControllerHolder();
-    }
-
-    @Test
-    public void testSetVibratorController_linksVibratorControllerToDeath() throws RemoteException {
-        mVibratorControllerHolder.setVibratorController(mFakeVibratorController);
-        assertThat(mVibratorControllerHolder.getVibratorController())
-                .isEqualTo(mFakeVibratorController);
-        assertThat(mFakeVibratorController.isLinkedToDeath).isTrue();
-    }
-
-    @Test
-    public void testSetVibratorController_setControllerToNull_unlinksVibratorControllerToDeath()
-            throws RemoteException {
-        mVibratorControllerHolder.setVibratorController(mFakeVibratorController);
-        mVibratorControllerHolder.setVibratorController(null);
-        assertThat(mFakeVibratorController.isLinkedToDeath).isFalse();
-        assertThat(mVibratorControllerHolder.getVibratorController()).isNull();
-    }
-
-    @Test
-    public void testBinderDied_withValidController_unlinksVibratorControllerToDeath()
-            throws RemoteException {
-        mVibratorControllerHolder.setVibratorController(mFakeVibratorController);
-        mVibratorControllerHolder.binderDied(mFakeVibratorController);
-        assertThat(mFakeVibratorController.isLinkedToDeath).isFalse();
-        assertThat(mVibratorControllerHolder.getVibratorController()).isNull();
-    }
-
-    @Test
-    public void testBinderDied_withInvalidController_ignoresRequest()
-            throws RemoteException {
-        mVibratorControllerHolder.setVibratorController(mFakeVibratorController);
-        FakeVibratorController imposterVibratorController = new FakeVibratorController();
-        mVibratorControllerHolder.binderDied(imposterVibratorController);
-        assertThat(mFakeVibratorController.isLinkedToDeath).isTrue();
-        assertThat(mVibratorControllerHolder.getVibratorController())
-                .isEqualTo(mFakeVibratorController);
-    }
-}
diff --git a/services/tests/vibrator/src/com/android/server/vibrator/VibratorManagerServiceTest.java b/services/tests/vibrator/src/com/android/server/vibrator/VibratorManagerServiceTest.java
index a105649..4e9bbe0 100644
--- a/services/tests/vibrator/src/com/android/server/vibrator/VibratorManagerServiceTest.java
+++ b/services/tests/vibrator/src/com/android/server/vibrator/VibratorManagerServiceTest.java
@@ -307,10 +307,9 @@
 
                     @Override
                     void addService(String name, IBinder service) {
-                        if (service instanceof VibratorManagerService.ExternalVibratorService) {
-                            mExternalVibratorService =
-                                    (VibratorManagerService.ExternalVibratorService) service;
-                        }
+                        Object serviceInstance = service;
+                        mExternalVibratorService =
+                                (VibratorManagerService.ExternalVibratorService) serviceInstance;
                     }
 
                     HapticFeedbackVibrationProvider createHapticFeedbackVibrationProvider(
@@ -1399,6 +1398,17 @@
     }
 
     @Test
+    public void performHapticFeedback_usesServiceAsToken() throws Exception {
+        VibratorManagerService service = createSystemReadyService();
+
+        HalVibration vibration =
+                performHapticFeedbackAndWaitUntilFinished(
+                        service, HapticFeedbackConstants.SCROLL_TICK, /* always= */ true);
+
+        assertTrue(vibration.callerToken == service);
+    }
+
+    @Test
     public void vibrate_withIntensitySettings_appliesSettingsToScaleVibrations() throws Exception {
         int defaultNotificationIntensity =
                 mVibrator.getDefaultVibrationIntensity(VibrationAttributes.USAGE_NOTIFICATION);
diff --git a/services/tests/vibrator/utils/com/android/server/vibrator/FakeVibratorController.java b/services/tests/vibrator/utils/com/android/server/vibrator/FakeVibratorController.java
deleted file mode 100644
index 7e23587..0000000
--- a/services/tests/vibrator/utils/com/android/server/vibrator/FakeVibratorController.java
+++ /dev/null
@@ -1,58 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.vibrator;
-
-import android.annotation.NonNull;
-import android.frameworks.vibrator.IVibratorController;
-import android.os.IBinder;
-import android.os.RemoteException;
-
-/**
- * Provides a fake implementation of {@link android.frameworks.vibrator.IVibratorController} for
- * testing.
- */
-public final class FakeVibratorController extends IVibratorController.Stub {
-
-    public boolean isLinkedToDeath = false;
-
-    @Override
-    public void requestVibrationParams(int i, long l, IBinder iBinder) throws RemoteException {
-
-    }
-
-    @Override
-    public int getInterfaceVersion() throws RemoteException {
-        return 0;
-    }
-
-    @Override
-    public String getInterfaceHash() throws RemoteException {
-        return null;
-    }
-
-    @Override
-    public void linkToDeath(@NonNull DeathRecipient recipient, int flags) {
-        super.linkToDeath(recipient, flags);
-        isLinkedToDeath = true;
-    }
-
-    @Override
-    public boolean unlinkToDeath(@NonNull DeathRecipient recipient, int flags) {
-        isLinkedToDeath = false;
-        return super.unlinkToDeath(recipient, flags);
-    }
-}
diff --git a/services/tests/wmtests/AndroidManifest.xml b/services/tests/wmtests/AndroidManifest.xml
index c3074bb..ef19791 100644
--- a/services/tests/wmtests/AndroidManifest.xml
+++ b/services/tests/wmtests/AndroidManifest.xml
@@ -99,11 +99,6 @@
             android:theme="@style/WhiteBackgroundTheme"
             android:exported="true"/>
 
-        <activity android:name="com.android.server.wm.TrustedPresentationCallbackTest$TestActivity"
-            android:exported="true"
-            android:showWhenLocked="true"
-            android:turnScreenOn="true" />
-
         <activity android:name="android.app.Activity"
             android:exported="true"
             android:showWhenLocked="true"
diff --git a/services/tests/wmtests/src/com/android/server/wm/DeferredDisplayUpdaterDiffTest.java b/services/tests/wmtests/src/com/android/server/wm/DeferredDisplayUpdaterDiffTest.java
new file mode 100644
index 0000000..44b69f1
--- /dev/null
+++ b/services/tests/wmtests/src/com/android/server/wm/DeferredDisplayUpdaterDiffTest.java
@@ -0,0 +1,267 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.hardware.display.DeviceProductInfo.CONNECTION_TO_SINK_UNKNOWN;
+import static android.view.RoundedCorner.POSITION_TOP_LEFT;
+import static android.view.RoundedCorners.NO_ROUNDED_CORNERS;
+
+import static com.android.server.wm.DeferredDisplayUpdater.DEFERRABLE_FIELDS;
+import static com.android.server.wm.DeferredDisplayUpdater.DIFF_NOT_WM_DEFERRABLE;
+import static com.android.server.wm.DeferredDisplayUpdater.DIFF_WM_DEFERRABLE;
+import static com.android.server.wm.DeferredDisplayUpdater.calculateDisplayInfoDiff;
+import static com.android.server.wm.utils.DisplayInfoOverrides.copyDisplayInfoFields;
+
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import android.annotation.NonNull;
+import android.graphics.Insets;
+import android.graphics.Rect;
+import android.hardware.display.DeviceProductInfo;
+import android.platform.test.annotations.Presubmit;
+import android.util.SparseArray;
+import android.view.Display;
+import android.view.DisplayAddress;
+import android.view.DisplayCutout;
+import android.view.DisplayInfo;
+import android.view.DisplayShape;
+import android.view.RoundedCorner;
+import android.view.RoundedCorners;
+import android.view.SurfaceControl.RefreshRateRange;
+
+import androidx.test.filters.SmallTest;
+
+import org.junit.Test;
+
+import java.lang.reflect.Field;
+import java.lang.reflect.Modifier;
+import java.lang.reflect.ParameterizedType;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.Set;
+import java.util.function.Consumer;
+
+/**
+ * Build/Install/Run:
+ * atest WmTests:DeferredDisplayUpdaterDiffTest
+ */
+@SmallTest
+@Presubmit
+public class DeferredDisplayUpdaterDiffTest {
+
+    private static final Set<String> IGNORED_FIELDS = new HashSet<>(Arrays.asList(
+            "name" // human-readable name is ignored in equals() checks
+    ));
+
+    private static final DisplayInfo EMPTY = new DisplayInfo();
+
+    @Test
+    public void testCalculateDisplayInfoDiff_allDifferent_returnsChanges() {
+        final DisplayInfo first = new DisplayInfo();
+        final DisplayInfo second = new DisplayInfo();
+        makeAllFieldsDifferent(first, second);
+
+        int diff = calculateDisplayInfoDiff(first, second);
+
+        assertWithMessage("Expected to receive a non-zero difference when "
+                + "there are changes in all fields of DisplayInfo\n"
+                + "Make sure that you have updated calculateDisplayInfoDiff function after "
+                + "changing DisplayInfo fields").that(diff).isGreaterThan(0);
+    }
+
+    @Test
+    public void testCalculateDisplayInfoDiff_forEveryDifferentField_returnsChanges() {
+        generateWithSingleDifferentField((first, second, field) -> {
+            int diff = calculateDisplayInfoDiff(first, second);
+
+            assertWithMessage("Expected to receive a non-zero difference when "
+                    + "there are changes in " + field + "\n"
+                    + "Make sure that you have updated calculateDisplayInfoDiff function after "
+                    + "changing DisplayInfo fields").that(diff).isGreaterThan(0);
+        });
+    }
+
+    @Test
+    public void testCalculateDisplayInfoDiff_forEveryDifferentField_returnsMatchingChange() {
+        generateWithSingleDifferentField((first, second, field) -> {
+            boolean hasDeferrableFieldChange = hasDeferrableFieldChange(first, second);
+            int expectedDiff =
+                    hasDeferrableFieldChange ? DIFF_WM_DEFERRABLE : DIFF_NOT_WM_DEFERRABLE;
+
+            int diff = calculateDisplayInfoDiff(first, second);
+
+            assertWithMessage("Expected to have diff = " + expectedDiff
+                    + ", for field = " + field + "\n"
+                    + "Make sure that you have updated calculateDisplayInfoDiff function after "
+                    + "changing DisplayInfo fields").that(
+                    diff).isEqualTo(expectedDiff);
+        });
+    }
+
+    /**
+     * Sets each field of the objects to different values using reflection
+     */
+    private static void makeAllFieldsDifferent(@NonNull DisplayInfo first,
+            @NonNull DisplayInfo second) {
+        forEachDisplayInfoField(field -> {
+            try {
+                setDifferentFieldValues(first, second, field);
+            } catch (IllegalAccessException e) {
+                throw new RuntimeException(e);
+            }
+        });
+    }
+
+    private static boolean hasDeferrableFieldChange(@NonNull DisplayInfo first,
+            @NonNull DisplayInfo second) {
+        final DisplayInfo firstDeferrableFieldsOnly = new DisplayInfo();
+        final DisplayInfo secondDeferrableFieldsOnly = new DisplayInfo();
+
+        copyDisplayInfoFields(/* out= */ firstDeferrableFieldsOnly, /* base= */
+                EMPTY, /* override= */ first, DEFERRABLE_FIELDS);
+        copyDisplayInfoFields(/* out= */ secondDeferrableFieldsOnly, /* base= */
+                EMPTY, /* override= */ second, DEFERRABLE_FIELDS);
+
+        return !firstDeferrableFieldsOnly.equals(secondDeferrableFieldsOnly);
+    }
+
+    /**
+     * Creates pairs of DisplayInfos where only one field is different, the callback is called for
+     * each field
+     */
+    private static void generateWithSingleDifferentField(DisplayInfoConsumer consumer) {
+        forEachDisplayInfoField(field -> {
+            final DisplayInfo first = new DisplayInfo();
+            final DisplayInfo second = new DisplayInfo();
+
+            try {
+                setDifferentFieldValues(first, second, field);
+            } catch (IllegalAccessException e) {
+                throw new RuntimeException(e);
+            }
+
+            consumer.consume(first, second, field);
+        });
+    }
+
+    private static void setDifferentFieldValues(@NonNull DisplayInfo first,
+            @NonNull DisplayInfo second,
+            @NonNull Field field) throws IllegalAccessException {
+        final Class<?> type = field.getType();
+        if (type.equals(int.class)) {
+            field.setInt(first, 1);
+            field.setInt(second, 2);
+        } else if (type.equals(double.class)) {
+            field.setDouble(first, 1.0);
+            field.setDouble(second, 2.0);
+        } else if (type.equals(short.class)) {
+            field.setShort(first, (short) 1);
+            field.setShort(second, (short) 2);
+        } else if (type.equals(long.class)) {
+            field.setLong(first, 1L);
+            field.setLong(second, 2L);
+        } else if (type.equals(char.class)) {
+            field.setChar(first, 'a');
+            field.setChar(second, 'b');
+        } else if (type.equals(byte.class)) {
+            field.setByte(first, (byte) 1);
+            field.setByte(second, (byte) 2);
+        } else if (type.equals(float.class)) {
+            field.setFloat(first, 1.0f);
+            field.setFloat(second, 2.0f);
+        } else if (type == boolean.class) {
+            field.setBoolean(first, true);
+            field.setBoolean(second, false);
+        } else if (type.equals(String.class)) {
+            field.set(first, "one");
+            field.set(second, "two");
+        } else if (type.equals(DisplayAddress.class)) {
+            field.set(first, DisplayAddress.fromPhysicalDisplayId(0));
+            field.set(second, DisplayAddress.fromPhysicalDisplayId(1));
+        } else if (type.equals(DeviceProductInfo.class)) {
+            field.set(first, new DeviceProductInfo("name", "pnp_id", "product_id1", 2023,
+                    CONNECTION_TO_SINK_UNKNOWN));
+            field.set(second, new DeviceProductInfo("name", "pnp_id", "product_id2", 2023,
+                    CONNECTION_TO_SINK_UNKNOWN));
+        } else if (type.equals(DisplayCutout.class)) {
+            field.set(first,
+                    new DisplayCutout(Insets.NONE, new Rect(0, 0, 100, 100), null, null,
+                            null));
+            field.set(second,
+                    new DisplayCutout(Insets.NONE, new Rect(0, 0, 200, 200), null, null,
+                            null));
+        } else if (type.equals(RoundedCorners.class)) {
+            field.set(first, NO_ROUNDED_CORNERS);
+
+            final RoundedCorners other = new RoundedCorners(NO_ROUNDED_CORNERS);
+            other.setRoundedCorner(POSITION_TOP_LEFT,
+                    new RoundedCorner(POSITION_TOP_LEFT, 1, 2, 3));
+            field.set(second, other);
+        } else if (type.equals(DisplayShape.class)) {
+            field.set(first, DisplayShape.createDefaultDisplayShape(100, 200, false));
+            field.set(second, DisplayShape.createDefaultDisplayShape(50, 100, false));
+        } else if (type.equals(RefreshRateRange.class)) {
+            field.set(first, new RefreshRateRange(0, 100));
+            field.set(second, new RefreshRateRange(20, 80));
+        } else if (type.equals(Display.HdrCapabilities.class)) {
+            field.set(first, new Display.HdrCapabilities(new int[]{0}, 100, 50, 25));
+            field.set(second, new Display.HdrCapabilities(new int[]{1}, 100, 50, 25));
+        } else if (type.equals(SparseArray.class)
+                && ((ParameterizedType) field.getGenericType()).getActualTypeArguments()[0].equals(
+                RefreshRateRange.class)) {
+            final SparseArray<RefreshRateRange> array1 = new SparseArray<>();
+            array1.set(0, new RefreshRateRange(0, 100));
+            final SparseArray<RefreshRateRange> array2 = new SparseArray<>();
+            array2.set(0, new RefreshRateRange(20, 80));
+            field.set(first, array1);
+            field.set(second, array2);
+        } else if (type.isArray() && type.getComponentType().equals(int.class)) {
+            field.set(first, new int[]{0});
+            field.set(second, new int[]{1});
+        } else if (type.isArray() && type.getComponentType().equals(Display.Mode.class)) {
+            field.set(first, new Display.Mode[]{new Display.Mode(100, 200, 300)});
+            field.set(second, new Display.Mode[]{new Display.Mode(10, 20, 30)});
+        } else {
+            throw new IllegalArgumentException("Field " + field
+                    + " is not supported by this test, please add implementation of setting "
+                    + "different values for this field");
+        }
+    }
+
+    private interface DisplayInfoConsumer {
+        void consume(DisplayInfo first, DisplayInfo second, Field field);
+    }
+
+    /**
+     * Iterates over every non-static field of DisplayInfo class except IGNORED_FIELDS
+     */
+    private static void forEachDisplayInfoField(Consumer<Field> consumer) {
+        for (Field field : DisplayInfo.class.getDeclaredFields()) {
+            field.setAccessible(true);
+
+            if (Modifier.isStatic(field.getModifiers())) {
+                continue;
+            }
+
+            if (IGNORED_FIELDS.contains(field.getName())) {
+                continue;
+            }
+
+            consumer.accept(field);
+        }
+    }
+}
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayContentDeferredUpdateTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayContentDeferredUpdateTests.java
new file mode 100644
index 0000000..dfa595c
--- /dev/null
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayContentDeferredUpdateTests.java
@@ -0,0 +1,237 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm;
+
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.any;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.mock;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.never;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.Mockito.atLeast;
+import static org.mockito.Mockito.clearInvocations;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.when;
+
+import android.platform.test.annotations.Presubmit;
+import android.view.DisplayInfo;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.server.wm.TransitionController.OnStartCollect;
+import com.android.window.flags.Flags;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+
+/**
+ * Tests for the {@link DisplayContent} class when FLAG_DEFER_DISPLAY_UPDATES is enabled.
+ *
+ * Build/Install/Run:
+ * atest WmTests:DisplayContentDeferredUpdateTests
+ */
+@SmallTest
+@Presubmit
+@RunWith(WindowTestRunner.class)
+public class DisplayContentDeferredUpdateTests extends WindowTestsBase {
+
+    @Override
+    protected void onBeforeSystemServicesCreated() {
+        // Set other flags to their default values
+        mSetFlagsRule.initAllFlagsToReleaseConfigDefault();
+
+        mSetFlagsRule.enableFlags(Flags.FLAG_DEFER_DISPLAY_UPDATES);
+    }
+
+    @Before
+    public void before() {
+        mockTransitionsController(/* enabled= */ true);
+        mockRemoteDisplayChangeController();
+    }
+
+    @Test
+    public void testUpdate_deferrableFieldChangedTransitionStarted_deferrableFieldUpdated() {
+        performInitialDisplayUpdate();
+
+        givenDisplayInfo(/* uniqueId= */ "old");
+        Runnable onUpdated = mock(Runnable.class);
+        mDisplayContent.requestDisplayUpdate(onUpdated);
+
+        // Emulate that collection has started
+        captureStartTransitionCollection().getValue().onCollectStarted(/* deferred= */ true);
+        verify(onUpdated).run();
+        clearInvocations(mDisplayContent.mTransitionController, onUpdated);
+
+        givenDisplayInfo(/* uniqueId= */ "new");
+        mDisplayContent.requestDisplayUpdate(onUpdated);
+        captureStartTransitionCollection().getValue().onCollectStarted(/* deferred= */ true);
+        verify(onUpdated).run();
+        assertThat(mDisplayContent.getDisplayInfo().uniqueId).isEqualTo("new");
+    }
+
+    @Test
+    public void testUpdate_nonDeferrableUpdateAndTransitionDeferred_nonDeferrableFieldUpdated() {
+        performInitialDisplayUpdate();
+
+        // Update only color mode (non-deferrable field) and keep the same unique id
+        givenDisplayInfo(/* uniqueId= */ "initial_unique_id", /* colorMode= */ 123);
+        Runnable onUpdated = mock(Runnable.class);
+        mDisplayContent.requestDisplayUpdate(onUpdated);
+
+        verify(onUpdated).run();
+        assertThat(mDisplayContent.getDisplayInfo().colorMode).isEqualTo(123);
+    }
+
+    @Test
+    public void testUpdate_nonDeferrableUpdateTwiceAndTransitionDeferred_fieldHasLatestValue() {
+        performInitialDisplayUpdate();
+
+        // Update only color mode (non-deferrable field) and keep the same unique id
+        givenDisplayInfo(/* uniqueId= */ "initial_unique_id", /* colorMode= */ 123);
+        mDisplayContent.requestDisplayUpdate(mock(Runnable.class));
+
+        assertThat(mDisplayContent.getDisplayInfo().colorMode).isEqualTo(123);
+        assertThat(mDisplayContent.getDisplayInfo().uniqueId)
+                .isEqualTo("initial_unique_id");
+
+        // Update unique id (deferrable field), keep the same color mode,
+        // this update should be deferred
+        givenDisplayInfo(/* uniqueId= */ "new_unique_id", /* colorMode= */ 123);
+        mDisplayContent.requestDisplayUpdate(mock(Runnable.class));
+
+        assertThat(mDisplayContent.getDisplayInfo().colorMode).isEqualTo(123);
+        assertThat(mDisplayContent.getDisplayInfo().uniqueId)
+                .isEqualTo("initial_unique_id");
+
+        // Update color mode again and keep the same unique id, color mode update
+        // should not be deferred, unique id update is still deferred as transition
+        // has not started collecting yet
+        givenDisplayInfo(/* uniqueId= */ "new_unique_id", /* colorMode= */ 456);
+        Runnable onUpdated = mock(Runnable.class);
+        mDisplayContent.requestDisplayUpdate(onUpdated);
+
+        assertThat(mDisplayContent.getDisplayInfo().colorMode).isEqualTo(456);
+        assertThat(mDisplayContent.getDisplayInfo().uniqueId)
+                .isEqualTo("initial_unique_id");
+
+        // Mark transition as started collected, so pending changes are applied
+        captureStartTransitionCollection().getValue().onCollectStarted(/* deferred= */ true);
+
+        // Verify that all fields have the latest values
+        verify(onUpdated).run();
+        assertThat(mDisplayContent.getDisplayInfo().colorMode).isEqualTo(456);
+        assertThat(mDisplayContent.getDisplayInfo().uniqueId).isEqualTo("new_unique_id");
+    }
+
+    @Test
+    public void testUpdate_deferrableFieldUpdatedTransitionPending_fieldNotUpdated() {
+        performInitialDisplayUpdate();
+        givenDisplayInfo(/* uniqueId= */ "old");
+        Runnable onUpdated = mock(Runnable.class);
+        mDisplayContent.requestDisplayUpdate(onUpdated);
+        captureStartTransitionCollection().getValue().onCollectStarted(/* deferred= */ true);
+        verify(onUpdated).run();
+        clearInvocations(mDisplayContent.mTransitionController, onUpdated);
+
+        givenDisplayInfo(/* uniqueId= */ "new");
+        mDisplayContent.requestDisplayUpdate(onUpdated);
+
+        captureStartTransitionCollection(); // do not continue by not starting the collection
+        verify(onUpdated, never()).run();
+        assertThat(mDisplayContent.getDisplayInfo().uniqueId).isEqualTo("old");
+    }
+
+    @Test
+    public void testTwoDisplayUpdates_transitionStarted_displayUpdated() {
+        performInitialDisplayUpdate();
+        givenDisplayInfo(/* uniqueId= */ "old");
+        Runnable onUpdated = mock(Runnable.class);
+        mDisplayContent.requestDisplayUpdate(onUpdated);
+        captureStartTransitionCollection().getValue()
+                .onCollectStarted(/* deferred= */ true);
+        verify(onUpdated).run();
+        clearInvocations(mDisplayContent.mTransitionController, onUpdated);
+
+        // Perform two display updates while WM is 'busy'
+        givenDisplayInfo(/* uniqueId= */ "new1");
+        Runnable onUpdated1 = mock(Runnable.class);
+        mDisplayContent.requestDisplayUpdate(onUpdated1);
+        givenDisplayInfo(/* uniqueId= */ "new2");
+        Runnable onUpdated2 = mock(Runnable.class);
+        mDisplayContent.requestDisplayUpdate(onUpdated2);
+
+        // Continue with the first update
+        captureStartTransitionCollection().getAllValues().get(0)
+                .onCollectStarted(/* deferred= */ true);
+        verify(onUpdated1).run();
+        verify(onUpdated2, never()).run();
+        assertThat(mDisplayContent.getDisplayInfo().uniqueId).isEqualTo("new1");
+
+        // Continue with the second update
+        captureStartTransitionCollection().getAllValues().get(1)
+                .onCollectStarted(/* deferred= */ true);
+        verify(onUpdated2).run();
+        assertThat(mDisplayContent.getDisplayInfo().uniqueId).isEqualTo("new2");
+    }
+
+    private void mockTransitionsController(boolean enabled) {
+        spyOn(mDisplayContent.mTransitionController);
+        when(mDisplayContent.mTransitionController.isShellTransitionsEnabled()).thenReturn(enabled);
+        doReturn(true).when(mDisplayContent.mTransitionController).startCollectOrQueue(any(),
+                any());
+    }
+
+    private void mockRemoteDisplayChangeController() {
+        spyOn(mDisplayContent.mRemoteDisplayChangeController);
+        doReturn(true).when(mDisplayContent.mRemoteDisplayChangeController)
+                .performRemoteDisplayChange(anyInt(), anyInt(), any(), any());
+    }
+
+    private ArgumentCaptor<OnStartCollect> captureStartTransitionCollection() {
+        ArgumentCaptor<OnStartCollect> callbackCaptor =
+                ArgumentCaptor.forClass(OnStartCollect.class);
+        verify(mDisplayContent.mTransitionController, atLeast(1)).startCollectOrQueue(any(),
+                callbackCaptor.capture());
+        return callbackCaptor;
+    }
+
+    private void givenDisplayInfo(String uniqueId) {
+        givenDisplayInfo(uniqueId, /* colorMode= */ 0);
+    }
+
+    private void givenDisplayInfo(String uniqueId, int colorMode) {
+        spyOn(mDisplayContent.mDisplay);
+        doAnswer(invocation -> {
+            DisplayInfo info = invocation.getArgument(0);
+            info.uniqueId = uniqueId;
+            info.colorMode = colorMode;
+            return null;
+        }).when(mDisplayContent.mDisplay).getDisplayInfo(any());
+    }
+
+    private void performInitialDisplayUpdate() {
+        givenDisplayInfo(/* uniqueId= */ "initial_unique_id", /* colorMode= */ 0);
+        Runnable onUpdated = mock(Runnable.class);
+        mDisplayContent.requestDisplayUpdate(onUpdated);
+    }
+}
diff --git a/services/tests/wmtests/src/com/android/server/wm/FrameRateSelectionPriorityTests.java b/services/tests/wmtests/src/com/android/server/wm/FrameRateSelectionPriorityTests.java
index c0a90b2..e77c14a 100644
--- a/services/tests/wmtests/src/com/android/server/wm/FrameRateSelectionPriorityTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/FrameRateSelectionPriorityTests.java
@@ -19,10 +19,13 @@
 import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION;
 
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.any;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.anyFloat;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.anyInt;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.eq;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.never;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.times;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
+import static com.android.window.flags.Flags.explicitRefreshRateHints;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
@@ -62,9 +65,11 @@
 
     private static final FrameRateVote FRAME_RATE_VOTE_NONE = new FrameRateVote();
     private static final FrameRateVote FRAME_RATE_VOTE_60_EXACT =
-            new FrameRateVote(60, Surface.FRAME_RATE_COMPATIBILITY_EXACT);
+            new FrameRateVote(60, Surface.FRAME_RATE_COMPATIBILITY_EXACT,
+                    SurfaceControl.FRAME_RATE_SELECTION_STRATEGY_OVERRIDE_CHILDREN);
     private static final FrameRateVote FRAME_RATE_VOTE_60_PREFERRED =
-            new FrameRateVote(60, Surface.FRAME_RATE_COMPATIBILITY_DEFAULT);
+            new FrameRateVote(60, Surface.FRAME_RATE_COMPATIBILITY_DEFAULT,
+                    SurfaceControl.FRAME_RATE_SELECTION_STRATEGY_OVERRIDE_CHILDREN);
 
     WindowState createWindow(String name) {
         WindowState window = createWindow(null, TYPE_APPLICATION, name);
@@ -110,6 +115,8 @@
                 any(SurfaceControl.class), anyInt());
         verify(appWindow.getPendingTransaction(), never()).setFrameRate(
                 any(SurfaceControl.class), anyInt(), anyInt(), anyInt());
+        verify(appWindow.getPendingTransaction(), never()).setFrameRateSelectionStrategy(
+                any(SurfaceControl.class), anyInt());
     }
 
     @Test
@@ -140,6 +147,8 @@
                 appWindow.getSurfaceControl(), 1);
         verify(appWindow.getPendingTransaction(), never()).setFrameRate(
                 any(SurfaceControl.class), anyInt(), anyInt(), anyInt());
+        verify(appWindow.getPendingTransaction(), never()).setFrameRateSelectionStrategy(
+                any(SurfaceControl.class), anyInt());
     }
 
     @Test
@@ -175,8 +184,17 @@
                 appWindow.getSurfaceControl(), 0);
         verify(appWindow.getPendingTransaction(), times(2)).setFrameRateSelectionPriority(
                 appWindow.getSurfaceControl(), 1);
-        verify(appWindow.getPendingTransaction(), never()).setFrameRate(
-                any(SurfaceControl.class), anyInt(), anyInt(), anyInt());
+        verify(appWindow.getPendingTransaction(), times(1)).setFrameRate(
+                eq(appWindow.getSurfaceControl()), anyFloat(),
+                eq(Surface.FRAME_RATE_COMPATIBILITY_EXACT), eq(Surface.CHANGE_FRAME_RATE_ALWAYS));
+        if (explicitRefreshRateHints()) {
+            verify(appWindow.getPendingTransaction(), times(1)).setFrameRateSelectionStrategy(
+                    appWindow.getSurfaceControl(),
+                    SurfaceControl.FRAME_RATE_SELECTION_STRATEGY_OVERRIDE_CHILDREN);
+        } else {
+            verify(appWindow.getPendingTransaction(), never()).setFrameRateSelectionStrategy(
+                    any(SurfaceControl.class), anyInt());
+        }
     }
 
     @Test
@@ -202,8 +220,17 @@
                 appWindow.getSurfaceControl(), RefreshRatePolicy.LAYER_PRIORITY_UNSET);
         verify(appWindow.getPendingTransaction()).setFrameRateSelectionPriority(
                 appWindow.getSurfaceControl(), 2);
-        verify(appWindow.getPendingTransaction(), never()).setFrameRate(
-                any(SurfaceControl.class), anyInt(), anyInt(), anyInt());
+        verify(appWindow.getPendingTransaction(), times(1)).setFrameRate(
+                eq(appWindow.getSurfaceControl()), anyFloat(),
+                eq(Surface.FRAME_RATE_COMPATIBILITY_EXACT), eq(Surface.CHANGE_FRAME_RATE_ALWAYS));
+        if (explicitRefreshRateHints()) {
+            verify(appWindow.getPendingTransaction(), times(1)).setFrameRateSelectionStrategy(
+                    appWindow.getSurfaceControl(),
+                    SurfaceControl.FRAME_RATE_SELECTION_STRATEGY_OVERRIDE_CHILDREN);
+        } else {
+            verify(appWindow.getPendingTransaction(), never()).setFrameRateSelectionStrategy(
+                    any(SurfaceControl.class), anyInt());
+        }
     }
 
     @Test
@@ -229,6 +256,8 @@
                 appWindow.getSurfaceControl(), RefreshRatePolicy.LAYER_PRIORITY_UNSET);
         verify(appWindow.getPendingTransaction(), never()).setFrameRate(
                 any(SurfaceControl.class), anyInt(), anyInt(), anyInt());
+        verify(appWindow.getPendingTransaction(), never()).setFrameRateSelectionStrategy(
+                any(SurfaceControl.class), anyInt());
     }
 
     @Test
@@ -256,6 +285,14 @@
         verify(appWindow.getPendingTransaction(), times(1)).setFrameRate(
                 appWindow.getSurfaceControl(), 60,
                 Surface.FRAME_RATE_COMPATIBILITY_EXACT, Surface.CHANGE_FRAME_RATE_ALWAYS);
+        if (explicitRefreshRateHints()) {
+            verify(appWindow.getPendingTransaction(), times(1)).setFrameRateSelectionStrategy(
+                    appWindow.getSurfaceControl(),
+                    SurfaceControl.FRAME_RATE_SELECTION_STRATEGY_OVERRIDE_CHILDREN);
+        } else {
+            verify(appWindow.getPendingTransaction(), never()).setFrameRateSelectionStrategy(
+                    any(SurfaceControl.class), anyInt());
+        }
     }
 
     @Test
@@ -283,6 +320,8 @@
                 appWindow.getSurfaceControl(), RefreshRatePolicy.LAYER_PRIORITY_UNSET);
         verify(appWindow.getPendingTransaction(), never()).setFrameRate(
                 any(SurfaceControl.class), anyInt(), anyInt(), anyInt());
+        verify(appWindow.getPendingTransaction(), never()).setFrameRateSelectionStrategy(
+                any(SurfaceControl.class), anyInt());
     }
 
     @Test
@@ -310,5 +349,13 @@
         verify(appWindow.getPendingTransaction(), times(1)).setFrameRate(
                 appWindow.getSurfaceControl(), 60,
                 Surface.FRAME_RATE_COMPATIBILITY_DEFAULT, Surface.CHANGE_FRAME_RATE_ALWAYS);
+        if (explicitRefreshRateHints()) {
+            verify(appWindow.getPendingTransaction(), times(1)).setFrameRateSelectionStrategy(
+                    appWindow.getSurfaceControl(),
+                    SurfaceControl.FRAME_RATE_SELECTION_STRATEGY_OVERRIDE_CHILDREN);
+        } else {
+            verify(appWindow.getPendingTransaction(), never()).setFrameRateSelectionStrategy(
+                    any(SurfaceControl.class), anyInt());
+        }
     }
 }
diff --git a/services/tests/wmtests/src/com/android/server/wm/InsetsPolicyTest.java b/services/tests/wmtests/src/com/android/server/wm/InsetsPolicyTest.java
index ffa1ed9..38a66a9 100644
--- a/services/tests/wmtests/src/com/android/server/wm/InsetsPolicyTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/InsetsPolicyTest.java
@@ -19,10 +19,13 @@
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
 import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
 import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
+import static android.view.InsetsSource.ID_IME;
 import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
+import static android.view.WindowInsets.Type.ime;
 import static android.view.WindowInsets.Type.navigationBars;
 import static android.view.WindowInsets.Type.statusBars;
 import static android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
+import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_CONSUME_IME_INSETS;
 import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION;
 import static android.view.WindowManager.LayoutParams.TYPE_NAVIGATION_BAR;
 import static android.view.WindowManager.LayoutParams.TYPE_NOTIFICATION_SHADE;
@@ -432,6 +435,56 @@
 
     }
 
+    @SetupWindows(addWindows = W_INPUT_METHOD)
+    @Test
+    public void testConsumeImeInsets() {
+        final DisplayPolicy displayPolicy = mDisplayContent.getDisplayPolicy();
+        final InsetsSource imeSource = new InsetsSource(ID_IME, ime());
+        imeSource.setVisible(true);
+        mImeWindow.mHasSurface = true;
+
+        final WindowState win1 = addWindow(TYPE_APPLICATION, "win1");
+        final WindowState win2 = addWindow(TYPE_APPLICATION, "win2");
+
+        win1.mAboveInsetsState.addSource(imeSource);
+        win1.mHasSurface = true;
+        win2.mAboveInsetsState.addSource(imeSource);
+        win2.mHasSurface = true;
+
+        assertTrue(mImeWindow.isVisible());
+        assertTrue(win1.isVisible());
+        assertTrue(win2.isVisible());
+
+        // Make sure both windows have visible IME insets.
+        assertTrue(win1.getInsetsState().isSourceOrDefaultVisible(ID_IME, ime()));
+        assertTrue(win2.getInsetsState().isSourceOrDefaultVisible(ID_IME, ime()));
+
+        win2.mAttrs.privateFlags |= PRIVATE_FLAG_CONSUME_IME_INSETS;
+
+        displayPolicy.beginPostLayoutPolicyLw();
+        displayPolicy.applyPostLayoutPolicyLw(win2, win2.mAttrs, null, null);
+        displayPolicy.applyPostLayoutPolicyLw(win1, win1.mAttrs, null, null);
+        displayPolicy.finishPostLayoutPolicyLw();
+
+        // Make sure win2 doesn't have visible IME insets, but win1 still does.
+        assertTrue(win2.getInsetsState().isSourceOrDefaultVisible(ID_IME, ime()));
+        assertFalse(win1.getInsetsState().isSourceOrDefaultVisible(ID_IME, ime()));
+        assertTrue(win1.getWindowFrames().hasInsetsChanged());
+
+        win2.mAttrs.privateFlags &= ~PRIVATE_FLAG_CONSUME_IME_INSETS;
+        win2.getWindowFrames().setInsetsChanged(false);
+
+        displayPolicy.beginPostLayoutPolicyLw();
+        displayPolicy.applyPostLayoutPolicyLw(win2, win2.mAttrs, null, null);
+        displayPolicy.applyPostLayoutPolicyLw(win1, win1.mAttrs, null, null);
+        displayPolicy.finishPostLayoutPolicyLw();
+
+        // Make sure both windows have visible IME insets.
+        assertTrue(win1.getInsetsState().isSourceOrDefaultVisible(ID_IME, ime()));
+        assertTrue(win2.getInsetsState().isSourceOrDefaultVisible(ID_IME, ime()));
+        assertTrue(win1.getWindowFrames().hasInsetsChanged());
+    }
+
     private WindowState addNavigationBar() {
         final Binder owner = new Binder();
         final WindowState win = createWindow(null, TYPE_NAVIGATION_BAR, "navBar");
diff --git a/services/tests/wmtests/src/com/android/server/wm/RefreshRatePolicyTest.java b/services/tests/wmtests/src/com/android/server/wm/RefreshRatePolicyTest.java
index 49a8886..c9a83b0 100644
--- a/services/tests/wmtests/src/com/android/server/wm/RefreshRatePolicyTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/RefreshRatePolicyTest.java
@@ -16,6 +16,7 @@
 
 package com.android.server.wm;
 
+import static android.view.SurfaceControl.FRAME_RATE_SELECTION_STRATEGY_OVERRIDE_CHILDREN;
 import static android.view.SurfaceControl.RefreshRateRange.FLOAT_TOLERANCE;
 import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
 
@@ -69,15 +70,20 @@
 
     private static final FrameRateVote FRAME_RATE_VOTE_NONE = new FrameRateVote();
     private static final FrameRateVote FRAME_RATE_VOTE_DENY_LIST =
-            new FrameRateVote(LOW_REFRESH_RATE, Surface.FRAME_RATE_COMPATIBILITY_EXACT);
+            new FrameRateVote(LOW_REFRESH_RATE, Surface.FRAME_RATE_COMPATIBILITY_EXACT,
+                    FRAME_RATE_SELECTION_STRATEGY_OVERRIDE_CHILDREN);
     private static final FrameRateVote FRAME_RATE_VOTE_LOW_EXACT =
-            new FrameRateVote(LOW_REFRESH_RATE, Surface.FRAME_RATE_COMPATIBILITY_EXACT);
+            new FrameRateVote(LOW_REFRESH_RATE, Surface.FRAME_RATE_COMPATIBILITY_EXACT,
+                    FRAME_RATE_SELECTION_STRATEGY_OVERRIDE_CHILDREN);
     private static final FrameRateVote FRAME_RATE_VOTE_HI_EXACT =
-            new FrameRateVote(HI_REFRESH_RATE, Surface.FRAME_RATE_COMPATIBILITY_EXACT);
+            new FrameRateVote(HI_REFRESH_RATE, Surface.FRAME_RATE_COMPATIBILITY_EXACT,
+                    FRAME_RATE_SELECTION_STRATEGY_OVERRIDE_CHILDREN);
     private static final FrameRateVote FRAME_RATE_VOTE_LOW_PREFERRED =
-            new FrameRateVote(LOW_REFRESH_RATE, Surface.FRAME_RATE_COMPATIBILITY_DEFAULT);
+            new FrameRateVote(LOW_REFRESH_RATE, Surface.FRAME_RATE_COMPATIBILITY_DEFAULT,
+                    FRAME_RATE_SELECTION_STRATEGY_OVERRIDE_CHILDREN);
     private static final FrameRateVote FRAME_RATE_VOTE_HI_PREFERRED =
-            new FrameRateVote(HI_REFRESH_RATE, Surface.FRAME_RATE_COMPATIBILITY_DEFAULT);
+            new FrameRateVote(HI_REFRESH_RATE, Surface.FRAME_RATE_COMPATIBILITY_DEFAULT,
+                    FRAME_RATE_SELECTION_STRATEGY_OVERRIDE_CHILDREN);
 
     // Parcel and Unparcel the LayoutParams in the window state to test the path the object
     // travels from the app's process to system server
diff --git a/services/tests/wmtests/src/com/android/server/wm/TrustedPresentationCallbackTest.java b/services/tests/wmtests/src/com/android/server/wm/TrustedPresentationCallbackTest.java
deleted file mode 100644
index c5dd447..0000000
--- a/services/tests/wmtests/src/com/android/server/wm/TrustedPresentationCallbackTest.java
+++ /dev/null
@@ -1,154 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.wm;
-
-import static android.server.wm.ActivityManagerTestBase.createFullscreenActivityScenarioRule;
-import static android.server.wm.BuildUtils.HW_TIMEOUT_MULTIPLIER;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertTrue;
-
-import android.app.Activity;
-import android.platform.test.annotations.Presubmit;
-import android.server.wm.CtsWindowInfoUtils;
-import android.view.SurfaceControl;
-import android.view.SurfaceControl.TrustedPresentationThresholds;
-
-import androidx.annotation.GuardedBy;
-import androidx.test.ext.junit.rules.ActivityScenarioRule;
-
-import com.android.server.wm.utils.CommonUtils;
-
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.rules.TestName;
-
-import java.util.function.Consumer;
-
-/**
- * TODO (b/287076178): Move these tests to
- * {@link android.view.surfacecontrol.cts.TrustedPresentationCallbackTest} when API is made public
- */
-@Presubmit
-public class TrustedPresentationCallbackTest {
-    private static final String TAG = "TrustedPresentationCallbackTest";
-    private static final int STABILITY_REQUIREMENT_MS = 500;
-    private static final long WAIT_TIME_MS = HW_TIMEOUT_MULTIPLIER * 2000L;
-
-    private static final float FRACTION_VISIBLE = 0.1f;
-
-    private final Object mResultsLock = new Object();
-    @GuardedBy("mResultsLock")
-    private boolean mResult;
-    @GuardedBy("mResultsLock")
-    private boolean mReceivedResults;
-
-    @Rule
-    public TestName mName = new TestName();
-
-    @Rule
-    public ActivityScenarioRule<TestActivity> mActivityRule = createFullscreenActivityScenarioRule(
-            TestActivity.class);
-
-    private TestActivity mActivity;
-
-    @Before
-    public void setup() {
-        mActivityRule.getScenario().onActivity(activity -> mActivity = activity);
-    }
-
-    @After
-    public void tearDown() {
-        CommonUtils.waitUntilActivityRemoved(mActivity);
-    }
-
-    @Test
-    public void testAddTrustedPresentationListenerOnWindow() throws InterruptedException {
-        TrustedPresentationThresholds thresholds = new TrustedPresentationThresholds(
-                1 /* minAlpha */, FRACTION_VISIBLE, STABILITY_REQUIREMENT_MS);
-        SurfaceControl.Transaction t = new SurfaceControl.Transaction();
-        mActivity.getWindow().getRootSurfaceControl().addTrustedPresentationCallback(t, thresholds,
-                Runnable::run, inTrustedPresentationState -> {
-                    synchronized (mResultsLock) {
-                        mResult = inTrustedPresentationState;
-                        mReceivedResults = true;
-                        mResultsLock.notify();
-                    }
-                });
-        t.apply();
-        synchronized (mResultsLock) {
-            assertResults();
-        }
-    }
-
-    @Test
-    public void testRemoveTrustedPresentationListenerOnWindow() throws InterruptedException {
-        TrustedPresentationThresholds thresholds = new TrustedPresentationThresholds(
-                1 /* minAlpha */, FRACTION_VISIBLE, STABILITY_REQUIREMENT_MS);
-        Consumer<Boolean> trustedPresentationCallback = inTrustedPresentationState -> {
-            synchronized (mResultsLock) {
-                mResult = inTrustedPresentationState;
-                mReceivedResults = true;
-                mResultsLock.notify();
-            }
-        };
-        SurfaceControl.Transaction t = new SurfaceControl.Transaction();
-        mActivity.getWindow().getRootSurfaceControl().addTrustedPresentationCallback(t, thresholds,
-                Runnable::run, trustedPresentationCallback);
-        t.apply();
-
-        synchronized (mResultsLock) {
-            if (!mReceivedResults) {
-                mResultsLock.wait(WAIT_TIME_MS);
-            }
-            assertResults();
-            // reset the state
-            mReceivedResults = false;
-        }
-
-        mActivity.getWindow().getRootSurfaceControl().removeTrustedPresentationCallback(t,
-                trustedPresentationCallback);
-        t.apply();
-
-        synchronized (mResultsLock) {
-            if (!mReceivedResults) {
-                mResultsLock.wait(WAIT_TIME_MS);
-            }
-            // Ensure we waited the full time and never received a notify on the result from the
-            // callback.
-            assertFalse("Should never have received a callback", mReceivedResults);
-            // results shouldn't have changed.
-            assertTrue(mResult);
-        }
-    }
-
-    @GuardedBy("mResultsLock")
-    private void assertResults() throws InterruptedException {
-        mResultsLock.wait(WAIT_TIME_MS);
-
-        if (!mReceivedResults) {
-            CtsWindowInfoUtils.dumpWindowsOnScreen(TAG, "test " + mName.getMethodName());
-        }
-        // Make sure we received the results and not just timed out
-        assertTrue("Timed out waiting for results", mReceivedResults);
-        assertTrue(mResult);
-    }
-
-    public static class TestActivity extends Activity {
-    }
-}
diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java
index 1e68687..7cb2cc3 100644
--- a/telephony/java/android/telephony/CarrierConfigManager.java
+++ b/telephony/java/android/telephony/CarrierConfigManager.java
@@ -9593,6 +9593,84 @@
             "satellite_connection_hysteresis_sec_int";
 
     /**
+     * This threshold is used when connected to a non-terrestrial LTE network.
+     * A list of 4 NTN LTE RSRP thresholds above which a signal level is considered POOR,
+     * MODERATE, GOOD, or EXCELLENT, to be used in SignalStrength reporting.
+     *
+     * Note that the min and max thresholds are fixed at -140 and -44, as explained in
+     * TS 136.133 9.1.4 - RSRP Measurement Report Mapping.
+     * <p>
+     * See SignalStrength#MAX_LTE_RSRP and SignalStrength#MIN_LTE_RSRP. Any signal level outside
+     * these boundaries is considered invalid.
+     */
+    @FlaggedApi(Flags.FLAG_CARRIER_ENABLED_SATELLITE_FLAG)
+    public static final String KEY_NTN_LTE_RSRP_THRESHOLDS_INT_ARRAY =
+            "ntn_lte_rsrp_thresholds_int_array";
+
+    /**
+     * This threshold is used when connected to a non-terrestrial LTE network.
+     * A list of 4 customized NTN LTE Reference Signal Received Quality (RSRQ) thresholds.
+     *
+     * Reference: TS 136.133 v12.6.0 section 9.1.7 - RSRQ Measurement Report Mapping.
+     *
+     * 4 threshold integers must be within the boundaries [-34 dB, 3 dB], and the levels are:
+     *     "NONE: [-34, threshold1)"
+     *     "POOR: [threshold1, threshold2)"
+     *     "MODERATE: [threshold2, threshold3)"
+     *     "GOOD:  [threshold3, threshold4)"
+     *     "EXCELLENT:  [threshold4, 3]"
+     *
+     * This key is considered invalid if the format is violated. If the key is invalid or
+     * not configured, a default value set will apply.
+     */
+    @FlaggedApi(Flags.FLAG_CARRIER_ENABLED_SATELLITE_FLAG)
+    public static final String KEY_NTN_LTE_RSRQ_THRESHOLDS_INT_ARRAY =
+            "ntn_lte_rsrq_thresholds_int_array";
+
+    /**
+     * This threshold is used when connected to a non-terrestrial LTE network.
+     * A list of 4 customized NTN LTE Reference Signal Signal to Noise Ratio (RSSNR) thresholds.
+     *
+     * 4 threshold integers must be within the boundaries [-20 dB, 30 dB], and the levels are:
+     *     "NONE: [-20, threshold1)"
+     *     "POOR: [threshold1, threshold2)"
+     *     "MODERATE: [threshold2, threshold3)"
+     *     "GOOD:  [threshold3, threshold4)"
+     *     "EXCELLENT:  [threshold4, 30]"
+     *
+     * This key is considered invalid if the format is violated. If the key is invalid or
+     * not configured, a default value set will apply.
+     */
+    @FlaggedApi(Flags.FLAG_CARRIER_ENABLED_SATELLITE_FLAG)
+    public static final String KEY_NTN_LTE_RSSNR_THRESHOLDS_INT_ARRAY =
+            "ntn_lte_rssnr_thresholds_int_array";
+
+    /**
+     * This threshold is used when connected to a non-terrestrial LTE network.
+     * Bit-field integer to determine whether to use Reference Signal Received Power (RSRP),
+     * Reference Signal Received Quality (RSRQ), or/and Reference Signal Signal to Noise Ratio
+     * (RSSNR) for the number of NTN LTE signal bars and signal criteria reporting enabling.
+     *
+     * <p> If a measure is not set, signal criteria reporting from modem will not be triggered and
+     * not be used for calculating signal level. If multiple measures are set bit, the parameter
+     * whose value is smallest is used to indicate the signal level.
+     * <UL>
+     *  <LI>RSRP = 1 << 0</LI>
+     *  <LI>RSRQ = 1 << 1</LI>
+     *  <LI>RSSNR = 1 << 2</LI>
+     * </UL>
+     * <p> The value of this key must be bitwise OR of CellSignalStrengthLte#USE_RSRP,
+     * CellSignalStrengthLte#USE_RSRQ, CellSignalStrengthLte#USE_RSSNR.
+     *
+     * <p> For example, if both RSRP and RSRQ are used, the value of key is 3 (1 << 0 | 1 << 1).
+     * If the key is invalid or not configured, a default value (RSRP = 1 << 0) will apply.
+     *
+     */
+    @FlaggedApi(Flags.FLAG_CARRIER_ENABLED_SATELLITE_FLAG)
+    public static final String KEY_PARAMETERS_USED_FOR_NTN_LTE_SIGNAL_BAR_INT =
+            "parameters_used_for_ntn_lte_signal_bar_int";
+
+    /**
      * Indicating whether DUN APN should be disabled when the device is roaming. In that case,
      * the default APN (i.e. internet) will be used for tethering.
      *
@@ -9814,7 +9892,6 @@
      *
      * @see TelephonyManager#PURCHASE_PREMIUM_CAPABILITY_RESULT_USER_CANCELED
      * @see TelephonyManager#PURCHASE_PREMIUM_CAPABILITY_RESULT_TIMEOUT
-     * @see TelephonyManager#PURCHASE_PREMIUM_CAPABILITY_RESULT_USER_DISABLED
      */
     public static final String
             KEY_PREMIUM_CAPABILITY_NOTIFICATION_BACKOFF_HYSTERESIS_TIME_MILLIS_LONG =
@@ -10628,6 +10705,32 @@
                 PersistableBundle.EMPTY);
         sDefaults.putBoolean(KEY_SATELLITE_ATTACH_SUPPORTED_BOOL, false);
         sDefaults.putInt(KEY_SATELLITE_CONNECTION_HYSTERESIS_SEC_INT, 300);
+        sDefaults.putIntArray(KEY_NTN_LTE_RSRP_THRESHOLDS_INT_ARRAY,
+                // Boundaries: [-140 dBm, -44 dBm]
+                new int[]{
+                        -128, /* SIGNAL_STRENGTH_POOR */
+                        -118, /* SIGNAL_STRENGTH_MODERATE */
+                        -108, /* SIGNAL_STRENGTH_GOOD */
+                        -98  /* SIGNAL_STRENGTH_GREAT */
+                });
+        sDefaults.putIntArray(KEY_NTN_LTE_RSRQ_THRESHOLDS_INT_ARRAY,
+                // Boundaries: [-34 dB, 3 dB]
+                new int[]{
+                        -20, /* SIGNAL_STRENGTH_POOR */
+                        -17, /* SIGNAL_STRENGTH_MODERATE */
+                        -14, /* SIGNAL_STRENGTH_GOOD */
+                        -11  /* SIGNAL_STRENGTH_GREAT */
+                });
+        sDefaults.putIntArray(KEY_NTN_LTE_RSSNR_THRESHOLDS_INT_ARRAY,
+                // Boundaries: [-20 dBm, 30 dBm]
+                new int[] {
+                        -3, /* SIGNAL_STRENGTH_POOR */
+                        1, /* SIGNAL_STRENGTH_MODERATE */
+                        5,  /* SIGNAL_STRENGTH_GOOD */
+                        13   /* SIGNAL_STRENGTH_GREAT */
+                });
+        sDefaults.putInt(KEY_PARAMETERS_USED_FOR_NTN_LTE_SIGNAL_BAR_INT,
+                CellSignalStrengthLte.USE_RSRP);
         sDefaults.putBoolean(KEY_DISABLE_DUN_APN_WHILE_ROAMING_WITH_PRESET_APN_BOOL, false);
         sDefaults.putString(KEY_DEFAULT_PREFERRED_APN_NAME_STRING, "");
         sDefaults.putBoolean(KEY_SUPPORTS_CALL_COMPOSER_BOOL, false);
diff --git a/telephony/java/android/telephony/CellSignalStrengthLte.java b/telephony/java/android/telephony/CellSignalStrengthLte.java
index 5e90261..f528263 100644
--- a/telephony/java/android/telephony/CellSignalStrengthLte.java
+++ b/telephony/java/android/telephony/CellSignalStrengthLte.java
@@ -263,29 +263,35 @@
             rssnrThresholds = sRssnrThresholds;
             rsrpOnly = false;
         } else {
-            mParametersUseForLevel = cc.getInt(
-                    CarrierConfigManager.KEY_PARAMETERS_USED_FOR_LTE_SIGNAL_BAR_INT);
-            if (DBG) {
-                Rlog.i(LOG_TAG, "Using signal strength level: " + mParametersUseForLevel);
+            if (ss != null && ss.isUsingNonTerrestrialNetwork()) {
+                if (DBG) log("updateLevel: from NTN_LTE");
+                mParametersUseForLevel = cc.getInt(
+                        CarrierConfigManager.KEY_PARAMETERS_USED_FOR_NTN_LTE_SIGNAL_BAR_INT);
+                rsrpThresholds = cc.getIntArray(
+                        CarrierConfigManager.KEY_NTN_LTE_RSRP_THRESHOLDS_INT_ARRAY);
+                rsrqThresholds = cc.getIntArray(
+                        CarrierConfigManager.KEY_NTN_LTE_RSRQ_THRESHOLDS_INT_ARRAY);
+                rssnrThresholds = cc.getIntArray(
+                        CarrierConfigManager.KEY_NTN_LTE_RSSNR_THRESHOLDS_INT_ARRAY);
+            } else {
+                mParametersUseForLevel = cc.getInt(
+                        CarrierConfigManager.KEY_PARAMETERS_USED_FOR_LTE_SIGNAL_BAR_INT);
+                rsrpThresholds = cc.getIntArray(
+                        CarrierConfigManager.KEY_LTE_RSRP_THRESHOLDS_INT_ARRAY);
+                rsrqThresholds = cc.getIntArray(
+                        CarrierConfigManager.KEY_LTE_RSRQ_THRESHOLDS_INT_ARRAY);
+                rssnrThresholds = cc.getIntArray(
+                        CarrierConfigManager.KEY_LTE_RSSNR_THRESHOLDS_INT_ARRAY);
             }
-            rsrpThresholds = cc.getIntArray(
-                    CarrierConfigManager.KEY_LTE_RSRP_THRESHOLDS_INT_ARRAY);
             if (rsrpThresholds == null) rsrpThresholds = sRsrpThresholds;
-            if (DBG) {
-                Rlog.i(LOG_TAG, "Applying LTE RSRP Thresholds: "
-                        + Arrays.toString(rsrpThresholds));
-            }
-            rsrqThresholds = cc.getIntArray(
-                    CarrierConfigManager.KEY_LTE_RSRQ_THRESHOLDS_INT_ARRAY);
             if (rsrqThresholds == null) rsrqThresholds = sRsrqThresholds;
-            if (DBG) {
-                Rlog.i(LOG_TAG, "Applying LTE RSRQ Thresholds: "
-                        + Arrays.toString(rsrqThresholds));
-            }
-            rssnrThresholds = cc.getIntArray(
-                    CarrierConfigManager.KEY_LTE_RSSNR_THRESHOLDS_INT_ARRAY);
             if (rssnrThresholds == null) rssnrThresholds = sRssnrThresholds;
             if (DBG) {
+                Rlog.i(LOG_TAG, "Using signal strength level: " + mParametersUseForLevel);
+                Rlog.i(LOG_TAG, "Applying LTE RSRP Thresholds: "
+                        + Arrays.toString(rsrpThresholds));
+                Rlog.i(LOG_TAG, "Applying LTE RSRQ Thresholds: "
+                        + Arrays.toString(rsrqThresholds));
                 Rlog.i(LOG_TAG, "Applying LTE RSSNR Thresholds: "
                         + Arrays.toString(rssnrThresholds));
             }
diff --git a/telephony/java/android/telephony/SubscriptionManager.java b/telephony/java/android/telephony/SubscriptionManager.java
index e12a815..b356fde 100644
--- a/telephony/java/android/telephony/SubscriptionManager.java
+++ b/telephony/java/android/telephony/SubscriptionManager.java
@@ -109,10 +109,6 @@
  * Then for SDK 35+, if the caller identity is personal profile, then
  * {@link #getActiveSubscriptionInfoList} will return subscription 1 only and vice versa.
  *
- * <p>If the caller needs to see all subscriptions across user profiles,
- * use {@link #createForAllUserProfiles} to convert the instance to see all. Additional permission
- * may be required as documented on the each API.
- *
  */
 @SystemService(Context.TELEPHONY_SUBSCRIPTION_SERVICE)
 @RequiresFeature(PackageManager.FEATURE_TELEPHONY_SUBSCRIPTION)
@@ -1815,9 +1811,6 @@
      * Then for SDK 35+, if the caller identity is personal profile, then this will return
      * subscription 1 only and vice versa.
      *
-     * <p>If the caller needs to see all subscriptions across user profiles,
-     * use {@link #createForAllUserProfiles} to convert this instance to see all.
-     *
      * <p> The records will be sorted by {@link SubscriptionInfo#getSimSlotIndex} then by
      * {@link SubscriptionInfo#getSubscriptionId}.
      *
@@ -2085,9 +2078,6 @@
      * Android SDK 35(V) and above, while Android SDK 34(U) and below can see all subscriptions as
      * it does today.
      *
-     * <p>If the caller needs to see all subscriptions across user profiles,
-     * use {@link #createForAllUserProfiles} to convert this instance to see all.
-     *
      * @return The current number of active subscriptions.
      *
      * @see #getActiveSubscriptionInfoList()
diff --git a/tools/hoststubgen/hoststubgen/helper-framework-runtime-src/framework/com/android/hoststubgen/nativesubstitution/LongArrayMultiStateCounter_host.java b/tools/hoststubgen/hoststubgen/helper-framework-runtime-src/framework/com/android/hoststubgen/nativesubstitution/LongArrayMultiStateCounter_host.java
new file mode 100644
index 0000000..068dfe8
--- /dev/null
+++ b/tools/hoststubgen/hoststubgen/helper-framework-runtime-src/framework/com/android/hoststubgen/nativesubstitution/LongArrayMultiStateCounter_host.java
@@ -0,0 +1,362 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.hoststubgen.nativesubstitution;
+
+import android.os.BadParcelableException;
+import android.os.Parcel;
+
+import java.util.Arrays;
+import java.util.HashMap;
+
+/**
+ * Native implementation substitutions for the LongArrayMultiStateCounter class.
+ */
+public class LongArrayMultiStateCounter_host {
+
+    /**
+     * A reimplementation of {@link com.android.internal.os.LongArrayMultiStateCounter}, only in
+     * Java instead of native.  The majority of the code (in C++) can be found in
+     * /frameworks/native/libs/battery/MultiStateCounter.h
+     */
+    private static class LongArrayMultiStateCounterRavenwood {
+        private final int mStateCount;
+        private final int mArrayLength;
+        private int mCurrentState;
+        private long mLastStateChangeTimestampMs = -1;
+        private long mLastUpdateTimestampMs = -1;
+        private boolean mEnabled = true;
+
+        private static class State {
+            private long mTimeInStateSinceUpdate;
+            private long[] mCounter;
+        }
+
+        private final State[] mStates;
+        private final long[] mValues;
+        private final long[] mDelta;
+
+        LongArrayMultiStateCounterRavenwood(int stateCount, int arrayLength) {
+            mStateCount = stateCount;
+            mArrayLength = arrayLength;
+            mStates = new State[stateCount];
+            for (int i = 0; i < mStateCount; i++) {
+                mStates[i] = new State();
+                mStates[i].mCounter = new long[mArrayLength];
+            }
+            mValues = new long[mArrayLength];
+            mDelta = new long[mArrayLength];
+        }
+
+        public void setEnabled(boolean enabled, long timestampMs) {
+            if (enabled == mEnabled) {
+                return;
+            }
+
+            if (!enabled) {
+                setState(mCurrentState, timestampMs);
+                mEnabled = false;
+            } else {
+                if (timestampMs < mLastUpdateTimestampMs) {
+                    timestampMs = mLastUpdateTimestampMs;
+                }
+
+                if (mLastStateChangeTimestampMs >= 0) {
+                    mLastStateChangeTimestampMs = timestampMs;
+                }
+                mEnabled = true;
+            }
+        }
+
+        public void setState(int state, long timestampMs) {
+            if (mEnabled && mLastStateChangeTimestampMs >= 0 && mLastUpdateTimestampMs >= 0) {
+                if (timestampMs < mLastUpdateTimestampMs) {
+                    timestampMs = mLastUpdateTimestampMs;
+                }
+
+                if (timestampMs >= mLastStateChangeTimestampMs) {
+                    mStates[mCurrentState].mTimeInStateSinceUpdate +=
+                            timestampMs - mLastStateChangeTimestampMs;
+                } else {
+                    for (int i = 0; i < mStateCount; i++) {
+                        mStates[i].mTimeInStateSinceUpdate = 0;
+                    }
+                }
+            }
+            mCurrentState = state;
+            mLastStateChangeTimestampMs = timestampMs;
+        }
+
+        public void updateValue(long[] values, long timestampMs) {
+            if (mEnabled || mLastUpdateTimestampMs < mLastStateChangeTimestampMs) {
+                if (timestampMs < mLastStateChangeTimestampMs) {
+                    timestampMs = mLastStateChangeTimestampMs;
+                }
+
+                setState(mCurrentState, timestampMs);
+
+                if (mLastUpdateTimestampMs >= 0) {
+                    if (timestampMs > mLastUpdateTimestampMs) {
+                        if (delta(mValues, values, mDelta)) {
+                            long timeSinceUpdate = timestampMs - mLastUpdateTimestampMs;
+                            for (int i = 0; i < mStateCount; i++) {
+                                long timeInState = mStates[i].mTimeInStateSinceUpdate;
+                                if (timeInState > 0) {
+                                    add(mStates[i].mCounter, mDelta, timeInState, timeSinceUpdate);
+                                    mStates[i].mTimeInStateSinceUpdate = 0;
+                                }
+                            }
+                        } else {
+                            throw new RuntimeException();
+                        }
+                    } else if (timestampMs < mLastUpdateTimestampMs) {
+                        throw new RuntimeException();
+                    }
+                }
+            }
+            System.arraycopy(values, 0, mValues, 0, mArrayLength);
+            mLastUpdateTimestampMs = timestampMs;
+        }
+
+        public void incrementValues(long[] delta, long timestampMs) {
+            long[] values = Arrays.copyOf(mValues, mValues.length);
+            for (int i = 0; i < mArrayLength; i++) {
+                values[i] += delta[i];
+            }
+            updateValue(values, timestampMs);
+        }
+
+        public void getValues(long[] values, int state) {
+            System.arraycopy(mStates[state].mCounter, 0, values, 0, mArrayLength);
+        }
+
+        public void reset() {
+            mLastStateChangeTimestampMs = -1;
+            mLastUpdateTimestampMs = -1;
+            for (int i = 0; i < mStateCount; i++) {
+                mStates[i].mTimeInStateSinceUpdate = 0;
+                Arrays.fill(mStates[i].mCounter, 0);
+            }
+        }
+
+        public void writeToParcel(Parcel parcel) {
+            parcel.writeInt(mStateCount);
+            parcel.writeInt(mArrayLength);
+            for (int i = 0; i < mStateCount; i++) {
+                parcel.writeLongArray(mStates[i].mCounter);
+            }
+        }
+
+        public void initFromParcel(Parcel parcel) {
+            try {
+                for (int i = 0; i < mStateCount; i++) {
+                    parcel.readLongArray(mStates[i].mCounter);
+                }
+            } catch (Exception e) {
+                throw new BadParcelableException(e);
+            }
+        }
+
+        @Override
+        public String toString() {
+            StringBuilder sb = new StringBuilder();
+            sb.append("[");
+            for (int state = 0; state < mStateCount; state++) {
+                if (state != 0) {
+                    sb.append(", ");
+                }
+                sb.append(state).append(": {");
+                for (int i = 0; i < mStates[state].mCounter.length; i++) {
+                    if (i != 0) {
+                        sb.append(", ");
+                    }
+                    sb.append(mStates[state].mCounter[i]);
+                }
+                sb.append("}");
+            }
+            sb.append("]");
+            if (mLastUpdateTimestampMs >= 0) {
+                sb.append(" updated: ").append(mLastUpdateTimestampMs);
+            }
+            if (mLastStateChangeTimestampMs >= 0) {
+                sb.append(" currentState: ").append(mCurrentState);
+                if (mLastStateChangeTimestampMs > mLastUpdateTimestampMs) {
+                    sb.append(" stateChanged: ").append(mLastStateChangeTimestampMs);
+                }
+            } else {
+                sb.append(" currentState: none");
+            }
+            return sb.toString();
+        }
+
+        private boolean delta(long[] values1, long[] values2, long[] delta) {
+            if (delta.length != mArrayLength) {
+                throw new RuntimeException();
+            }
+
+            boolean is_delta_valid = true;
+            for (int i = 0; i < mArrayLength; i++) {
+                if (values2[i] >= values1[i]) {
+                    delta[i] = values2[i] - values1[i];
+                } else {
+                    delta[i] = 0;
+                    is_delta_valid = false;
+                }
+            }
+
+            return is_delta_valid;
+        }
+
+        private void add(long[] counter, long[] delta, long numerator, long denominator) {
+            if (numerator != denominator) {
+                for (int i = 0; i < mArrayLength; i++) {
+                    counter[i] += delta[i] * numerator / denominator;
+                }
+            } else {
+                for (int i = 0; i < mArrayLength; i++) {
+                    counter[i] += delta[i];
+                }
+            }
+        }
+    }
+
+    public static class LongArrayContainer_host {
+        private static final HashMap<Long, long[]> sInstances = new HashMap<>();
+        private static long sNextId = 1;
+
+        public static long native_init(int arrayLength) {
+            long[] array = new long[arrayLength];
+            long instanceId = sNextId++;
+            sInstances.put(instanceId, array);
+            return instanceId;
+        }
+
+        static long[] getInstance(long instanceId) {
+            return sInstances.get(instanceId);
+        }
+
+        public static void native_setValues(long instanceId, long[] values) {
+            System.arraycopy(values, 0, getInstance(instanceId), 0, values.length);
+        }
+
+        public static void native_getValues(long instanceId, long[] values) {
+            System.arraycopy(getInstance(instanceId), 0, values, 0, values.length);
+        }
+
+        public static boolean native_combineValues(long instanceId, long[] array, int[] indexMap) {
+            long[] values = getInstance(instanceId);
+
+            boolean nonZero = false;
+            Arrays.fill(array, 0);
+
+            for (int i = 0; i < values.length; i++) {
+                int index = indexMap[i];
+                if (index < 0 || index >= array.length) {
+                    throw new IndexOutOfBoundsException("Index " + index + " is out of bounds: [0, "
+                                                        + (array.length - 1) + "]");
+                }
+                if (values[i] != 0) {
+                    array[index] += values[i];
+                    nonZero = true;
+                }
+            }
+            return nonZero;
+        }
+    }
+
+    private static final HashMap<Long, LongArrayMultiStateCounterRavenwood> sInstances =
+            new HashMap<>();
+    private static long sNextId = 1;
+
+    public static long native_init(int stateCount, int arrayLength) {
+        LongArrayMultiStateCounterRavenwood instance = new LongArrayMultiStateCounterRavenwood(
+                stateCount, arrayLength);
+        long instanceId = sNextId++;
+        sInstances.put(instanceId, instance);
+        return instanceId;
+    }
+
+    private static LongArrayMultiStateCounterRavenwood getInstance(long instanceId) {
+        return sInstances.get(instanceId);
+    }
+
+    public static void native_setEnabled(long instanceId, boolean enabled,
+            long timestampMs) {
+        getInstance(instanceId).setEnabled(enabled, timestampMs);
+    }
+
+    public static int native_getStateCount(long instanceId) {
+        return getInstance(instanceId).mStateCount;
+    }
+
+    public static int native_getArrayLength(long instanceId) {
+        return getInstance(instanceId).mArrayLength;
+    }
+
+    public static void native_updateValues(long instanceId, long containerInstanceId,
+            long timestampMs) {
+        getInstance(instanceId).updateValue(
+                LongArrayContainer_host.getInstance(containerInstanceId), timestampMs);
+    }
+
+    public static void native_setState(long instanceId, int state, long timestampMs) {
+        getInstance(instanceId).setState(state, timestampMs);
+    }
+
+    public static void native_incrementValues(long instanceId, long containerInstanceId,
+            long timestampMs) {
+        getInstance(instanceId).incrementValues(
+                LongArrayContainer_host.getInstance(containerInstanceId), timestampMs);
+    }
+
+    public static void native_getCounts(long instanceId, long containerInstanceId, int state) {
+        getInstance(instanceId).getValues(LongArrayContainer_host.getInstance(containerInstanceId),
+                state);
+    }
+
+    public static void native_reset(long instanceId) {
+        getInstance(instanceId).reset();
+    }
+
+    public static void native_writeToParcel(long instanceId, Parcel parcel, int flags) {
+        getInstance(instanceId).writeToParcel(parcel);
+    }
+
+    public static long native_initFromParcel(Parcel parcel) {
+        int stateCount = parcel.readInt();
+        if (stateCount < 0 || stateCount > 0xEFFF) {
+            throw new BadParcelableException("stateCount out of range");
+        }
+        // LongArrayMultiStateCounter.cpp uses AParcel, which throws on out-of-data.
+        if (parcel.dataPosition() >= parcel.dataSize()) {
+            throw new RuntimeException("Bad parcel");
+        }
+        int arrayLength = parcel.readInt();
+        if (parcel.dataPosition() >= parcel.dataSize()) {
+            throw new RuntimeException("Bad parcel");
+        }
+        long instanceId = native_init(stateCount, arrayLength);
+        getInstance(instanceId).initFromParcel(parcel);
+        if (parcel.dataPosition() > parcel.dataSize()) {
+            throw new RuntimeException("Bad parcel");
+        }
+        return instanceId;
+    }
+
+    public static String native_toString(long instanceId) {
+        return getInstance(instanceId).toString();
+    }
+}
diff --git a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/AndroidHeuristicsFilter.kt b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/AndroidHeuristicsFilter.kt
index 8354d98..8ca4732 100644
--- a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/AndroidHeuristicsFilter.kt
+++ b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/AndroidHeuristicsFilter.kt
@@ -23,12 +23,16 @@
 class AndroidHeuristicsFilter(
         private val classes: ClassNodes,
         val aidlPolicy: FilterPolicyWithReason?,
+        val featureFlagsPolicy: FilterPolicyWithReason?,
         fallback: OutputFilter
 ) : DelegatingFilter(fallback) {
     override fun getPolicyForClass(className: String): FilterPolicyWithReason {
         if (aidlPolicy != null && classes.isAidlClass(className)) {
             return aidlPolicy
         }
+        if (featureFlagsPolicy != null && classes.isFeatureFlagsClass(className)) {
+            return featureFlagsPolicy
+        }
         return super.getPolicyForClass(className)
     }
 }
@@ -40,4 +44,16 @@
     return hasClass(className) &&
             hasClass("$className\$Stub") &&
             hasClass("$className\$Stub\$Proxy")
-}
\ No newline at end of file
+}
+
+/**
+ * @return if a given class "seems like" an feature flags class.
+ */
+private fun ClassNodes.isFeatureFlagsClass(className: String): Boolean {
+    // Matches template classes defined here:
+    // https://cs.android.com/android/platform/superproject/+/master:build/make/tools/aconfig/templates/
+    return className.endsWith("/Flags")
+            || className.endsWith("/FeatureFlags")
+            || className.endsWith("/FeatureFlagsImpl")
+            || className.endsWith("/FakeFeatureFlagsImpl");
+}
diff --git a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/TextFileFilterPolicyParser.kt b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/TextFileFilterPolicyParser.kt
index b4354ba..d38a6e3 100644
--- a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/TextFileFilterPolicyParser.kt
+++ b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/TextFileFilterPolicyParser.kt
@@ -62,7 +62,8 @@
 
         var lineNo = 0
 
-        var aidlPolicy: FilterPolicy? = null
+        var aidlPolicy: FilterPolicyWithReason? = null
+        var featureFlagsPolicy: FilterPolicyWithReason? = null
 
         try {
             BufferedReader(FileReader(filename)).use { reader ->
@@ -130,7 +131,15 @@
                                             throw ParseException(
                                                     "Policy for AIDL classes already defined")
                                         }
-                                        aidlPolicy = policy
+                                        aidlPolicy = policy.withReason("$FILTER_REASON (AIDL)")
+                                    }
+                                    SpecialClass.FeatureFlags -> {
+                                        if (featureFlagsPolicy != null) {
+                                            throw ParseException(
+                                                    "Policy for feature flags already defined")
+                                        }
+                                        featureFlagsPolicy =
+                                                policy.withReason("$FILTER_REASON (feature flags)")
                                     }
                                 }
                             }
@@ -196,10 +205,10 @@
         }
 
         var ret: OutputFilter = imf
-        aidlPolicy?.let { policy ->
+        if (aidlPolicy != null || featureFlagsPolicy != null) {
             log.d("AndroidHeuristicsFilter enabled")
             ret = AndroidHeuristicsFilter(
-                    classes, policy.withReason("$FILTER_REASON (AIDL)"), imf)
+                    classes, aidlPolicy, featureFlagsPolicy, imf)
         }
         return ret
     }
@@ -208,6 +217,7 @@
 private enum class SpecialClass {
     NotSpecial,
     Aidl,
+    FeatureFlags,
 }
 
 private fun resolveSpecialClass(className: String): SpecialClass {
@@ -216,6 +226,7 @@
     }
     when (className.lowercase()) {
         ":aidl" -> return SpecialClass.Aidl
+        ":feature_flags" -> return SpecialClass.FeatureFlags
     }
     throw ParseException("Invalid special class name \"$className\"")
 }