Merge "Update Special App Access Compose Components for ECM" into main
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..ad3e422 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);
                     }
                 }
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/core/api/current.txt b/core/api/current.txt
index d46d07e..a761674 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -4516,7 +4516,7 @@
     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);
@@ -9706,9 +9706,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
@@ -18787,6 +18787,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();
@@ -18939,6 +18940,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
@@ -39062,7 +39064,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 +39300,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 +39308,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 +39340,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 +39445,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 +39474,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);
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index a1465df..c64f230 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -4089,7 +4089,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
@@ -4195,12 +4196,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
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index 98a78cf..3f2376d 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
@@ -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);
   }
diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java
index c52d27ea..cb08dad 100644
--- a/core/java/android/app/Activity.java
+++ b/core/java/android/app/Activity.java
@@ -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/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/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/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..f865a36 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -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
diff --git a/core/java/android/content/pm/UserProperties.java b/core/java/android/content/pm/UserProperties.java
index 56e8291..c401268 100644
--- a/core/java/android/content/pm/UserProperties.java
+++ b/core/java/android/content/pm/UserProperties.java
@@ -69,6 +69,9 @@
     private static final String ATTR_DELETE_APP_WITH_PARENT = "deleteAppWithParent";
     private static final String ATTR_ALWAYS_VISIBLE = "alwaysVisible";
 
+    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 = {
             INDEX_SHOW_IN_LAUNCHER,
@@ -86,6 +89,7 @@
             INDEX_SHOW_IN_QUIET_MODE,
             INDEX_SHOW_IN_SHARING_SURFACES,
             INDEX_AUTH_ALWAYS_REQUIRED_TO_DISABLE_QUIET_MODE,
+            INDEX_CROSS_PROFILE_CONTENT_SHARING_STRATEGY
     })
     @Retention(RetentionPolicy.SOURCE)
     private @interface PropertyIndex {
@@ -105,6 +109,7 @@
     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;
     /** A bit set, mapping each PropertyIndex to whether it is present (1) or absent (0). */
     private long mPropertiesPresent = 0;
 
@@ -365,6 +370,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
@@ -423,6 +467,7 @@
         setCredentialShareableWithParent(orig.isCredentialShareableWithParent());
         setShowInQuietMode(orig.getShowInQuietMode());
         setShowInSharingSurfaces(orig.getShowInSharingSurfaces());
+        setCrossProfileContentSharingStrategy(orig.getCrossProfileContentSharingStrategy());
     }
 
     /**
@@ -776,8 +821,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 +836,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 +845,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() {
@@ -827,6 +901,7 @@
                 + isAuthAlwaysRequiredToDisableQuietMode()
                 + ", mDeleteAppWithParent=" + getDeleteAppWithParent()
                 + ", mAlwaysVisible=" + getAlwaysVisible()
+                + ", mCrossProfileContentSharingStrategy=" + getCrossProfileContentSharingStrategy()
                 + "}";
     }
 
@@ -856,6 +931,8 @@
                 + isAuthAlwaysRequiredToDisableQuietMode());
         pw.println(prefix + "    mDeleteAppWithParent=" + getDeleteAppWithParent());
         pw.println(prefix + "    mAlwaysVisible=" + getAlwaysVisible());
+        pw.println(prefix + "    mCrossProfileContentSharingStrategy="
+                + getCrossProfileContentSharingStrategy());
     }
 
     /**
@@ -934,6 +1011,8 @@
                 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);
             }
@@ -1008,6 +1087,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.
@@ -1029,6 +1112,7 @@
         dest.writeBoolean(mAuthAlwaysRequiredToDisableQuietMode);
         dest.writeBoolean(mDeleteAppWithParent);
         dest.writeBoolean(mAlwaysVisible);
+        dest.writeInt(mCrossProfileContentSharingStrategy);
     }
 
     /**
@@ -1054,6 +1138,7 @@
         mAuthAlwaysRequiredToDisableQuietMode = source.readBoolean();
         mDeleteAppWithParent = source.readBoolean();
         mAlwaysVisible = source.readBoolean();
+        mCrossProfileContentSharingStrategy = source.readInt();
     }
 
     @Override
@@ -1100,6 +1185,8 @@
         private boolean mAuthAlwaysRequiredToDisableQuietMode = false;
         private boolean mDeleteAppWithParent = false;
         private boolean mAlwaysVisible = false;
+        private @CrossProfileContentSharingStrategy int mCrossProfileContentSharingStrategy =
+                CROSS_PROFILE_CONTENT_SHARING_NO_DELEGATION;
 
         /**
          * @hide
@@ -1231,6 +1318,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
          */
@@ -1253,7 +1353,8 @@
                     mCredentialShareableWithParent,
                     mAuthAlwaysRequiredToDisableQuietMode,
                     mDeleteAppWithParent,
-                    mAlwaysVisible);
+                    mAlwaysVisible,
+                    mCrossProfileContentSharingStrategy);
         }
     } // end Builder
 
@@ -1272,7 +1373,8 @@
             boolean credentialShareableWithParent,
             boolean authAlwaysRequiredToDisableQuietMode,
             boolean deleteAppWithParent,
-            boolean alwaysVisible) {
+            boolean alwaysVisible,
+            @CrossProfileContentSharingStrategy int crossProfileContentSharingStrategy) {
         mDefaultProperties = null;
         setShowInLauncher(showInLauncher);
         setStartWithParent(startWithParent);
@@ -1290,5 +1392,6 @@
                 authAlwaysRequiredToDisableQuietMode);
         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..dec5b9c 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>
      *
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/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/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/Process.java b/core/java/android/os/Process.java
index 7e07e1f..fc8523e 100644
--- a/core/java/android/os/Process.java
+++ b/core/java/android/os/Process.java
@@ -1599,7 +1599,7 @@
      * fully removed, otherwise system resources may leak.
      * @hide
      */
-    public static final native int sendSignalToProcessGroup(int uid, int pid, int signal);
+    public static final native boolean sendSignalToProcessGroup(int uid, int pid, int signal);
 
     /**
       * Freeze the cgroup for the given UID.
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/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/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/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index 1530aa7..7134529 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -3216,6 +3216,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 +3931,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;
@@ -12036,7 +12047,7 @@
         try {
             if (mLastPreferredFrameRateCategory != frameRateCategory) {
                 mFrameRateTransaction.setFrameRateCategory(mSurfaceControl,
-                    frameRateCategory, false).applyAsyncUnsafe();
+                        frameRateCategory, false).applyAsyncUnsafe();
                 mLastPreferredFrameRateCategory = frameRateCategory;
             }
         } catch (Exception e) {
@@ -12159,6 +12170,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/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/jni/android_util_Process.cpp b/core/jni/android_util_Process.cpp
index 91dfc60..43e0c34 100644
--- a/core/jni/android_util_Process.cpp
+++ b/core/jni/android_util_Process.cpp
@@ -1238,7 +1238,7 @@
     return killProcessGroup(uid, pid, SIGKILL);
 }
 
-jint android_os_Process_sendSignalToProcessGroup(JNIEnv* env, jobject clazz, jint uid, jint pid,
+jboolean android_os_Process_sendSignalToProcessGroup(JNIEnv* env, jobject clazz, jint uid, jint pid,
                                                  jint signal) {
     return sendSignalToProcessGroup(uid, pid, signal);
 }
@@ -1310,7 +1310,7 @@
         //{"setApplicationObject", "(Landroid/os/IBinder;)V",
         //(void*)android_os_Process_setApplicationObject},
         {"killProcessGroup", "(II)I", (void*)android_os_Process_killProcessGroup},
-        {"sendSignalToProcessGroup", "(III)I", (void*)android_os_Process_sendSignalToProcessGroup},
+        {"sendSignalToProcessGroup", "(III)Z", (void*)android_os_Process_sendSignalToProcessGroup},
         {"removeAllProcessGroups", "()V", (void*)android_os_Process_removeAllProcessGroups},
         {"nativePidFdOpen", "(II)I", (void*)android_os_Process_nativePidFdOpen},
         {"freezeCgroupUid", "(IZ)V", (void*)android_os_Process_freezeCgroupUID},
diff --git a/core/tests/coretests/Android.bp b/core/tests/coretests/Android.bp
index 0ad349b..88abaa1 100644
--- a/core/tests/coretests/Android.bp
+++ b/core/tests/coretests/Android.bp
@@ -179,8 +179,10 @@
         "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/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/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/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/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/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/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/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/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/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/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/systemui/authentication/domain/interactor/AuthenticationInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractorTest.kt
index d968c1b..2a02164 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
@@ -27,8 +27,6 @@
 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
@@ -315,6 +313,59 @@
         }
 
     @Test
+    fun isAutoConfirmEnabled_featureDisabled_returnsFalse() =
+        testScope.runTest {
+            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_featureEnabledButDisabledByThrottling() =
+        testScope.runTest {
+            val isAutoConfirmEnabled by collectLastValue(underTest.isAutoConfirmEnabled)
+            val throttling by collectLastValue(underTest.throttling)
+            utils.authenticationRepository.setAutoConfirmFeatureEnabled(true)
+
+            // The feature is enabled.
+            assertThat(isAutoConfirmEnabled).isTrue()
+
+            // Make many wrong attempts to trigger throttling.
+            repeat(FakeAuthenticationRepository.MAX_FAILED_AUTH_TRIES_BEFORE_THROTTLING) {
+                underTest.authenticate(listOf(5, 6, 7)) // Wrong PIN
+            }
+            assertThat(throttling).isNotNull()
+
+            // Throttling disabled auto-confirm.
+            assertThat(isAutoConfirmEnabled).isFalse()
+
+            // Move the clock forward one more second, to completely finish the throttling period:
+            advanceTimeBy(FakeAuthenticationRepository.THROTTLE_DURATION_MS + 1000L)
+            assertThat(throttling).isNull()
+
+            // Auto-confirm is still disabled, because throttling 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()
+        }
+
+    @Test
     fun throttling() =
         testScope.runTest {
             val throttling by collectLastValue(underTest.throttling)
@@ -335,7 +386,7 @@
                     AuthenticationThrottlingModel(
                         failedAttemptCount =
                             FakeAuthenticationRepository.MAX_FAILED_AUTH_TRIES_BEFORE_THROTTLING,
-                        remainingMs = FakeAuthenticationRepository.THROTTLE_DURATION_MS,
+                        remainingSeconds = FakeAuthenticationRepository.THROTTLE_DURATION_SECONDS,
                     )
                 )
 
@@ -347,15 +398,13 @@
                     AuthenticationThrottlingModel(
                         failedAttemptCount =
                             FakeAuthenticationRepository.MAX_FAILED_AUTH_TRIES_BEFORE_THROTTLING,
-                        remainingMs = FakeAuthenticationRepository.THROTTLE_DURATION_MS,
+                        remainingSeconds = FakeAuthenticationRepository.THROTTLE_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 ->
+            val throttleTimeoutSec = FakeAuthenticationRepository.THROTTLE_DURATION_SECONDS
+            repeat(FakeAuthenticationRepository.THROTTLE_DURATION_SECONDS - 1) { time ->
                 advanceTimeBy(1000)
                 assertThat(throttling)
                     .isEqualTo(
@@ -363,9 +412,7 @@
                             failedAttemptCount =
                                 FakeAuthenticationRepository
                                     .MAX_FAILED_AUTH_TRIES_BEFORE_THROTTLING,
-                            remainingMs =
-                                ((throttleTimeoutSec - (time + 1)).seconds.inWholeMilliseconds)
-                                    .toInt(),
+                            remainingSeconds = throttleTimeoutSec - (time + 1),
                         )
                     )
             }
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..3e3a1a9 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
@@ -29,7 +29,6 @@
 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
@@ -268,7 +267,7 @@
                     AuthenticationThrottlingModel(
                         failedAttemptCount =
                             FakeAuthenticationRepository.MAX_FAILED_AUTH_TRIES_BEFORE_THROTTLING,
-                        remainingMs = FakeAuthenticationRepository.THROTTLE_DURATION_MS,
+                        remainingSeconds = FakeAuthenticationRepository.THROTTLE_DURATION_SECONDS,
                     )
                 )
             assertTryAgainMessage(
@@ -286,8 +285,7 @@
                     .toInt()
             )
 
-            throttling?.remainingMs?.let { remainingMs ->
-                val seconds = ceil(remainingMs / 1000f).toInt()
+            throttling?.remainingSeconds?.let { seconds ->
                 repeat(seconds) { time ->
                     advanceTimeBy(1000)
                     val remainingTimeSec = seconds - time - 1
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..2b64d8e 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
@@ -144,7 +145,9 @@
             }
             assertThat(message?.isUpdateAnimated).isFalse()
 
-            throttling?.remainingMs?.let { remainingMs -> advanceTimeBy(remainingMs.toLong()) }
+            throttling?.remainingSeconds?.let { remainingSeconds ->
+                advanceTimeBy(remainingSeconds.seconds.inWholeMilliseconds)
+            }
             assertThat(message?.isUpdateAnimated).isTrue()
         }
 
@@ -167,7 +170,9 @@
             }
             assertThat(isInputEnabled).isFalse()
 
-            throttling?.remainingMs?.let { milliseconds -> advanceTimeBy(milliseconds.toLong()) }
+            throttling?.remainingSeconds?.let { remainingSeconds ->
+                advanceTimeBy(remainingSeconds.seconds.inWholeMilliseconds)
+            }
             assertThat(isInputEnabled).isTrue()
         }
 
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..a217d93 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
@@ -335,12 +335,12 @@
             repeat(failedAttemptCount) {
                 authenticationRepository.reportAuthenticationAttempt(false)
             }
-            val remainingTimeMs = 30_000
-            authenticationRepository.setThrottleDuration(remainingTimeMs)
+            val remainingTimeSeconds = 30
+            authenticationRepository.setThrottleDuration(remainingTimeSeconds * 1000)
             authenticationRepository.throttling.value =
                 AuthenticationThrottlingModel(
                     failedAttemptCount = failedAttemptCount,
-                    remainingMs = remainingTimeMs,
+                    remainingSeconds = remainingTimeSeconds,
                 )
         } else {
             authenticationRepository.reportAuthenticationAttempt(true)
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/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/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..28f3e93 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)
             }
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/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/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..bd84b28 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
@@ -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.
      */
@@ -93,6 +85,17 @@
      */
     val throttling: MutableStateFlow<AuthenticationThrottlingModel?>
 
+    /** Whether throttling has occurred at least once since the last successful authentication. */
+    val hasThrottlingOccurred: 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
      * challenge needs to be completed in order to unlock an otherwise locked device.
@@ -172,11 +175,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
@@ -190,8 +188,13 @@
     override val throttling: MutableStateFlow<AuthenticationThrottlingModel?> =
         MutableStateFlow(null)
 
-    private val selectedUserId: Int
-        get() = userRepository.getSelectedUserInfo().id
+    override val hasThrottlingOccurred: 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) {
@@ -280,6 +283,9 @@
         }
     }
 
+    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..7f8f887 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
@@ -29,6 +29,7 @@
 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
@@ -94,15 +95,14 @@
      *
      * 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.hasThrottlingOccurred) {
                 featureEnabled,
-                throttling ->
-                // Disable auto-confirm during throttling.
-                featureEnabled && throttling == null
+                hasThrottlingOccurred ->
+                // Disable auto-confirm if throttling occurred since the last successful
+                // authentication attempt.
+                featureEnabled && !hasThrottlingOccurred
             }
             .stateIn(
                 scope = applicationScope,
@@ -220,6 +220,7 @@
             repository.setThrottleDuration(
                 durationMs = authenticationResult.throttleDurationMs,
             )
+            repository.hasThrottlingOccurred.value = true
             startThrottlingCountdown()
         }
 
@@ -227,6 +228,8 @@
             // Since authentication succeeded, we should refresh throttling to make sure that our
             // state is completely reflecting the upstream source of truth.
             refreshThrottling()
+
+            repository.hasThrottlingOccurred.value = false
         }
 
         return if (authenticationResult.isSuccessful) {
@@ -283,7 +286,7 @@
                 if (remainingMs > 0) {
                     AuthenticationThrottlingModel(
                         failedAttemptCount = failedAttemptCount.await(),
-                        remainingMs = remainingMs.toInt(),
+                        remainingSeconds = ceil(remainingMs / 1000f).toInt(),
                     )
                 } else {
                     null // Throttling ended.
diff --git a/packages/SystemUI/src/com/android/systemui/authentication/shared/model/AuthenticationThrottlingModel.kt b/packages/SystemUI/src/com/android/systemui/authentication/shared/model/AuthenticationThrottlingModel.kt
index d0d398e..8392528 100644
--- a/packages/SystemUI/src/com/android/systemui/authentication/shared/model/AuthenticationThrottlingModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/authentication/shared/model/AuthenticationThrottlingModel.kt
@@ -23,10 +23,13 @@
     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 throttling this will be `0`.
      *
      * This number is changed throughout the timeout.
+     *
+     * 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/bouncer/domain/interactor/BouncerInteractor.kt b/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractor.kt
index 1122877..677f60d 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
@@ -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
@@ -259,7 +258,7 @@
             throttlingModel != null ->
                 applicationContext.getString(
                     com.android.internal.R.string.lockscreen_too_many_failed_attempts_countdown,
-                    throttlingModel.remainingMs.milliseconds.inWholeSeconds,
+                    throttlingModel.remainingSeconds,
                 )
             message != null -> message
             else -> ""
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..fefc3e3 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
@@ -205,7 +204,7 @@
                             applicationContext.getString(
                                 authMethodViewModel.throttlingMessageId,
                                 throttling.failedAttemptCount,
-                                ceil(throttling.remainingMs / 1000f).toInt(),
+                                throttling.remainingSeconds,
                             )
                         } else {
                             null
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/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/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/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/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/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/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/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/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/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/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/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/domain/interactor/KeyguardTransitionScenariosTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt
index bf6d5c4..976dc5f 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
@@ -1329,12 +1328,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 +1345,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/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/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..85b9392 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
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/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/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..4642b47 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
@@ -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
@@ -53,6 +50,12 @@
     override val throttling: MutableStateFlow<AuthenticationThrottlingModel?> =
         MutableStateFlow(null)
 
+    override val hasThrottlingOccurred = MutableStateFlow(false)
+
+    private val _isAutoConfirmFeatureEnabled = MutableStateFlow(false)
+    override val isAutoConfirmFeatureEnabled: StateFlow<Boolean> =
+        _isAutoConfirmFeatureEnabled.asStateFlow()
+
     private val _authenticationMethod =
         MutableStateFlow<AuthenticationMethodModel>(DEFAULT_AUTHENTICATION_METHOD)
     override val authenticationMethod: StateFlow<AuthenticationMethodModel> =
@@ -107,6 +110,9 @@
 
     override suspend fun setThrottleDuration(durationMs: Int) {
         throttlingEndTimestamp = if (durationMs > 0) currentTime() + durationMs else 0
+        if (durationMs > 0) {
+            hasThrottlingOccurred.value = true
+        }
     }
 
     override suspend fun checkCredential(
@@ -128,6 +134,7 @@
         return if (
             isSuccessful || failedAttemptCount < MAX_FAILED_AUTH_TRIES_BEFORE_THROTTLING - 1
         ) {
+            hasThrottlingOccurred.value = false
             AuthenticationResultModel(
                 isSuccessful = isSuccessful,
                 throttleDurationMs = 0,
@@ -166,7 +173,8 @@
                 AuthenticationPatternCoordinate(0, 2),
             )
         const val MAX_FAILED_AUTH_TRIES_BEFORE_THROTTLING = 5
-        const val THROTTLE_DURATION_MS = 30000
+        const val THROTTLE_DURATION_SECONDS = 30
+        const val THROTTLE_DURATION_MS = THROTTLE_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/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%
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/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%
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/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..e3f1932 100644
--- a/ravenwood/ravenwood-annotation-allowed-classes.txt
+++ b/ravenwood/ravenwood-annotation-allowed-classes.txt
@@ -88,6 +88,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/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/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/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/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/AudioDeviceInventory.java b/services/core/java/com/android/server/audio/AudioDeviceInventory.java
index e54bf64..d077ebc 100644
--- a/services/core/java/com/android/server/audio/AudioDeviceInventory.java
+++ b/services/core/java/com/android/server/audio/AudioDeviceInventory.java
@@ -1501,7 +1501,7 @@
             } else {
                 status = addOp.deviceRoleAction(useCase, role, devices);
                 if (status == AudioSystem.SUCCESS) {
-                    rolesMap.put(key, devices);
+                    rolesMap.put(key, new ArrayList(devices));
                 }
             }
             return status;
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/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/pm/UserTypeFactory.java b/services/core/java/com/android/server/pm/UserTypeFactory.java
index 4ef8cb7..7f013b8 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));
     }
 
     /**
@@ -318,7 +320,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/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/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/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/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/BackgroundActivityStartController.java b/services/core/java/com/android/server/wm/BackgroundActivityStartController.java
index f8b22c9..07dac54 100644
--- a/services/core/java/com/android/server/wm/BackgroundActivityStartController.java
+++ b/services/core/java/com/android/server/wm/BackgroundActivityStartController.java
@@ -443,6 +443,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 +474,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 +506,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;
         }
     }
@@ -580,7 +599,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 +848,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 +859,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 +880,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 +888,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 +1489,7 @@
                     intent != null ? intent.getComponent().flattenToShortString() : "";
             FrameworkStatsLog.write(FrameworkStatsLog.BAL_ALLOWED,
                     activityName,
-                    code,
+                    BAL_ALLOW_PENDING_INTENT,
                     callingUid,
                     realCallingUid);
         }
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/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/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..9b047f2 100644
--- a/services/tests/servicestests/res/xml/usertypes_test_profile.xml
+++ b/services/tests/servicestests/res/xml/usertypes_test_profile.xml
@@ -45,6 +45,7 @@
             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/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..72cc969 100644
--- a/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceUserPropertiesTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceUserPropertiesTest.java
@@ -71,6 +71,7 @@
                 .setAuthAlwaysRequiredToDisableQuietMode(false)
                 .setDeleteAppWithParent(false)
                 .setAlwaysVisible(false)
+                .setCrossProfileContentSharingStrategy(0)
                 .build();
         final UserProperties actualProps = new UserProperties(defaultProps);
         actualProps.setShowInLauncher(14);
@@ -86,6 +87,7 @@
         actualProps.setAuthAlwaysRequiredToDisableQuietMode(true);
         actualProps.setDeleteAppWithParent(true);
         actualProps.setAlwaysVisible(true);
+        actualProps.setCrossProfileContentSharingStrategy(1);
 
         // Write the properties to xml.
         final ByteArrayOutputStream baos = new ByteArrayOutputStream();
@@ -199,6 +201,8 @@
                 copy::isMediaSharedWithParent, true);
         assertEqualGetterOrThrows(orig::isCredentialShareableWithParent,
                 copy::isCredentialShareableWithParent, true);
+        assertEqualGetterOrThrows(orig::getCrossProfileContentSharingStrategy,
+                copy::getCrossProfileContentSharingStrategy, true);
     }
 
     /**
@@ -256,5 +260,7 @@
                 .isEqualTo(actual.isAuthAlwaysRequiredToDisableQuietMode());
         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..d0ad573 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;
@@ -96,7 +95,8 @@
                 .setShowInQuietMode(30)
                 .setInheritDevicePolicy(340)
                 .setDeleteAppWithParent(true)
-                .setAlwaysVisible(true);
+                .setAlwaysVisible(true)
+                .setCrossProfileContentSharingStrategy(1);
 
         final UserTypeDetails type = new UserTypeDetails.Builder()
                 .setName("a.name")
@@ -175,6 +175,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 +233,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());
     }
@@ -323,7 +327,8 @@
                 .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()
@@ -370,6 +375,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();
@@ -422,6 +429,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..ced0bb5 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,6 +339,8 @@
         assertThat(typeProps.isAuthAlwaysRequiredToDisableQuietMode())
                 .isEqualTo(privateProfileUserProperties
                         .isAuthAlwaysRequiredToDisableQuietMode());
+        assertThat(typeProps.getCrossProfileContentSharingStrategy())
+                .isEqualTo(privateProfileUserProperties.getCrossProfileContentSharingStrategy());
         assertThrows(SecurityException.class, privateProfileUserProperties::getDeleteAppWithParent);
         compareDrawables(mUserManager.getUserBadge(),
                 Resources.getSystem().getDrawable(userTypeDetails.getBadgePlain()));
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/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/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\"")
 }