Merge "Revamp BinaryTransparencyService."
diff --git a/.clang-format b/.clang-format
index 03af56d..d60d33c 100644
--- a/.clang-format
+++ b/.clang-format
@@ -9,5 +9,17 @@
 ConstructorInitializerIndentWidth: 6
 ContinuationIndentWidth: 8
 IndentWidth: 4
+JavaImportGroups:
+- android
+- androidx
+- com.android
+- dalvik
+- libcore
+- com
+- junit
+- net
+- org
+- java
+- javax
 PenaltyBreakBeforeFirstCallParameter: 100000
 SpacesBeforeTrailingComments: 1
diff --git a/apct-tests/perftests/core/Android.bp b/apct-tests/perftests/core/Android.bp
index 98e4f45..9366ff2d 100644
--- a/apct-tests/perftests/core/Android.bp
+++ b/apct-tests/perftests/core/Android.bp
@@ -62,4 +62,10 @@
 
     test_suites: ["device-tests"],
     certificate: "platform",
+
+    errorprone: {
+        javacflags: [
+            "-Xep:ReturnValueIgnored:WARN",
+        ],
+    },
 }
diff --git a/apct-tests/perftests/core/src/android/libcore/ReferencePerfTest.java b/apct-tests/perftests/core/src/android/libcore/ReferencePerfTest.java
index 2ef68ca..05a3e12 100644
--- a/apct-tests/perftests/core/src/android/libcore/ReferencePerfTest.java
+++ b/apct-tests/perftests/core/src/android/libcore/ReferencePerfTest.java
@@ -118,7 +118,7 @@
         int got = count.get();
         if (n != got) {
             throw new IllegalStateException(
-                    String.format("Only %i of %i objects finalized?", got, n));
+                    String.format("Only %d of %d objects finalized?", got, n));
         }
     }
 }
diff --git a/apct-tests/perftests/core/src/android/os/DisplayPerfTest.java b/apct-tests/perftests/core/src/android/os/DisplayPerfTest.java
index 0802072..0cce6ad 100644
--- a/apct-tests/perftests/core/src/android/os/DisplayPerfTest.java
+++ b/apct-tests/perftests/core/src/android/os/DisplayPerfTest.java
@@ -18,11 +18,11 @@
 
 import android.content.Context;
 import android.hardware.display.DisplayManager;
+import android.perftests.utils.BenchmarkState;
+import android.perftests.utils.PerfStatusReporter;
 import android.provider.Settings;
 import android.view.Display;
 
-import androidx.benchmark.BenchmarkState;
-import androidx.benchmark.junit4.BenchmarkRule;
 import androidx.test.platform.app.InstrumentationRegistry;
 import androidx.test.runner.AndroidJUnit4;
 
@@ -38,7 +38,7 @@
     private static final float DELTA = 0.001f;
 
     @Rule
-    public final BenchmarkRule mBenchmarkRule = new BenchmarkRule();
+    public final PerfStatusReporter mPerfStatusReporter = new PerfStatusReporter();
 
     private DisplayManager mDisplayManager;
     private Context mContext;
@@ -51,7 +51,7 @@
 
     @Test
     public void testBrightnessChanges() throws Exception {
-        final BenchmarkState state = mBenchmarkRule.getState();
+        final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
         Settings.System.putInt(mContext.getContentResolver(),
                 Settings.System.SCREEN_BRIGHTNESS_MODE,
                 Settings.System.SCREEN_BRIGHTNESS_MODE_MANUAL);
diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobConcurrencyManager.java b/apex/jobscheduler/service/java/com/android/server/job/JobConcurrencyManager.java
index 53f5c6e..f9dd0b3 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobConcurrencyManager.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobConcurrencyManager.java
@@ -327,6 +327,7 @@
     private final ArraySet<ContextAssignment> mRecycledIdle = new ArraySet<>();
     private final ArrayList<ContextAssignment> mRecycledPreferredUidOnly = new ArrayList<>();
     private final ArrayList<ContextAssignment> mRecycledStoppable = new ArrayList<>();
+    private final AssignmentInfo mRecycledAssignmentInfo = new AssignmentInfo();
 
     private final Pools.Pool<ContextAssignment> mContextAssignmentPool =
             new Pools.SimplePool<>(MAX_RETAINED_OBJECTS);
@@ -414,7 +415,7 @@
     @VisibleForTesting
     JobConcurrencyManager(JobSchedulerService service, Injector injector) {
         mService = service;
-        mLock = mService.mLock;
+        mLock = mService.getLock();
         mContext = service.getTestableContext();
         mInjector = injector;
 
@@ -693,8 +694,9 @@
             return;
         }
 
-        final long minPreferredUidOnlyWaitingTimeMs = prepareForAssignmentDeterminationLocked(
-                mRecycledIdle, mRecycledPreferredUidOnly, mRecycledStoppable);
+        prepareForAssignmentDeterminationLocked(
+                mRecycledIdle, mRecycledPreferredUidOnly, mRecycledStoppable,
+                mRecycledAssignmentInfo);
 
         if (DEBUG) {
             Slog.d(TAG, printAssignments("running jobs initial",
@@ -703,7 +705,7 @@
 
         determineAssignmentsLocked(
                 mRecycledChanged, mRecycledIdle, mRecycledPreferredUidOnly, mRecycledStoppable,
-                minPreferredUidOnlyWaitingTimeMs);
+                mRecycledAssignmentInfo);
 
         if (DEBUG) {
             Slog.d(TAG, printAssignments("running jobs final",
@@ -715,17 +717,18 @@
         carryOutAssignmentChangesLocked(mRecycledChanged);
 
         cleanUpAfterAssignmentChangesLocked(
-                mRecycledChanged, mRecycledIdle, mRecycledPreferredUidOnly, mRecycledStoppable);
+                mRecycledChanged, mRecycledIdle, mRecycledPreferredUidOnly, mRecycledStoppable,
+                mRecycledAssignmentInfo);
 
         noteConcurrency();
     }
 
-    /** @return the minimum remaining execution time for preferred UID only JobServiceContexts. */
     @VisibleForTesting
     @GuardedBy("mLock")
-    long prepareForAssignmentDeterminationLocked(final ArraySet<ContextAssignment> idle,
+    void prepareForAssignmentDeterminationLocked(final ArraySet<ContextAssignment> idle,
             final List<ContextAssignment> preferredUidOnly,
-            final List<ContextAssignment> stoppable) {
+            final List<ContextAssignment> stoppable,
+            final AssignmentInfo info) {
         final PendingJobQueue pendingJobQueue = mService.getPendingJobQueue();
         final List<JobServiceContext> activeServices = mActiveServices;
 
@@ -755,6 +758,9 @@
             if (js != null) {
                 mWorkCountTracker.incrementRunningJobCount(jsc.getRunningJobWorkType());
                 assignment.workType = jsc.getRunningJobWorkType();
+                if (js.startedAsExpeditedJob && js.lastEvaluatedBias == JobInfo.BIAS_TOP_APP) {
+                    info.numRunningTopEj++;
+                }
             }
 
             assignment.preferredUid = jsc.getPreferredUid();
@@ -789,10 +795,11 @@
         }
 
         mWorkCountTracker.onCountDone();
-        // Return 0 if there were no preferred UID only contexts to indicate no waiting time due
+        // Set 0 if there were no preferred UID only contexts to indicate no waiting time due
         // to such jobs.
-        return minPreferredUidOnlyWaitingTimeMs == Long.MAX_VALUE
-                ? 0 : minPreferredUidOnlyWaitingTimeMs;
+        info.minPreferredUidOnlyWaitingTimeMs =
+                minPreferredUidOnlyWaitingTimeMs == Long.MAX_VALUE
+                        ? 0 : minPreferredUidOnlyWaitingTimeMs;
     }
 
     @VisibleForTesting
@@ -801,7 +808,7 @@
             final ArraySet<ContextAssignment> idle,
             final List<ContextAssignment> preferredUidOnly,
             final List<ContextAssignment> stoppable,
-            long minPreferredUidOnlyWaitingTimeMs) {
+            @NonNull AssignmentInfo info) {
         final PendingJobQueue pendingJobQueue = mService.getPendingJobQueue();
         final List<JobServiceContext> activeServices = mActiveServices;
         pendingJobQueue.resetIterator();
@@ -832,7 +839,7 @@
             // pending jobs that could be designated as waiting too long, and those other jobs
             // would only have to wait for the new slots to become available.
             final long minWaitingTimeMs =
-                    Math.min(minPreferredUidOnlyWaitingTimeMs, minChangedWaitingTimeMs);
+                    Math.min(info.minPreferredUidOnlyWaitingTimeMs, minChangedWaitingTimeMs);
 
             // Find an available slot for nextPending. The context should be one of the following:
             // 1. Unused
@@ -861,13 +868,6 @@
                 }
             }
             if (selectedContext == null && stoppable.size() > 0) {
-                int topEjCount = 0;
-                for (int r = mRunningJobs.size() - 1; r >= 0; --r) {
-                    JobStatus js = mRunningJobs.valueAt(r);
-                    if (js.startedAsExpeditedJob && js.lastEvaluatedBias == JobInfo.BIAS_TOP_APP) {
-                        topEjCount++;
-                    }
-                }
                 for (int s = stoppable.size() - 1; s >= 0; --s) {
                     final ContextAssignment assignment = stoppable.get(s);
                     final JobStatus runningJob = assignment.context.getRunningJobLocked();
@@ -888,7 +888,8 @@
                         final int currentJobBias = mService.evaluateJobBiasLocked(runningJob);
                         canReplace = runningJob.lastEvaluatedBias < JobInfo.BIAS_TOP_APP // Case 2
                                 || currentJobBias < JobInfo.BIAS_TOP_APP // Case 3
-                                || topEjCount > .5 * mWorkTypeConfig.getMaxTotal(); // Case 4
+                                // Case 4
+                                || info.numRunningTopEj > .5 * mWorkTypeConfig.getMaxTotal();
                     }
                     if (!canReplace && mMaxWaitTimeBypassEnabled) { // Case 5
                         if (nextPending.shouldTreatAsExpeditedJob()) {
@@ -955,7 +956,7 @@
                 if (selectedContext != null) {
                     selectedContext.newJob = nextPending;
                     preferredUidOnly.remove(selectedContext);
-                    minPreferredUidOnlyWaitingTimeMs = newMinPreferredUidOnlyWaitingTimeMs;
+                    info.minPreferredUidOnlyWaitingTimeMs = newMinPreferredUidOnlyWaitingTimeMs;
                 }
             }
             // Make sure to run EJs for the TOP app immediately.
@@ -1072,7 +1073,8 @@
     private void cleanUpAfterAssignmentChangesLocked(final ArraySet<ContextAssignment> changed,
             final ArraySet<ContextAssignment> idle,
             final List<ContextAssignment> preferredUidOnly,
-            final List<ContextAssignment> stoppable) {
+            final List<ContextAssignment> stoppable,
+            final AssignmentInfo assignmentInfo) {
         for (int s = stoppable.size() - 1; s >= 0; --s) {
             final ContextAssignment assignment = stoppable.get(s);
             assignment.clear();
@@ -1093,6 +1095,7 @@
         idle.clear();
         stoppable.clear();
         preferredUidOnly.clear();
+        assignmentInfo.clear();
         mWorkCountTracker.resetStagingCount();
         mActivePkgStats.forEach(mPackageStatsStagingCountClearer);
     }
@@ -2540,6 +2543,17 @@
         }
     }
 
+    @VisibleForTesting
+    static final class AssignmentInfo {
+        public long minPreferredUidOnlyWaitingTimeMs;
+        public int numRunningTopEj;
+
+        void clear() {
+            minPreferredUidOnlyWaitingTimeMs = 0;
+            numRunningTopEj = 0;
+        }
+    }
+
     // TESTING HELPERS
 
     @VisibleForTesting
diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
index d9fe30d..e0d1a30 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
@@ -569,7 +569,7 @@
         public static final long DEFAULT_RUNTIME_MIN_EJ_GUARANTEE_MS = 3 * MINUTE_IN_MILLIS;
         @VisibleForTesting
         static final long DEFAULT_RUNTIME_MIN_HIGH_PRIORITY_GUARANTEE_MS = 5 * MINUTE_IN_MILLIS;
-        static final boolean DEFAULT_PERSIST_IN_SPLIT_FILES = false;
+        static final boolean DEFAULT_PERSIST_IN_SPLIT_FILES = true;
         private static final boolean DEFAULT_USE_TARE_POLICY = false;
 
         /**
diff --git a/boot/Android.bp b/boot/Android.bp
index 7839918..6e52914 100644
--- a/boot/Android.bp
+++ b/boot/Android.bp
@@ -136,6 +136,10 @@
             apex: "com.android.car.framework",
             module: "com.android.car.framework-bootclasspath-fragment",
         },
+        {
+            apex: "com.android.virt",
+            module: "com.android.virt-bootclasspath-fragment",
+        },
     ],
 
     // Additional information needed by hidden api processing.
diff --git a/core/api/current.txt b/core/api/current.txt
index a3738f7..07ced0e 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -91,6 +91,18 @@
     field public static final String EXPAND_STATUS_BAR = "android.permission.EXPAND_STATUS_BAR";
     field public static final String FACTORY_TEST = "android.permission.FACTORY_TEST";
     field public static final String FOREGROUND_SERVICE = "android.permission.FOREGROUND_SERVICE";
+    field public static final String FOREGROUND_SERVICE_CAMERA = "android.permission.FOREGROUND_SERVICE_CAMERA";
+    field public static final String FOREGROUND_SERVICE_CONNECTED_DEVICE = "android.permission.FOREGROUND_SERVICE_CONNECTED_DEVICE";
+    field public static final String FOREGROUND_SERVICE_DATA_SYNC = "android.permission.FOREGROUND_SERVICE_DATA_SYNC";
+    field public static final String FOREGROUND_SERVICE_HEALTH = "android.permission.FOREGROUND_SERVICE_HEALTH";
+    field public static final String FOREGROUND_SERVICE_LOCATION = "android.permission.FOREGROUND_SERVICE_LOCATION";
+    field public static final String FOREGROUND_SERVICE_MEDIA_PLAYBACK = "android.permission.FOREGROUND_SERVICE_MEDIA_PLAYBACK";
+    field public static final String FOREGROUND_SERVICE_MEDIA_PROJECTION = "android.permission.FOREGROUND_SERVICE_MEDIA_PROJECTION";
+    field public static final String FOREGROUND_SERVICE_MICROPHONE = "android.permission.FOREGROUND_SERVICE_MICROPHONE";
+    field public static final String FOREGROUND_SERVICE_PHONE_CALL = "android.permission.FOREGROUND_SERVICE_PHONE_CALL";
+    field public static final String FOREGROUND_SERVICE_REMOTE_MESSAGING = "android.permission.FOREGROUND_SERVICE_REMOTE_MESSAGING";
+    field public static final String FOREGROUND_SERVICE_SPECIAL_USE = "android.permission.FOREGROUND_SERVICE_SPECIAL_USE";
+    field public static final String FOREGROUND_SERVICE_SYSTEM_EXEMPTED = "android.permission.FOREGROUND_SERVICE_SYSTEM_EXEMPTED";
     field public static final String GET_ACCOUNTS = "android.permission.GET_ACCOUNTS";
     field public static final String GET_ACCOUNTS_PRIVILEGED = "android.permission.GET_ACCOUNTS_PRIVILEGED";
     field public static final String GET_PACKAGE_SIZE = "android.permission.GET_PACKAGE_SIZE";
@@ -5278,6 +5290,13 @@
     field @NonNull public static final android.os.Parcelable.Creator<android.app.ForegroundServiceStartNotAllowedException> CREATOR;
   }
 
+  public final class ForegroundServiceTypeNotAllowedException extends android.app.ServiceStartNotAllowedException implements android.os.Parcelable {
+    ctor public ForegroundServiceTypeNotAllowedException(@NonNull String);
+    method public int describeContents();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.app.ForegroundServiceTypeNotAllowedException> CREATOR;
+  }
+
   @Deprecated public class Fragment implements android.content.ComponentCallbacks2 android.view.View.OnCreateContextMenuListener {
     ctor @Deprecated public Fragment();
     method @Deprecated public void dump(String, java.io.FileDescriptor, java.io.PrintWriter, String[]);
@@ -6939,7 +6958,7 @@
     method public void onTrimMemory(int);
     method public boolean onUnbind(android.content.Intent);
     method public final void startForeground(int, android.app.Notification);
-    method public final void startForeground(int, @NonNull android.app.Notification, int);
+    method public final void startForeground(int, @NonNull android.app.Notification, @RequiresPermission int);
     method @Deprecated public final void stopForeground(boolean);
     method public final void stopForeground(int);
     method public final void stopSelf();
@@ -12126,6 +12145,7 @@
     field public static final String FEATURE_TOUCHSCREEN_MULTITOUCH_JAZZHAND = "android.hardware.touchscreen.multitouch.jazzhand";
     field public static final String FEATURE_USB_ACCESSORY = "android.hardware.usb.accessory";
     field public static final String FEATURE_USB_HOST = "android.hardware.usb.host";
+    field public static final String FEATURE_UWB = "android.hardware.uwb";
     field public static final String FEATURE_VERIFIED_BOOT = "android.software.verified_boot";
     field public static final String FEATURE_VR_HEADTRACKING = "android.hardware.vr.headtracking";
     field @Deprecated public static final String FEATURE_VR_MODE = "android.software.vr.mode";
@@ -12187,6 +12207,7 @@
     field public static final int PERMISSION_DENIED = -1; // 0xffffffff
     field public static final int PERMISSION_GRANTED = 0; // 0x0
     field public static final String PROPERTY_MEDIA_CAPABILITIES = "android.media.PROPERTY_MEDIA_CAPABILITIES";
+    field public static final String PROPERTY_SPECIAL_USE_FGS_SUBTYPE = "android.app.PROPERTY_SPECIAL_USE_FGS_SUBTYPE";
     field public static final int SIGNATURE_FIRST_NOT_SIGNED = -1; // 0xffffffff
     field public static final int SIGNATURE_MATCH = 0; // 0x0
     field public static final int SIGNATURE_NEITHER_SIGNED = 1; // 0x1
@@ -12400,16 +12421,20 @@
     field public static final int FLAG_SINGLE_USER = 1073741824; // 0x40000000
     field public static final int FLAG_STOP_WITH_TASK = 1; // 0x1
     field public static final int FLAG_USE_APP_ZYGOTE = 8; // 0x8
-    field public static final int FOREGROUND_SERVICE_TYPE_CAMERA = 64; // 0x40
-    field public static final int FOREGROUND_SERVICE_TYPE_CONNECTED_DEVICE = 16; // 0x10
-    field public static final int FOREGROUND_SERVICE_TYPE_DATA_SYNC = 1; // 0x1
-    field public static final int FOREGROUND_SERVICE_TYPE_LOCATION = 8; // 0x8
+    field @RequiresPermission(allOf={android.Manifest.permission.FOREGROUND_SERVICE_CAMERA}, anyOf={android.Manifest.permission.CAMERA}, conditional=true) public static final int FOREGROUND_SERVICE_TYPE_CAMERA = 64; // 0x40
+    field @RequiresPermission(allOf={android.Manifest.permission.FOREGROUND_SERVICE_CONNECTED_DEVICE}, anyOf={android.Manifest.permission.BLUETOOTH_CONNECT, android.Manifest.permission.CHANGE_NETWORK_STATE, android.Manifest.permission.CHANGE_WIFI_STATE, android.Manifest.permission.CHANGE_WIFI_MULTICAST_STATE, android.Manifest.permission.NFC, android.Manifest.permission.TRANSMIT_IR}, conditional=true) public static final int FOREGROUND_SERVICE_TYPE_CONNECTED_DEVICE = 16; // 0x10
+    field @Deprecated @RequiresPermission(value=android.Manifest.permission.FOREGROUND_SERVICE_DATA_SYNC, conditional=true) public static final int FOREGROUND_SERVICE_TYPE_DATA_SYNC = 1; // 0x1
+    field @RequiresPermission(allOf={android.Manifest.permission.FOREGROUND_SERVICE_HEALTH}, anyOf={android.Manifest.permission.ACTIVITY_RECOGNITION, android.Manifest.permission.BODY_SENSORS, android.Manifest.permission.HIGH_SAMPLING_RATE_SENSORS}, conditional=true) public static final int FOREGROUND_SERVICE_TYPE_HEALTH = 256; // 0x100
+    field @RequiresPermission(allOf={android.Manifest.permission.FOREGROUND_SERVICE_LOCATION}, anyOf={android.Manifest.permission.ACCESS_COARSE_LOCATION, android.Manifest.permission.ACCESS_FINE_LOCATION}, conditional=true) public static final int FOREGROUND_SERVICE_TYPE_LOCATION = 8; // 0x8
     field public static final int FOREGROUND_SERVICE_TYPE_MANIFEST = -1; // 0xffffffff
-    field public static final int FOREGROUND_SERVICE_TYPE_MEDIA_PLAYBACK = 2; // 0x2
-    field public static final int FOREGROUND_SERVICE_TYPE_MEDIA_PROJECTION = 32; // 0x20
-    field public static final int FOREGROUND_SERVICE_TYPE_MICROPHONE = 128; // 0x80
-    field public static final int FOREGROUND_SERVICE_TYPE_NONE = 0; // 0x0
-    field public static final int FOREGROUND_SERVICE_TYPE_PHONE_CALL = 4; // 0x4
+    field @RequiresPermission(value=android.Manifest.permission.FOREGROUND_SERVICE_MEDIA_PLAYBACK, conditional=true) public static final int FOREGROUND_SERVICE_TYPE_MEDIA_PLAYBACK = 2; // 0x2
+    field @RequiresPermission(value=android.Manifest.permission.FOREGROUND_SERVICE_MEDIA_PROJECTION, conditional=true) public static final int FOREGROUND_SERVICE_TYPE_MEDIA_PROJECTION = 32; // 0x20
+    field @RequiresPermission(allOf={android.Manifest.permission.FOREGROUND_SERVICE_MICROPHONE}, anyOf={android.Manifest.permission.CAPTURE_AUDIO_OUTPUT, android.Manifest.permission.RECORD_AUDIO}, conditional=true) public static final int FOREGROUND_SERVICE_TYPE_MICROPHONE = 128; // 0x80
+    field @Deprecated public static final int FOREGROUND_SERVICE_TYPE_NONE = 0; // 0x0
+    field @RequiresPermission(allOf={android.Manifest.permission.FOREGROUND_SERVICE_PHONE_CALL}, anyOf={android.Manifest.permission.MANAGE_OWN_CALLS}, conditional=true) public static final int FOREGROUND_SERVICE_TYPE_PHONE_CALL = 4; // 0x4
+    field @RequiresPermission(value=android.Manifest.permission.FOREGROUND_SERVICE_REMOTE_MESSAGING, conditional=true) public static final int FOREGROUND_SERVICE_TYPE_REMOTE_MESSAGING = 512; // 0x200
+    field @RequiresPermission(value=android.Manifest.permission.FOREGROUND_SERVICE_SPECIAL_USE, conditional=true) public static final int FOREGROUND_SERVICE_TYPE_SPECIAL_USE = 1073741824; // 0x40000000
+    field @RequiresPermission(value=android.Manifest.permission.FOREGROUND_SERVICE_SYSTEM_EXEMPTED, conditional=true) public static final int FOREGROUND_SERVICE_TYPE_SYSTEM_EXEMPTED = 1024; // 0x400
     field public int flags;
     field public String permission;
   }
@@ -32290,6 +32315,7 @@
     method public static final boolean is64Bit();
     method public static boolean isApplicationUid(int);
     method public static final boolean isIsolated();
+    method public static final boolean isIsolatedUid(int);
     method public static final boolean isSdkSandbox();
     method public static final void killProcess(int);
     method public static final int myPid();
@@ -39931,7 +39957,7 @@
   public abstract class WallpaperService extends android.app.Service {
     ctor public WallpaperService();
     method public final android.os.IBinder onBind(android.content.Intent);
-    method public abstract android.service.wallpaper.WallpaperService.Engine onCreateEngine();
+    method @MainThread public abstract android.service.wallpaper.WallpaperService.Engine onCreateEngine();
     field public static final String SERVICE_INTERFACE = "android.service.wallpaper.WallpaperService";
     field public static final String SERVICE_META_DATA = "android.service.wallpaper";
   }
@@ -39946,20 +39972,20 @@
     method public boolean isPreview();
     method public boolean isVisible();
     method public void notifyColorsChanged();
-    method public void onApplyWindowInsets(android.view.WindowInsets);
-    method public android.os.Bundle onCommand(String, int, int, int, android.os.Bundle, boolean);
-    method @Nullable public android.app.WallpaperColors onComputeColors();
-    method public void onCreate(android.view.SurfaceHolder);
-    method public void onDesiredSizeChanged(int, int);
-    method public void onDestroy();
-    method public void onOffsetsChanged(float, float, float, float, int, int);
-    method public void onSurfaceChanged(android.view.SurfaceHolder, int, int, int);
-    method public void onSurfaceCreated(android.view.SurfaceHolder);
-    method public void onSurfaceDestroyed(android.view.SurfaceHolder);
-    method public void onSurfaceRedrawNeeded(android.view.SurfaceHolder);
-    method public void onTouchEvent(android.view.MotionEvent);
-    method public void onVisibilityChanged(boolean);
-    method public void onZoomChanged(@FloatRange(from=0.0f, to=1.0f) float);
+    method @MainThread public void onApplyWindowInsets(android.view.WindowInsets);
+    method @MainThread public android.os.Bundle onCommand(String, int, int, int, android.os.Bundle, boolean);
+    method @MainThread @Nullable public android.app.WallpaperColors onComputeColors();
+    method @MainThread public void onCreate(android.view.SurfaceHolder);
+    method @MainThread public void onDesiredSizeChanged(int, int);
+    method @MainThread public void onDestroy();
+    method @MainThread public void onOffsetsChanged(float, float, float, float, int, int);
+    method @MainThread public void onSurfaceChanged(android.view.SurfaceHolder, int, int, int);
+    method @MainThread public void onSurfaceCreated(android.view.SurfaceHolder);
+    method @MainThread public void onSurfaceDestroyed(android.view.SurfaceHolder);
+    method @MainThread public void onSurfaceRedrawNeeded(android.view.SurfaceHolder);
+    method @MainThread public void onTouchEvent(android.view.MotionEvent);
+    method @MainThread public void onVisibilityChanged(boolean);
+    method @MainThread public void onZoomChanged(@FloatRange(from=0.0f, to=1.0f) float);
     method public void setOffsetNotificationsEnabled(boolean);
     method public void setTouchEventsEnabled(boolean);
   }
@@ -41888,6 +41914,7 @@
     field public static final String KEY_VOICE_PRIVACY_DISABLE_UI_BOOL = "voice_privacy_disable_ui_bool";
     field public static final String KEY_VOLTE_REPLACEMENT_RAT_INT = "volte_replacement_rat_int";
     field public static final String KEY_VONR_ENABLED_BOOL = "vonr_enabled_bool";
+    field public static final String KEY_VONR_ON_BY_DEFAULT_BOOL = "vonr_on_by_default_bool";
     field public static final String KEY_VONR_SETTING_VISIBILITY_BOOL = "vonr_setting_visibility_bool";
     field public static final String KEY_VT_UPGRADE_SUPPORTED_FOR_DOWNGRADED_RTT_CALL_BOOL = "vt_upgrade_supported_for_downgraded_rtt_call";
     field public static final String KEY_VVM_CELLULAR_DATA_REQUIRED_BOOL = "vvm_cellular_data_required_bool";
diff --git a/core/api/module-lib-current.txt b/core/api/module-lib-current.txt
index 1183547..e6ddf9f 100644
--- a/core/api/module-lib-current.txt
+++ b/core/api/module-lib-current.txt
@@ -385,6 +385,7 @@
     method public static void traceBegin(long, @NonNull String);
     method public static void traceCounter(long, @NonNull String, int);
     method public static void traceEnd(long);
+    field public static final long TRACE_TAG_AIDL = 16777216L; // 0x1000000L
     field public static final long TRACE_TAG_NETWORK = 2097152L; // 0x200000L
   }
 
@@ -412,6 +413,7 @@
 
   public final class DeviceConfig {
     field public static final String NAMESPACE_ALARM_MANAGER = "alarm_manager";
+    field public static final String NAMESPACE_APP_CLONING = "app_cloning";
     field public static final String NAMESPACE_APP_STANDBY = "app_standby";
     field public static final String NAMESPACE_DEVICE_IDLE = "device_idle";
   }
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index 4cead54..5169413 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -2909,6 +2909,7 @@
     method @NonNull public java.util.Set<android.content.ComponentName> getBlockedCrossTaskNavigations();
     method public int getDefaultActivityPolicy();
     method public int getDefaultNavigationPolicy();
+    method public int getDevicePolicy(int);
     method public int getLockState();
     method @Nullable public String getName();
     method @NonNull public java.util.Set<android.os.UserHandle> getUsersWithMatchingAccounts();
@@ -2916,14 +2917,18 @@
     field public static final int ACTIVITY_POLICY_DEFAULT_ALLOWED = 0; // 0x0
     field public static final int ACTIVITY_POLICY_DEFAULT_BLOCKED = 1; // 0x1
     field @NonNull public static final android.os.Parcelable.Creator<android.companion.virtual.VirtualDeviceParams> CREATOR;
+    field public static final int DEVICE_POLICY_CUSTOM = 1; // 0x1
+    field public static final int DEVICE_POLICY_DEFAULT = 0; // 0x0
     field public static final int LOCK_STATE_ALWAYS_UNLOCKED = 1; // 0x1
     field public static final int LOCK_STATE_DEFAULT = 0; // 0x0
     field public static final int NAVIGATION_POLICY_DEFAULT_ALLOWED = 0; // 0x0
     field public static final int NAVIGATION_POLICY_DEFAULT_BLOCKED = 1; // 0x1
+    field public static final int POLICY_TYPE_SENSORS = 0; // 0x0
   }
 
   public static final class VirtualDeviceParams.Builder {
     ctor public VirtualDeviceParams.Builder();
+    method @NonNull public android.companion.virtual.VirtualDeviceParams.Builder addDevicePolicy(int, int);
     method @NonNull public android.companion.virtual.VirtualDeviceParams build();
     method @NonNull public android.companion.virtual.VirtualDeviceParams.Builder setAllowedActivities(@NonNull java.util.Set<android.content.ComponentName>);
     method @NonNull public android.companion.virtual.VirtualDeviceParams.Builder setAllowedCrossTaskNavigations(@NonNull java.util.Set<android.content.ComponentName>);
@@ -3502,6 +3507,7 @@
     field public static final String FEATURE_REBOOT_ESCROW = "android.hardware.reboot_escrow";
     field public static final String FEATURE_TELEPHONY_CARRIERLOCK = "android.hardware.telephony.carrierlock";
     field public static final String FEATURE_TELEPHONY_IMS_SINGLE_REGISTRATION = "android.hardware.telephony.ims.singlereg";
+    field public static final String FEATURE_VIRTUALIZATION_FRAMEWORK = "android.software.virtualization_framework";
     field public static final int FLAGS_PERMISSION_RESERVED_PERMISSION_CONTROLLER = -268435456; // 0xf0000000
     field public static final int FLAG_PERMISSION_APPLY_RESTRICTION = 16384; // 0x4000
     field public static final int FLAG_PERMISSION_AUTO_REVOKED = 131072; // 0x20000
@@ -7156,6 +7162,7 @@
 
   public class Tuner implements java.lang.AutoCloseable {
     ctor @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER) public Tuner(@NonNull android.content.Context, @Nullable String, int);
+    method public int applyFrontend(@NonNull android.media.tv.tuner.frontend.FrontendInfo);
     method public int cancelScanning();
     method public int cancelTuning();
     method public void clearOnTuneEventListener();
@@ -7186,7 +7193,6 @@
     method @Nullable @RequiresPermission(android.Manifest.permission.ACCESS_TV_SHARED_FILTER) public static android.media.tv.tuner.filter.SharedFilter openSharedFilter(@NonNull android.content.Context, @NonNull String, @NonNull java.util.concurrent.Executor, @NonNull android.media.tv.tuner.filter.SharedFilterCallback);
     method @Nullable public android.media.tv.tuner.filter.TimeFilter openTimeFilter();
     method public int removeOutputPid(@IntRange(from=0) int);
-    method public int requestFrontendById(int);
     method public int scan(@NonNull android.media.tv.tuner.frontend.FrontendSettings, int, @NonNull java.util.concurrent.Executor, @NonNull android.media.tv.tuner.frontend.ScanCallback);
     method public int setLnaEnabled(boolean);
     method public int setMaxNumberOfFrontends(int, @IntRange(from=0) int);
@@ -9969,9 +9975,10 @@
     method public boolean isCloneProfile();
     method public boolean isCredentialSharableWithParent();
     method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS, android.Manifest.permission.QUERY_USERS}) public boolean isGuestUser();
+    method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS, android.Manifest.permission.QUERY_USERS}) public boolean isMainUser();
     method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.QUERY_USERS, android.Manifest.permission.INTERACT_ACROSS_USERS}, conditional=true) public boolean isManagedProfile(int);
     method public boolean isMediaSharedWithParent();
-    method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS, android.Manifest.permission.QUERY_USERS}) public boolean isPrimaryUser();
+    method @Deprecated @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS, android.Manifest.permission.QUERY_USERS}) public boolean isPrimaryUser();
     method public static boolean isRemoveResultSuccessful(int);
     method public boolean isRestrictedProfile();
     method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS}, conditional=true) public boolean isRestrictedProfile(@NonNull android.os.UserHandle);
@@ -11925,11 +11932,39 @@
     method public abstract void onStopUpdates();
     method public final void reportPermanentFailure(@NonNull Throwable);
     method public final void reportSuggestion(@NonNull android.service.timezone.TimeZoneProviderSuggestion);
+    method public final void reportSuggestion(@NonNull android.service.timezone.TimeZoneProviderSuggestion, @NonNull android.service.timezone.TimeZoneProviderStatus);
     method public final void reportUncertain();
+    method public final void reportUncertain(@NonNull android.service.timezone.TimeZoneProviderStatus);
     field public static final String PRIMARY_LOCATION_TIME_ZONE_PROVIDER_SERVICE_INTERFACE = "android.service.timezone.PrimaryLocationTimeZoneProviderService";
     field public static final String SECONDARY_LOCATION_TIME_ZONE_PROVIDER_SERVICE_INTERFACE = "android.service.timezone.SecondaryLocationTimeZoneProviderService";
   }
 
+  public final class TimeZoneProviderStatus implements android.os.Parcelable {
+    method public int describeContents();
+    method public int getConnectivityDependencyStatus();
+    method public int getLocationDetectionDependencyStatus();
+    method public int getTimeZoneResolutionOperationStatus();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.service.timezone.TimeZoneProviderStatus> CREATOR;
+    field public static final int DEPENDENCY_STATUS_BLOCKED_BY_ENVIRONMENT = 4; // 0x4
+    field public static final int DEPENDENCY_STATUS_BLOCKED_BY_SETTINGS = 6; // 0x6
+    field public static final int DEPENDENCY_STATUS_DEGRADED_BY_SETTINGS = 5; // 0x5
+    field public static final int DEPENDENCY_STATUS_NOT_APPLICABLE = 1; // 0x1
+    field public static final int DEPENDENCY_STATUS_OK = 2; // 0x2
+    field public static final int DEPENDENCY_STATUS_TEMPORARILY_UNAVAILABLE = 3; // 0x3
+    field public static final int OPERATION_STATUS_FAILED = 3; // 0x3
+    field public static final int OPERATION_STATUS_NOT_APPLICABLE = 1; // 0x1
+    field public static final int OPERATION_STATUS_OK = 2; // 0x2
+  }
+
+  public static final class TimeZoneProviderStatus.Builder {
+    ctor public TimeZoneProviderStatus.Builder();
+    method @NonNull public android.service.timezone.TimeZoneProviderStatus build();
+    method @NonNull public android.service.timezone.TimeZoneProviderStatus.Builder setConnectivityDependencyStatus(int);
+    method @NonNull public android.service.timezone.TimeZoneProviderStatus.Builder setLocationDetectionDependencyStatus(int);
+    method @NonNull public android.service.timezone.TimeZoneProviderStatus.Builder setTimeZoneResolutionOperationStatus(int);
+  }
+
   public final class TimeZoneProviderSuggestion implements android.os.Parcelable {
     method public int describeContents();
     method public long getElapsedRealtimeMillis();
@@ -12226,7 +12261,7 @@
 
   public class WallpaperService.Engine {
     method public boolean isInAmbientMode();
-    method public void onAmbientModeChanged(boolean, long);
+    method @MainThread public void onAmbientModeChanged(boolean, long);
   }
 
 }
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index d1c1a17..3fee610 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -905,6 +905,7 @@
     method public boolean isFull();
     method public boolean isGuest();
     method public boolean isInitialized();
+    method public boolean isMain();
     method public boolean isManagedProfile();
     method public boolean isPrimary();
     method public boolean isProfile();
@@ -923,6 +924,7 @@
     field public static final int FLAG_FULL = 1024; // 0x400
     field @Deprecated public static final int FLAG_GUEST = 4; // 0x4
     field public static final int FLAG_INITIALIZED = 16; // 0x10
+    field public static final int FLAG_MAIN = 16384; // 0x4000
     field @Deprecated public static final int FLAG_MANAGED_PROFILE = 32; // 0x20
     field public static final int FLAG_PRIMARY = 1; // 0x1
     field public static final int FLAG_PROFILE = 4096; // 0x1000
diff --git a/core/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java
index 0f4644c..1b3282e 100644
--- a/core/java/android/app/AppOpsManager.java
+++ b/core/java/android/app/AppOpsManager.java
@@ -1398,9 +1398,18 @@
      */
     public static final int OP_READ_WRITE_HEALTH_DATA = AppProtoEnums.APP_OP_READ_WRITE_HEALTH_DATA;
 
+    /**
+     * Use foreground service with the type
+     * {@link android.content.pm.ServiceInfo#FOREGROUND_SERVICE_TYPE_SPECIAL_USE}.
+     *
+     * @hide
+     */
+    public static final int OP_FOREGROUND_SERVICE_SPECIAL_USE =
+            AppProtoEnums.APP_OP_FOREGROUND_SERVICE_SPECIAL_USE;
+
     /** @hide */
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
-    public static final int _NUM_OP = 127;
+    public static final int _NUM_OP = 128;
 
     /** Access to coarse location information. */
     public static final String OPSTR_COARSE_LOCATION = "android:coarse_location";
@@ -1930,6 +1939,14 @@
     public static final String OPSTR_SYSTEM_EXEMPT_FROM_FORCED_APP_STANDBY =
             "android:system_exempt_from_forced_app_standby";
 
+    /**
+     * Start a foreground service with the type "specialUse".
+     *
+     * @hide
+     */
+    public static final String OPSTR_FOREGROUND_SERVICE_SPECIAL_USE =
+            "android:foreground_service_special_use";
+
     /** {@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} */
@@ -2026,6 +2043,7 @@
             OP_TURN_SCREEN_ON,
             OP_RUN_LONG_JOBS,
             OP_READ_MEDIA_VISUAL_USER_SELECTED,
+            OP_FOREGROUND_SERVICE_SPECIAL_USE,
     };
 
     static final AppOpInfo[] sAppOpInfos = new AppOpInfo[]{
@@ -2419,7 +2437,10 @@
                 OPSTR_SYSTEM_EXEMPT_FROM_FORCED_APP_STANDBY,
                 "SYSTEM_EXEMPT_FROM_FORCED_APP_STANDBY").build(),
         new AppOpInfo.Builder(OP_READ_WRITE_HEALTH_DATA, OPSTR_READ_WRITE_HEALTH_DATA,
-                "READ_WRITE_HEALTH_DATA").setDefaultMode(AppOpsManager.MODE_ALLOWED).build()
+                "READ_WRITE_HEALTH_DATA").setDefaultMode(AppOpsManager.MODE_ALLOWED).build(),
+        new AppOpInfo.Builder(OP_FOREGROUND_SERVICE_SPECIAL_USE,
+                OPSTR_FOREGROUND_SERVICE_SPECIAL_USE, "FOREGROUND_SERVICE_SPECIAL_USE")
+                .setPermission(Manifest.permission.FOREGROUND_SERVICE_SPECIAL_USE).build(),
     };
 
     // The number of longs needed to form a full bitmask of app ops
@@ -8486,8 +8507,9 @@
      */
     public int startProxyOpNoThrow(int op, @NonNull AttributionSource attributionSource,
             @Nullable String message, boolean skipProxyOperation) {
-        return startProxyOpNoThrow(op, attributionSource, message, skipProxyOperation,
-                ATTRIBUTION_FLAGS_NONE, ATTRIBUTION_FLAGS_NONE, ATTRIBUTION_CHAIN_ID_NONE);
+        return startProxyOpNoThrow(attributionSource.getToken(), op, attributionSource, message,
+                skipProxyOperation, ATTRIBUTION_FLAGS_NONE, ATTRIBUTION_FLAGS_NONE,
+                ATTRIBUTION_CHAIN_ID_NONE);
     }
 
     /**
@@ -8499,7 +8521,8 @@
      *
      * @hide
      */
-    public int startProxyOpNoThrow(int op, @NonNull AttributionSource attributionSource,
+    public int startProxyOpNoThrow(@NonNull IBinder clientId, int op,
+            @NonNull AttributionSource attributionSource,
             @Nullable String message, boolean skipProxyOperation, @AttributionFlags
             int proxyAttributionFlags, @AttributionFlags int proxiedAttributionFlags,
             int attributionChainId) {
@@ -8517,7 +8540,7 @@
                 }
             }
 
-            SyncNotedAppOp syncOp = mService.startProxyOperation(op,
+            SyncNotedAppOp syncOp = mService.startProxyOperation(clientId, op,
                     attributionSource, false, collectionMode == COLLECT_ASYNC, message,
                     shouldCollectMessage, skipProxyOperation, proxyAttributionFlags,
                     proxiedAttributionFlags, attributionChainId);
@@ -8615,9 +8638,10 @@
      */
     public void finishProxyOp(@NonNull String op, int proxiedUid,
             @NonNull String proxiedPackageName, @Nullable String proxiedAttributionTag) {
-        finishProxyOp(op, new AttributionSource(mContext.getAttributionSource(),
+        IBinder token = mContext.getAttributionSource().getToken();
+        finishProxyOp(token, op, new AttributionSource(mContext.getAttributionSource(),
                 new AttributionSource(proxiedUid, proxiedPackageName,  proxiedAttributionTag,
-                        mContext.getAttributionSource().getToken())), /*skipProxyOperation*/ false);
+                        token)), /*skipProxyOperation*/ false);
     }
 
     /**
@@ -8632,10 +8656,11 @@
      *
      * @hide
      */
-    public void finishProxyOp(@NonNull String op, @NonNull AttributionSource attributionSource,
-            boolean skipProxyOperation) {
+    public void finishProxyOp(@NonNull IBinder clientId, @NonNull String op,
+            @NonNull AttributionSource attributionSource, boolean skipProxyOperation) {
         try {
-            mService.finishProxyOperation(strOpToOp(op), attributionSource, skipProxyOperation);
+            mService.finishProxyOperation(clientId, strOpToOp(op), attributionSource,
+                    skipProxyOperation);
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
diff --git a/core/java/android/app/AppOpsManagerInternal.java b/core/java/android/app/AppOpsManagerInternal.java
index 4d6e4ae..43023fe 100644
--- a/core/java/android/app/AppOpsManagerInternal.java
+++ b/core/java/android/app/AppOpsManagerInternal.java
@@ -26,13 +26,11 @@
 import android.util.SparseIntArray;
 
 import com.android.internal.app.IAppOpsCallback;
-import com.android.internal.util.function.DecFunction;
 import com.android.internal.util.function.HeptFunction;
 import com.android.internal.util.function.HexFunction;
 import com.android.internal.util.function.QuadFunction;
 import com.android.internal.util.function.QuintConsumer;
 import com.android.internal.util.function.QuintFunction;
-import com.android.internal.util.function.TriFunction;
 import com.android.internal.util.function.UndecFunction;
 
 /**
@@ -135,6 +133,7 @@
         /**
          * Allows overriding start proxy operation behavior.
          *
+         * @param clientId The client calling start, represented by an IBinder
          * @param code The op code to start.
          * @param attributionSource The permission identity of the caller.
          * @param startIfModeDefault Whether to start the op of the mode is default.
@@ -148,11 +147,12 @@
          * @param superImpl The super implementation.
          * @return The app op note result.
          */
-        SyncNotedAppOp startProxyOperation(int code, @NonNull AttributionSource attributionSource,
-                boolean startIfModeDefault, boolean shouldCollectAsyncNotedOp, String message,
-                boolean shouldCollectMessage, boolean skipProxyOperation, @AttributionFlags
-                int proxyAttributionFlags, @AttributionFlags int proxiedAttributionFlags,
-                int attributionChainId, @NonNull DecFunction<Integer, AttributionSource, Boolean,
+        SyncNotedAppOp startProxyOperation(@NonNull IBinder clientId, int code,
+                @NonNull AttributionSource attributionSource, boolean startIfModeDefault,
+                boolean shouldCollectAsyncNotedOp, String message, boolean shouldCollectMessage,
+                boolean skipProxyOperation, @AttributionFlags int proxyAttributionFlags,
+                @AttributionFlags int proxiedAttributionFlags, int attributionChainId,
+                @NonNull UndecFunction<IBinder, Integer, AttributionSource, Boolean,
                         Boolean, String, Boolean, Boolean, Integer, Integer, Integer,
                         SyncNotedAppOp> superImpl);
 
@@ -176,10 +176,15 @@
          *
          * @param code The op code to finish.
          * @param attributionSource The permission identity of the caller.
+         * @param skipProxyOperation Whether to skip the proxy in the proxy/proxied operation
+         * @param clientId The client calling finishProxyOperation
+         * @param superImpl The "standard" implementation to potentially call
          */
-        void finishProxyOperation(int code, @NonNull AttributionSource attributionSource,
+        void finishProxyOperation(@NonNull IBinder clientId, int code,
+                @NonNull AttributionSource attributionSource,
                 boolean skipProxyOperation,
-                @NonNull TriFunction<Integer, AttributionSource, Boolean, Void> superImpl);
+                @NonNull QuadFunction<IBinder, Integer, AttributionSource, Boolean,
+                        Void> superImpl);
     }
 
     /**
diff --git a/core/java/android/app/ContextImpl.java b/core/java/android/app/ContextImpl.java
index 10cdf53..042bdd7 100644
--- a/core/java/android/app/ContextImpl.java
+++ b/core/java/android/app/ContextImpl.java
@@ -1814,12 +1814,6 @@
             }
         }
         try {
-            ActivityThread thread = ActivityThread.currentActivityThread();
-            Instrumentation instrumentation = thread.getInstrumentation();
-            if (instrumentation.isInstrumenting()
-                    && ((flags & Context.RECEIVER_NOT_EXPORTED) == 0)) {
-                flags = flags | Context.RECEIVER_EXPORTED;
-            }
             final Intent intent = ActivityManager.getService().registerReceiverWithFeature(
                     mMainThread.getApplicationThread(), mBasePackageName, getAttributionTag(),
                     AppOpsManager.toReceiverId(receiver), rd, filter, broadcastPermission, userId,
diff --git a/core/java/android/app/ForegroundServiceTypeNotAllowedException.java b/core/java/android/app/ForegroundServiceTypeNotAllowedException.java
new file mode 100644
index 0000000..c258242
--- /dev/null
+++ b/core/java/android/app/ForegroundServiceTypeNotAllowedException.java
@@ -0,0 +1,61 @@
+/*
+ * 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 android.app;
+
+import android.annotation.NonNull;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * Exception thrown when an app tries to start a foreground {@link Service} without a valid type.
+ */
+public final class ForegroundServiceTypeNotAllowedException
+        extends ServiceStartNotAllowedException implements Parcelable {
+    /**
+     * Constructor.
+     */
+    public ForegroundServiceTypeNotAllowedException(@NonNull String message) {
+        super(message);
+    }
+
+    ForegroundServiceTypeNotAllowedException(@NonNull Parcel source) {
+        super(source.readString());
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+        dest.writeString(getMessage());
+    }
+
+    public static final @NonNull Creator<android.app.ForegroundServiceTypeNotAllowedException>
+            CREATOR = new Creator<android.app.ForegroundServiceTypeNotAllowedException>() {
+                @NonNull
+                public android.app.ForegroundServiceTypeNotAllowedException createFromParcel(
+                        Parcel source) {
+                    return new android.app.ForegroundServiceTypeNotAllowedException(source);
+                }
+
+                @NonNull
+                public android.app.ForegroundServiceTypeNotAllowedException[] newArray(int size) {
+                    return new android.app.ForegroundServiceTypeNotAllowedException[size];
+                }
+            };
+}
diff --git a/core/java/android/app/ForegroundServiceTypePolicy.java b/core/java/android/app/ForegroundServiceTypePolicy.java
new file mode 100644
index 0000000..eccc563
--- /dev/null
+++ b/core/java/android/app/ForegroundServiceTypePolicy.java
@@ -0,0 +1,1033 @@
+/*
+ * 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 android.app;
+
+import static android.app.AppOpsManager.MODE_ALLOWED;
+import static android.app.AppOpsManager.MODE_FOREGROUND;
+import static android.content.pm.PackageManager.PERMISSION_DENIED;
+import static android.content.pm.PackageManager.PERMISSION_GRANTED;
+import static android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_CAMERA;
+import static android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_CONNECTED_DEVICE;
+import static android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_DATA_SYNC;
+import static android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_HEALTH;
+import static android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_LOCATION;
+import static android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_MEDIA_PLAYBACK;
+import static android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_MEDIA_PROJECTION;
+import static android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_MICROPHONE;
+import static android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_NONE;
+import static android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_PHONE_CALL;
+import static android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_REMOTE_MESSAGING;
+import static android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_SPECIAL_USE;
+import static android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_SYSTEM_EXEMPTED;
+
+import android.Manifest;
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SuppressLint;
+import android.app.compat.CompatChanges;
+import android.compat.Compatibility;
+import android.compat.annotation.ChangeId;
+import android.compat.annotation.Disabled;
+import android.compat.annotation.EnabledAfter;
+import android.compat.annotation.Overridable;
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.content.pm.ServiceInfo;
+import android.content.pm.ServiceInfo.ForegroundServiceType;
+import android.hardware.usb.UsbAccessory;
+import android.hardware.usb.UsbDevice;
+import android.hardware.usb.UsbManager;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.util.ArraySet;
+import android.util.SparseArray;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.compat.CompatibilityChangeConfig;
+import com.android.internal.compat.IPlatformCompat;
+import com.android.internal.util.ArrayUtils;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.HashMap;
+import java.util.Optional;
+
+/**
+ * This class enforces the policies around the foreground service types.
+ *
+ * @hide
+ */
+public abstract class ForegroundServiceTypePolicy {
+    static final String TAG = "ForegroundServiceTypePolicy";
+    static final boolean DEBUG_FOREGROUND_SERVICE_TYPE_POLICY = false;
+
+    /**
+     * The FGS type enforcement:
+     * deprecating the {@link android.content.pm.ServiceInfo#FOREGROUND_SERVICE_TYPE_NONE}.
+     *
+     * <p>Starting a FGS with this type (equivalent of no type) from apps with
+     * targetSdkVersion {@link android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE} or later will
+     * result in a warning in the log.</p>
+     *
+     * @hide
+     */
+    @ChangeId
+    @EnabledAfter(targetSdkVersion = android.os.Build.VERSION_CODES.TIRAMISU)
+    @Overridable
+    public static final long FGS_TYPE_NONE_DEPRECATION_CHANGE_ID = 255042465L;
+
+    /**
+     * The FGS type enforcement:
+     * disabling the {@link android.content.pm.ServiceInfo#FOREGROUND_SERVICE_TYPE_NONE}.
+     *
+     * <p>Starting a FGS with this type (equivalent of no type) from apps with
+     * targetSdkVersion {@link android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE} or later will
+     * result in an exception.</p>
+     *
+     * @hide
+     */
+    // TODO (b/254661666): Change to @EnabledAfter(T)
+    @ChangeId
+    @Disabled
+    @Overridable
+    public static final long FGS_TYPE_NONE_DISABLED_CHANGE_ID = 255038118L;
+
+    /**
+     * The FGS type enforcement:
+     * deprecating the {@link android.content.pm.ServiceInfo#FOREGROUND_SERVICE_TYPE_DATA_SYNC}.
+     *
+     * <p>Starting a FGS with this type from apps with targetSdkVersion
+     * {@link android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE} or later will
+     * result in a warning in the log.</p>
+     *
+     * @hide
+     */
+    @ChangeId
+    @EnabledAfter(targetSdkVersion = android.os.Build.VERSION_CODES.TIRAMISU)
+    @Overridable
+    public static final long FGS_TYPE_DATA_SYNC_DEPRECATION_CHANGE_ID = 255039210L;
+
+    /**
+     * The FGS type enforcement:
+     * disabling the {@link android.content.pm.ServiceInfo#FOREGROUND_SERVICE_TYPE_DATA_SYNC}.
+     *
+     * <p>Starting a FGS with this type from apps with targetSdkVersion
+     * {@link android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE} or later will
+     * result in an exception.</p>
+     *
+     * @hide
+     */
+    // TODO (b/254661666): Change to @EnabledSince(U) in next OS release
+    @ChangeId
+    @Disabled
+    @Overridable
+    public static final long FGS_TYPE_DATA_SYNC_DISABLED_CHANGE_ID = 255659651L;
+
+    /**
+     * The FGS type enforcement: Starting a FGS from apps with targetSdkVersion
+     * {@link android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE} or later but without the required
+     * permissions associated with the FGS type will result in a SecurityException.
+     *
+     * @hide
+     */
+    // TODO (b/254661666): Change to @EnabledAfter(T)
+    @ChangeId
+    @Disabled
+    @Overridable
+    public static final long FGS_TYPE_PERMISSION_CHANGE_ID = 254662522L;
+
+    /**
+     * The policy for the {@link ServiceInfo#FOREGROUND_SERVICE_TYPE_NONE}.
+     *
+     * @hide
+     */
+    public static final @NonNull ForegroundServiceTypePolicyInfo FGS_TYPE_POLICY_NONE =
+            new ForegroundServiceTypePolicyInfo(
+            FOREGROUND_SERVICE_TYPE_NONE,
+            FGS_TYPE_NONE_DEPRECATION_CHANGE_ID,
+            FGS_TYPE_NONE_DISABLED_CHANGE_ID,
+            null,
+            null
+    );
+
+    /**
+     * The policy for the {@link ServiceInfo#FOREGROUND_SERVICE_TYPE_DATA_SYNC}.
+     *
+     * @hide
+     */
+    public static final @NonNull ForegroundServiceTypePolicyInfo FGS_TYPE_POLICY_DATA_SYNC =
+            new ForegroundServiceTypePolicyInfo(
+            FOREGROUND_SERVICE_TYPE_DATA_SYNC,
+            FGS_TYPE_DATA_SYNC_DEPRECATION_CHANGE_ID,
+            FGS_TYPE_DATA_SYNC_DISABLED_CHANGE_ID,
+            new ForegroundServiceTypePermissions(new ForegroundServiceTypePermission[] {
+                new RegularPermission(Manifest.permission.FOREGROUND_SERVICE_DATA_SYNC)
+            }, true),
+            null
+    );
+
+    /**
+     * The policy for the {@link ServiceInfo#FOREGROUND_SERVICE_TYPE_MEDIA_PLAYBACK}.
+     *
+     * @hide
+     */
+    public static final @NonNull ForegroundServiceTypePolicyInfo FGS_TYPE_POLICY_MEDIA_PLAYBACK =
+            new ForegroundServiceTypePolicyInfo(
+            FOREGROUND_SERVICE_TYPE_MEDIA_PLAYBACK,
+            ForegroundServiceTypePolicyInfo.INVALID_CHANGE_ID,
+            ForegroundServiceTypePolicyInfo.INVALID_CHANGE_ID,
+            new ForegroundServiceTypePermissions(new ForegroundServiceTypePermission[] {
+                new RegularPermission(Manifest.permission.FOREGROUND_SERVICE_MEDIA_PLAYBACK)
+            }, true),
+            null
+    );
+
+    /**
+     * The policy for the {@link ServiceInfo#FOREGROUND_SERVICE_TYPE_PHONE_CALL}.
+     *
+     * @hide
+     */
+    public static final @NonNull ForegroundServiceTypePolicyInfo FGS_TYPE_POLICY_PHONE_CALL =
+            new ForegroundServiceTypePolicyInfo(
+            FOREGROUND_SERVICE_TYPE_PHONE_CALL,
+            ForegroundServiceTypePolicyInfo.INVALID_CHANGE_ID,
+            ForegroundServiceTypePolicyInfo.INVALID_CHANGE_ID,
+            new ForegroundServiceTypePermissions(new ForegroundServiceTypePermission[] {
+                new RegularPermission(Manifest.permission.FOREGROUND_SERVICE_PHONE_CALL)
+            }, true),
+            new ForegroundServiceTypePermissions(new ForegroundServiceTypePermission[] {
+                new RegularPermission(Manifest.permission.MANAGE_OWN_CALLS)
+            }, false)
+    );
+
+    /**
+     * The policy for the {@link ServiceInfo#FOREGROUND_SERVICE_TYPE_LOCATION}.
+     *
+     * @hide
+     */
+    public static final @NonNull ForegroundServiceTypePolicyInfo FGS_TYPE_POLICY_LOCATION =
+            new ForegroundServiceTypePolicyInfo(
+            FOREGROUND_SERVICE_TYPE_LOCATION,
+            ForegroundServiceTypePolicyInfo.INVALID_CHANGE_ID,
+            ForegroundServiceTypePolicyInfo.INVALID_CHANGE_ID,
+            new ForegroundServiceTypePermissions(new ForegroundServiceTypePermission[] {
+                new RegularPermission(Manifest.permission.FOREGROUND_SERVICE_LOCATION)
+            }, true),
+            new ForegroundServiceTypePermissions(new ForegroundServiceTypePermission[] {
+                new RegularPermission(Manifest.permission.ACCESS_COARSE_LOCATION),
+                new RegularPermission(Manifest.permission.ACCESS_FINE_LOCATION),
+            }, false)
+    );
+
+    /**
+     * The policy for the {@link ServiceInfo#FOREGROUND_SERVICE_TYPE_CONNECTED_DEVICE}.
+     *
+     * @hide
+     */
+    public static final @NonNull ForegroundServiceTypePolicyInfo FGS_TYPE_POLICY_CONNECTED_DEVICE =
+            new ForegroundServiceTypePolicyInfo(
+            FOREGROUND_SERVICE_TYPE_CONNECTED_DEVICE,
+            ForegroundServiceTypePolicyInfo.INVALID_CHANGE_ID,
+            ForegroundServiceTypePolicyInfo.INVALID_CHANGE_ID,
+            new ForegroundServiceTypePermissions(new ForegroundServiceTypePermission[] {
+                new RegularPermission(Manifest.permission.FOREGROUND_SERVICE_CONNECTED_DEVICE)
+            }, true),
+            new ForegroundServiceTypePermissions(new ForegroundServiceTypePermission[] {
+                new RegularPermission(Manifest.permission.BLUETOOTH_CONNECT),
+                new RegularPermission(Manifest.permission.CHANGE_NETWORK_STATE),
+                new RegularPermission(Manifest.permission.CHANGE_WIFI_STATE),
+                new RegularPermission(Manifest.permission.CHANGE_WIFI_MULTICAST_STATE),
+                new RegularPermission(Manifest.permission.NFC),
+                new RegularPermission(Manifest.permission.TRANSMIT_IR),
+                new UsbDevicePermission(),
+                new UsbAccessoryPermission(),
+            }, false)
+    );
+
+    /**
+     * The policy for the {@link ServiceInfo#FOREGROUND_SERVICE_TYPE_MEDIA_PROJECTION}.
+     *
+     * @hide
+     */
+    public static final @NonNull ForegroundServiceTypePolicyInfo FGS_TYPE_POLICY_MEDIA_PROJECTION =
+            new ForegroundServiceTypePolicyInfo(
+            FOREGROUND_SERVICE_TYPE_MEDIA_PROJECTION,
+            ForegroundServiceTypePolicyInfo.INVALID_CHANGE_ID,
+            ForegroundServiceTypePolicyInfo.INVALID_CHANGE_ID,
+            new ForegroundServiceTypePermissions(new ForegroundServiceTypePermission[] {
+                new RegularPermission(Manifest.permission.FOREGROUND_SERVICE_MEDIA_PROJECTION)
+            }, true),
+            new ForegroundServiceTypePermissions(new ForegroundServiceTypePermission[] {
+                new RegularPermission(Manifest.permission.CAPTURE_VIDEO_OUTPUT),
+                new AppOpPermission(AppOpsManager.OP_PROJECT_MEDIA)
+            }, false)
+    );
+
+    /**
+     * The policy for the {@link ServiceInfo#FOREGROUND_SERVICE_TYPE_CAMERA}.
+     *
+     * @hide
+     */
+    public static final @NonNull ForegroundServiceTypePolicyInfo FGS_TYPE_POLICY_CAMERA =
+            new ForegroundServiceTypePolicyInfo(
+            FOREGROUND_SERVICE_TYPE_CAMERA,
+            ForegroundServiceTypePolicyInfo.INVALID_CHANGE_ID,
+            ForegroundServiceTypePolicyInfo.INVALID_CHANGE_ID,
+            new ForegroundServiceTypePermissions(new ForegroundServiceTypePermission[] {
+                new RegularPermission(Manifest.permission.FOREGROUND_SERVICE_CAMERA)
+            }, true),
+            new ForegroundServiceTypePermissions(new ForegroundServiceTypePermission[] {
+                new RegularPermission(Manifest.permission.CAMERA),
+                new RegularPermission(Manifest.permission.SYSTEM_CAMERA),
+            }, false)
+    );
+
+    /**
+     * The policy for the {@link ServiceInfo#FOREGROUND_SERVICE_TYPE_MICROPHONE}.
+     *
+     * @hide
+     */
+    public static final @NonNull ForegroundServiceTypePolicyInfo FGS_TYPE_POLICY_MICROPHONE =
+            new ForegroundServiceTypePolicyInfo(
+            FOREGROUND_SERVICE_TYPE_MICROPHONE,
+            ForegroundServiceTypePolicyInfo.INVALID_CHANGE_ID,
+            ForegroundServiceTypePolicyInfo.INVALID_CHANGE_ID,
+            new ForegroundServiceTypePermissions(new ForegroundServiceTypePermission[] {
+                new RegularPermission(Manifest.permission.FOREGROUND_SERVICE_MICROPHONE)
+            }, true),
+            new ForegroundServiceTypePermissions(new ForegroundServiceTypePermission[] {
+                new RegularPermission(Manifest.permission.CAPTURE_AUDIO_HOTWORD),
+                new RegularPermission(Manifest.permission.CAPTURE_AUDIO_OUTPUT),
+                new RegularPermission(Manifest.permission.CAPTURE_MEDIA_OUTPUT),
+                new RegularPermission(Manifest.permission.CAPTURE_TUNER_AUDIO_INPUT),
+                new RegularPermission(Manifest.permission.CAPTURE_VOICE_COMMUNICATION_OUTPUT),
+                new RegularPermission(Manifest.permission.RECORD_AUDIO),
+            }, false)
+    );
+
+    /**
+     * The policy for the {@link ServiceInfo#FOREGROUND_SERVICE_TYPE_HEALTH}.
+     *
+     * @hide
+     */
+    public static final @NonNull ForegroundServiceTypePolicyInfo FGS_TYPE_POLICY_HEALTH =
+            new ForegroundServiceTypePolicyInfo(
+            FOREGROUND_SERVICE_TYPE_HEALTH,
+            ForegroundServiceTypePolicyInfo.INVALID_CHANGE_ID,
+            ForegroundServiceTypePolicyInfo.INVALID_CHANGE_ID,
+            new ForegroundServiceTypePermissions(new ForegroundServiceTypePermission[] {
+                new RegularPermission(Manifest.permission.FOREGROUND_SERVICE_HEALTH)
+            }, true),
+            new ForegroundServiceTypePermissions(new ForegroundServiceTypePermission[] {
+                new RegularPermission(Manifest.permission.ACTIVITY_RECOGNITION),
+                new RegularPermission(Manifest.permission.BODY_SENSORS),
+                new RegularPermission(Manifest.permission.HIGH_SAMPLING_RATE_SENSORS),
+            }, false)
+    );
+
+    /**
+     * The policy for the {@link ServiceInfo#FOREGROUND_SERVICE_TYPE_REMOTE_MESSAGING}.
+     *
+     * @hide
+     */
+    public static final @NonNull ForegroundServiceTypePolicyInfo FGS_TYPE_POLICY_REMOTE_MESSAGING =
+            new ForegroundServiceTypePolicyInfo(
+            FOREGROUND_SERVICE_TYPE_REMOTE_MESSAGING,
+            ForegroundServiceTypePolicyInfo.INVALID_CHANGE_ID,
+            ForegroundServiceTypePolicyInfo.INVALID_CHANGE_ID,
+            new ForegroundServiceTypePermissions(new ForegroundServiceTypePermission[] {
+                new RegularPermission(Manifest.permission.FOREGROUND_SERVICE_REMOTE_MESSAGING)
+            }, true),
+            null
+    );
+
+    /**
+     * The policy for the {@link ServiceInfo#FOREGROUND_SERVICE_TYPE_SYSTEM_EXEMPTED}.
+     *
+     * @hide
+     */
+    public static final @NonNull ForegroundServiceTypePolicyInfo FGS_TYPE_POLICY_SYSTEM_EXEMPTED =
+            new ForegroundServiceTypePolicyInfo(
+            FOREGROUND_SERVICE_TYPE_SYSTEM_EXEMPTED,
+            ForegroundServiceTypePolicyInfo.INVALID_CHANGE_ID,
+            ForegroundServiceTypePolicyInfo.INVALID_CHANGE_ID,
+            new ForegroundServiceTypePermissions(new ForegroundServiceTypePermission[] {
+                new RegularPermission(Manifest.permission.FOREGROUND_SERVICE_SYSTEM_EXEMPTED)
+            }, true),
+            new ForegroundServiceTypePermissions(new ForegroundServiceTypePermission[] {
+                new RegularPermission(Manifest.permission.SCHEDULE_EXACT_ALARM),
+                new RegularPermission(Manifest.permission.USE_EXACT_ALARM),
+                new AppOpPermission(AppOpsManager.OP_ACTIVATE_VPN),
+                new AppOpPermission(AppOpsManager.OP_ACTIVATE_PLATFORM_VPN),
+            }, false)
+    );
+
+    /**
+     * The policy for the {@link ServiceInfo#FOREGROUND_SERVICE_TYPE_SPECIAL_USE}.
+     *
+     * @hide
+     */
+    public static final @NonNull ForegroundServiceTypePolicyInfo FGS_TYPE_POLICY_SPECIAL_USE =
+            new ForegroundServiceTypePolicyInfo(
+            FOREGROUND_SERVICE_TYPE_SPECIAL_USE,
+            ForegroundServiceTypePolicyInfo.INVALID_CHANGE_ID,
+            ForegroundServiceTypePolicyInfo.INVALID_CHANGE_ID,
+            new ForegroundServiceTypePermissions(new ForegroundServiceTypePermission[] {
+                new RegularPermission(Manifest.permission.FOREGROUND_SERVICE_SPECIAL_USE)
+            }, true),
+            null
+    );
+
+    /**
+     * Foreground service policy check result code: this one is not actually being used.
+     *
+     * @hide
+     */
+    public static final int FGS_TYPE_POLICY_CHECK_UNKNOWN =
+            AppProtoEnums.FGS_TYPE_POLICY_CHECK_UNKNOWN;
+
+    /**
+     * Foreground service policy check result code: okay to go.
+     *
+     * @hide
+     */
+    public static final int FGS_TYPE_POLICY_CHECK_OK =
+            AppProtoEnums.FGS_TYPE_POLICY_CHECK_OK;
+
+    /**
+     * Foreground service policy check result code: this foreground service type is deprecated.
+     *
+     * @hide
+     */
+    public static final int FGS_TYPE_POLICY_CHECK_DEPRECATED =
+            AppProtoEnums.FGS_TYPE_POLICY_CHECK_DEPRECATED;
+
+    /**
+     * Foreground service policy check result code: this foreground service type is disabled.
+     *
+     * @hide
+     */
+    public static final int FGS_TYPE_POLICY_CHECK_DISABLED =
+            AppProtoEnums.FGS_TYPE_POLICY_CHECK_DISABLED;
+
+    /**
+     * Foreground service policy check result code: the caller doesn't have permission to start
+     * foreground service with this type, but the policy is permissive.
+     *
+     * @hide
+     */
+    public static final int FGS_TYPE_POLICY_CHECK_PERMISSION_DENIED_PERMISSIVE =
+            AppProtoEnums.FGS_TYPE_POLICY_CHECK_PERMISSION_DENIED_PERMISSIVE;
+
+    /**
+     * Foreground service policy check result code: the caller doesn't have permission to start
+     * foreground service with this type, and the policy is enforced.
+     *
+     * @hide
+     */
+    public static final int FGS_TYPE_POLICY_CHECK_PERMISSION_DENIED_ENFORCED =
+            AppProtoEnums.FGS_TYPE_POLICY_CHECK_PERMISSION_DENIED_ENFORCED;
+
+    /**
+     * @hide
+     */
+    @IntDef(flag = true, prefix = { "FGS_TYPE_POLICY_CHECK_" }, value = {
+         FGS_TYPE_POLICY_CHECK_UNKNOWN,
+         FGS_TYPE_POLICY_CHECK_OK,
+         FGS_TYPE_POLICY_CHECK_DEPRECATED,
+         FGS_TYPE_POLICY_CHECK_DISABLED,
+         FGS_TYPE_POLICY_CHECK_PERMISSION_DENIED_PERMISSIVE,
+         FGS_TYPE_POLICY_CHECK_PERMISSION_DENIED_ENFORCED,
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface ForegroundServicePolicyCheckCode{}
+
+    /**
+     * @return The policy info for the given type.
+     */
+    @NonNull
+    public abstract ForegroundServiceTypePolicyInfo getForegroundServiceTypePolicyInfo(
+            @ForegroundServiceType int type, @ForegroundServiceType int defaultToType);
+
+    /**
+     * Run check on the foreground service type policy for the given uid/pid
+     *
+     * @hide
+     */
+    @ForegroundServicePolicyCheckCode
+    public abstract int checkForegroundServiceTypePolicy(@NonNull Context context,
+            @NonNull String packageName, int callerUid, int callerPid, boolean allowWhileInUse,
+            @NonNull ForegroundServiceTypePolicyInfo policy);
+
+    @GuardedBy("sLock")
+    private static ForegroundServiceTypePolicy sDefaultForegroundServiceTypePolicy = null;
+
+    private static final Object sLock = new Object();
+
+    /**
+     * Return the default policy for FGS type.
+     */
+    public static @NonNull ForegroundServiceTypePolicy getDefaultPolicy() {
+        synchronized (sLock) {
+            if (sDefaultForegroundServiceTypePolicy == null) {
+                sDefaultForegroundServiceTypePolicy = new DefaultForegroundServiceTypePolicy();
+            }
+            return sDefaultForegroundServiceTypePolicy;
+        }
+    }
+
+    /**
+     * Constructor.
+     *
+     * @hide
+     */
+    public ForegroundServiceTypePolicy() {
+    }
+
+    /**
+     * This class represents the policy for a specific FGS service type.
+     *
+     * @hide
+     */
+    public static final class ForegroundServiceTypePolicyInfo {
+        /**
+         * The foreground service type.
+         */
+        final @ForegroundServiceType int mType;
+
+        /**
+         * The change id to tell if this FGS type is deprecated.
+         *
+         * <p>A 0 indicates it's not deprecated.</p>
+         */
+        final long mDeprecationChangeId;
+
+        /**
+         * The change id to tell if this FGS type is disabled.
+         *
+         * <p>A 0 indicates it's not disabled.</p>
+         */
+        final long mDisabledChangeId;
+
+        /**
+         * The required permissions to start a foreground with this type, all of them
+         * MUST have been granted.
+         */
+        final @Nullable ForegroundServiceTypePermissions mAllOfPermissions;
+
+        /**
+         * The required permissions to start a foreground with this type, any one of them
+         * being granted is sufficient.
+         */
+        final @Nullable ForegroundServiceTypePermissions mAnyOfPermissions;
+
+        /**
+         * A customized check for the permissions.
+         */
+        @Nullable ForegroundServiceTypePermission mCustomPermission;
+
+        /**
+         * Not a real change id, but a place holder.
+         */
+        private static final long INVALID_CHANGE_ID = 0L;
+
+        /**
+         * @return {@code true} if the given change id is valid.
+         */
+        private static boolean isValidChangeId(long changeId) {
+            return changeId != INVALID_CHANGE_ID;
+        }
+
+        /**
+         * Construct a new instance.
+         *
+         * @hide
+         */
+        public ForegroundServiceTypePolicyInfo(@ForegroundServiceType int type,
+                long deprecationChangeId, long disabledChangeId,
+                @Nullable ForegroundServiceTypePermissions allOfPermissions,
+                @Nullable ForegroundServiceTypePermissions anyOfPermissions) {
+            mType = type;
+            mDeprecationChangeId = deprecationChangeId;
+            mDisabledChangeId = disabledChangeId;
+            mAllOfPermissions = allOfPermissions;
+            mAnyOfPermissions = anyOfPermissions;
+        }
+
+        /**
+         * @return The foreground service type.
+         */
+        @ForegroundServiceType
+        public int getForegroundServiceType() {
+            return mType;
+        }
+
+        @Override
+        public String toString() {
+            final StringBuilder sb = toPermissionString(new StringBuilder());
+            sb.append("type=0x");
+            sb.append(Integer.toHexString(mType));
+            sb.append(" deprecationChangeId=");
+            sb.append(mDeprecationChangeId);
+            sb.append(" disabledChangeId=");
+            sb.append(mDisabledChangeId);
+            sb.append(" customPermission=");
+            sb.append(mCustomPermission);
+            return sb.toString();
+        }
+
+        /**
+         * @return The required permissions.
+         */
+        public String toPermissionString() {
+            return toPermissionString(new StringBuilder()).toString();
+        }
+
+        private StringBuilder toPermissionString(StringBuilder sb) {
+            if (mAllOfPermissions != null) {
+                sb.append("all of the permissions ");
+                sb.append(mAllOfPermissions.toString());
+                sb.append(' ');
+            }
+            if (mAnyOfPermissions != null) {
+                sb.append("any of the permissions ");
+                sb.append(mAnyOfPermissions.toString());
+                sb.append(' ');
+            }
+            return sb;
+        }
+
+        /**
+         * @hide
+         */
+        public void setCustomPermission(
+                @Nullable ForegroundServiceTypePermission customPermission) {
+            mCustomPermission = customPermission;
+        }
+
+        /**
+         * @return The name of the permissions which are all required.
+         *         It may contain app op names.
+         *
+         * For test only.
+         */
+        public @NonNull Optional<String[]> getRequiredAllOfPermissionsForTest() {
+            if (mAllOfPermissions == null) {
+                return Optional.empty();
+            }
+            return Optional.of(mAllOfPermissions.toStringArray());
+        }
+
+        /**
+         * @return The name of the permissions where any of the is granted is sufficient.
+         *         It may contain app op names.
+         *
+         * For test only.
+         */
+        public @NonNull Optional<String[]> getRequiredAnyOfPermissionsForTest() {
+            if (mAnyOfPermissions == null) {
+                return Optional.empty();
+            }
+            return Optional.of(mAnyOfPermissions.toStringArray());
+        }
+
+        /**
+         * Whether or not this type is disabled.
+         */
+        @SuppressLint("AndroidFrameworkRequiresPermission")
+        public boolean isTypeDisabled(int callerUid) {
+            return isValidChangeId(mDisabledChangeId)
+                    && CompatChanges.isChangeEnabled(mDisabledChangeId, callerUid);
+        }
+
+        /**
+         * Override the type disabling change Id.
+         *
+         * For test only.
+         */
+        public void setTypeDisabledForTest(boolean disabled, @NonNull String packageName)
+                throws RemoteException {
+            overrideChangeIdForTest(mDisabledChangeId, disabled, packageName);
+        }
+
+        /**
+         * clear the type disabling change Id.
+         *
+         * For test only.
+         */
+        public void clearTypeDisabledForTest(@NonNull String packageName) throws RemoteException {
+            clearOverrideForTest(mDisabledChangeId, packageName);
+        }
+
+        @SuppressLint("AndroidFrameworkRequiresPermission")
+        boolean isTypeDeprecated(int callerUid) {
+            return isValidChangeId(mDeprecationChangeId)
+                    && CompatChanges.isChangeEnabled(mDeprecationChangeId, callerUid);
+        }
+
+        private void overrideChangeIdForTest(long changeId, boolean enable, String packageName)
+                throws RemoteException {
+            if (!isValidChangeId(changeId)) {
+                return;
+            }
+            final ArraySet<Long> enabled = new ArraySet<>();
+            final ArraySet<Long> disabled = new ArraySet<>();
+            if (enable) {
+                enabled.add(changeId);
+            } else {
+                disabled.add(changeId);
+            }
+            final CompatibilityChangeConfig overrides = new CompatibilityChangeConfig(
+                    new Compatibility.ChangeConfig(enabled, disabled));
+            IPlatformCompat platformCompat = IPlatformCompat.Stub.asInterface(
+                        ServiceManager.getService(Context.PLATFORM_COMPAT_SERVICE));
+            platformCompat.setOverridesForTest(overrides, packageName);
+        }
+
+        private void clearOverrideForTest(long changeId, @NonNull String packageName)
+                throws RemoteException {
+            IPlatformCompat platformCompat = IPlatformCompat.Stub.asInterface(
+                        ServiceManager.getService(Context.PLATFORM_COMPAT_SERVICE));
+            platformCompat.clearOverrideForTest(changeId, packageName);
+        }
+    }
+
+    /**
+     * This represents the set of permissions that's going to be required
+     * for a specific service type.
+     *
+     * @hide
+     */
+    public static class ForegroundServiceTypePermissions {
+        /**
+         * The set of the permissions to be required.
+         */
+        final @NonNull ForegroundServiceTypePermission[] mPermissions;
+
+        /**
+         * Are we requiring all of the permissions to be granted or any of them.
+         */
+        final boolean mAllOf;
+
+        /**
+         * Constructor.
+         */
+        public ForegroundServiceTypePermissions(
+                @NonNull ForegroundServiceTypePermission[] permissions, boolean allOf) {
+            mPermissions = permissions;
+            mAllOf = allOf;
+        }
+
+        /**
+         * Check the permissions.
+         */
+        @PackageManager.PermissionResult
+        public int checkPermissions(@NonNull Context context, int callerUid, int callerPid,
+                @NonNull String packageName, boolean allowWhileInUse) {
+            if (mAllOf) {
+                for (ForegroundServiceTypePermission perm : mPermissions) {
+                    final int result = perm.checkPermission(context, callerUid, callerPid,
+                            packageName, allowWhileInUse);
+                    if (result != PERMISSION_GRANTED) {
+                        return PERMISSION_DENIED;
+                    }
+                }
+                return PERMISSION_GRANTED;
+            } else {
+                boolean anyOfGranted = false;
+                for (ForegroundServiceTypePermission perm : mPermissions) {
+                    final int result = perm.checkPermission(context, callerUid, callerPid,
+                            packageName, allowWhileInUse);
+                    if (result == PERMISSION_GRANTED) {
+                        anyOfGranted = true;
+                        break;
+                    }
+                }
+                return anyOfGranted ? PERMISSION_GRANTED : PERMISSION_DENIED;
+            }
+        }
+
+        @Override
+        public String toString() {
+            final StringBuilder sb = new StringBuilder();
+            sb.append("allOf=");
+            sb.append(mAllOf);
+            sb.append(' ');
+            sb.append('[');
+            for (int i = 0; i < mPermissions.length; i++) {
+                if (i > 0) {
+                    sb.append(", ");
+                }
+                sb.append(mPermissions[i].toString());
+            }
+            sb.append(']');
+            return sb.toString();
+        }
+
+        @NonNull String[] toStringArray() {
+            final String[] names = new String[mPermissions.length];
+            for (int i = 0; i < mPermissions.length; i++) {
+                names[i] = mPermissions[i].mName;
+            }
+            return names;
+        }
+    }
+
+    /**
+     * This represents a permission that's going to be required for a specific service type.
+     *
+     * @hide
+     */
+    public abstract static class ForegroundServiceTypePermission {
+        /**
+         * The name of this permission.
+         */
+        final @NonNull String mName;
+
+        /**
+         * Constructor.
+         */
+        public ForegroundServiceTypePermission(@NonNull String name) {
+            mName = name;
+        }
+
+        /**
+         * Check if the given uid/pid/package has the access to the permission.
+         */
+        @PackageManager.PermissionResult
+        public abstract int checkPermission(@NonNull Context context, int callerUid, int callerPid,
+                @NonNull String packageName, boolean allowWhileInUse);
+
+        @Override
+        public String toString() {
+            return mName;
+        }
+    }
+
+    /**
+     * This represents a regular Android permission to be required for a specific service type.
+     */
+    static class RegularPermission extends ForegroundServiceTypePermission {
+        RegularPermission(@NonNull String name) {
+            super(name);
+        }
+
+        @Override
+        @SuppressLint("AndroidFrameworkRequiresPermission")
+        @PackageManager.PermissionResult
+        public int checkPermission(Context context, int callerUid, int callerPid,
+                String packageName, boolean allowWhileInUse) {
+            // Simple case, check if it's already granted.
+            if (context.checkPermission(mName, callerPid, callerUid) == PERMISSION_GRANTED) {
+                return PERMISSION_GRANTED;
+            }
+            if (allowWhileInUse) {
+                // Check its appops
+                final int opCode = AppOpsManager.permissionToOpCode(mName);
+                final AppOpsManager appOpsManager = context.getSystemService(AppOpsManager.class);
+                if (opCode != AppOpsManager.OP_NONE) {
+                    final int currentMode = appOpsManager.unsafeCheckOpRawNoThrow(opCode, callerUid,
+                            packageName);
+                    if (currentMode == MODE_FOREGROUND) {
+                        // It's in foreground only mode and we're allowing while-in-use.
+                        return PERMISSION_GRANTED;
+                    }
+                }
+            }
+            return PERMISSION_DENIED;
+        }
+    }
+
+    /**
+     * This represents an app op permission to be required for a specific service type.
+     */
+    static class AppOpPermission extends ForegroundServiceTypePermission {
+        final int mOpCode;
+
+        AppOpPermission(int opCode) {
+            super(AppOpsManager.opToPublicName(opCode));
+            mOpCode = opCode;
+        }
+
+        @Override
+        @PackageManager.PermissionResult
+        public int checkPermission(Context context, int callerUid, int callerPid,
+                String packageName, boolean allowWhileInUse) {
+            final AppOpsManager appOpsManager = context.getSystemService(AppOpsManager.class);
+            final int mode = appOpsManager.unsafeCheckOpRawNoThrow(mOpCode, callerUid, packageName);
+            return (mode == MODE_ALLOWED || (allowWhileInUse && mode == MODE_FOREGROUND))
+                    ? PERMISSION_GRANTED : PERMISSION_DENIED;
+        }
+    }
+
+    /**
+     * This represents a special Android permission to be required for accessing usb devices.
+     */
+    static class UsbDevicePermission extends ForegroundServiceTypePermission {
+        UsbDevicePermission() {
+            super("USB Device");
+        }
+
+        @Override
+        @SuppressLint("AndroidFrameworkRequiresPermission")
+        @PackageManager.PermissionResult
+        public int checkPermission(Context context, int callerUid, int callerPid,
+                String packageName, boolean allowWhileInUse) {
+            final UsbManager usbManager = context.getSystemService(UsbManager.class);
+            final HashMap<String, UsbDevice> devices = usbManager.getDeviceList();
+            if (!ArrayUtils.isEmpty(devices)) {
+                for (UsbDevice device : devices.values()) {
+                    if (usbManager.hasPermission(device, packageName, callerPid, callerUid)) {
+                        return PERMISSION_GRANTED;
+                    }
+                }
+            }
+            return PERMISSION_DENIED;
+        }
+    }
+
+    /**
+     * This represents a special Android permission to be required for accessing usb accessories.
+     */
+    static class UsbAccessoryPermission extends ForegroundServiceTypePermission {
+        UsbAccessoryPermission() {
+            super("USB Accessory");
+        }
+
+        @Override
+        @SuppressLint("AndroidFrameworkRequiresPermission")
+        @PackageManager.PermissionResult
+        public int checkPermission(Context context, int callerUid, int callerPid,
+                String packageName, boolean allowWhileInUse) {
+            final UsbManager usbManager = context.getSystemService(UsbManager.class);
+            final UsbAccessory[] accessories = usbManager.getAccessoryList();
+            if (!ArrayUtils.isEmpty(accessories)) {
+                for (UsbAccessory accessory: accessories) {
+                    if (usbManager.hasPermission(accessory, callerPid, callerUid)) {
+                        return PERMISSION_GRANTED;
+                    }
+                }
+            }
+            return PERMISSION_DENIED;
+        }
+    }
+
+    /**
+     * The default policy for the foreground service types.
+     *
+     * @hide
+     */
+    public static class DefaultForegroundServiceTypePolicy extends ForegroundServiceTypePolicy {
+        private final SparseArray<ForegroundServiceTypePolicyInfo> mForegroundServiceTypePolicies =
+                new SparseArray<>();
+
+        /**
+         * Constructor
+         */
+        public DefaultForegroundServiceTypePolicy() {
+            mForegroundServiceTypePolicies.put(FOREGROUND_SERVICE_TYPE_NONE,
+                    FGS_TYPE_POLICY_NONE);
+            mForegroundServiceTypePolicies.put(FOREGROUND_SERVICE_TYPE_DATA_SYNC,
+                    FGS_TYPE_POLICY_DATA_SYNC);
+            mForegroundServiceTypePolicies.put(FOREGROUND_SERVICE_TYPE_MEDIA_PLAYBACK,
+                    FGS_TYPE_POLICY_MEDIA_PLAYBACK);
+            mForegroundServiceTypePolicies.put(FOREGROUND_SERVICE_TYPE_PHONE_CALL,
+                    FGS_TYPE_POLICY_PHONE_CALL);
+            mForegroundServiceTypePolicies.put(FOREGROUND_SERVICE_TYPE_LOCATION,
+                    FGS_TYPE_POLICY_LOCATION);
+            mForegroundServiceTypePolicies.put(FOREGROUND_SERVICE_TYPE_CONNECTED_DEVICE,
+                    FGS_TYPE_POLICY_CONNECTED_DEVICE);
+            mForegroundServiceTypePolicies.put(FOREGROUND_SERVICE_TYPE_MEDIA_PROJECTION,
+                    FGS_TYPE_POLICY_MEDIA_PROJECTION);
+            mForegroundServiceTypePolicies.put(FOREGROUND_SERVICE_TYPE_CAMERA,
+                    FGS_TYPE_POLICY_CAMERA);
+            mForegroundServiceTypePolicies.put(FOREGROUND_SERVICE_TYPE_MICROPHONE,
+                    FGS_TYPE_POLICY_MICROPHONE);
+            mForegroundServiceTypePolicies.put(FOREGROUND_SERVICE_TYPE_HEALTH,
+                    FGS_TYPE_POLICY_HEALTH);
+            mForegroundServiceTypePolicies.put(FOREGROUND_SERVICE_TYPE_REMOTE_MESSAGING,
+                    FGS_TYPE_POLICY_REMOTE_MESSAGING);
+            mForegroundServiceTypePolicies.put(FOREGROUND_SERVICE_TYPE_SYSTEM_EXEMPTED,
+                    FGS_TYPE_POLICY_SYSTEM_EXEMPTED);
+            mForegroundServiceTypePolicies.put(FOREGROUND_SERVICE_TYPE_SPECIAL_USE,
+                    FGS_TYPE_POLICY_SPECIAL_USE);
+        }
+
+        @Override
+        public ForegroundServiceTypePolicyInfo getForegroundServiceTypePolicyInfo(
+                @ForegroundServiceType int type, @ForegroundServiceType int defaultToType) {
+            ForegroundServiceTypePolicyInfo info = mForegroundServiceTypePolicies.get(type);
+            if (info == null) {
+                // Unknown type, fallback to the defaultToType
+                info = mForegroundServiceTypePolicies.get(defaultToType);
+                if (info == null) {
+                    // It shouldn't happen.
+                    throw new IllegalArgumentException("Invalid default fgs type " + defaultToType);
+                }
+            }
+            return info;
+        }
+
+        @Override
+        @SuppressLint("AndroidFrameworkRequiresPermission")
+        @ForegroundServicePolicyCheckCode
+        public int checkForegroundServiceTypePolicy(Context context, String packageName,
+                int callerUid, int callerPid, boolean allowWhileInUse,
+                @NonNull ForegroundServiceTypePolicyInfo policy) {
+            // Has this FGS type been disabled and not allowed to use anymore?
+            if (policy.isTypeDisabled(callerUid)) {
+                return FGS_TYPE_POLICY_CHECK_DISABLED;
+            }
+            int permissionResult = PERMISSION_DENIED;
+            // Do we have the permission to start FGS with this type.
+            if (policy.mAllOfPermissions != null) {
+                permissionResult = policy.mAllOfPermissions.checkPermissions(context,
+                        callerUid, callerPid, packageName, allowWhileInUse);
+            }
+            // If it has the "all of" permissions granted, check the "any of" ones.
+            if (permissionResult == PERMISSION_GRANTED) {
+                boolean checkCustomPermission = true;
+                // Check the "any of" permissions.
+                if (policy.mAnyOfPermissions != null) {
+                    permissionResult = policy.mAnyOfPermissions.checkPermissions(context,
+                            callerUid, callerPid, packageName, allowWhileInUse);
+                    if (permissionResult == PERMISSION_GRANTED) {
+                        // We have one of them granted, no need to check custom permissions.
+                        checkCustomPermission = false;
+                    }
+                }
+                // If we have a customized permission checker, also call it now.
+                if (checkCustomPermission && policy.mCustomPermission != null) {
+                    permissionResult = policy.mCustomPermission.checkPermission(context,
+                            callerUid, callerPid, packageName, allowWhileInUse);
+                }
+            }
+            if (permissionResult != PERMISSION_GRANTED) {
+                return (CompatChanges.isChangeEnabled(
+                        FGS_TYPE_PERMISSION_CHANGE_ID, callerUid))
+                        ? FGS_TYPE_POLICY_CHECK_PERMISSION_DENIED_ENFORCED
+                        : FGS_TYPE_POLICY_CHECK_PERMISSION_DENIED_PERMISSIVE;
+            }
+            // Has this FGS type been deprecated?
+            if (policy.isTypeDeprecated(callerUid)) {
+                return FGS_TYPE_POLICY_CHECK_DEPRECATED;
+            }
+            return FGS_TYPE_POLICY_CHECK_OK;
+        }
+    }
+}
diff --git a/core/java/android/app/IntentService.java b/core/java/android/app/IntentService.java
index 2e83308..99f864c 100644
--- a/core/java/android/app/IntentService.java
+++ b/core/java/android/app/IntentService.java
@@ -57,8 +57,7 @@
  * @deprecated IntentService is subject to all the
  *   <a href="{@docRoot}about/versions/oreo/background.html">background execution limits</a>
  *   imposed with Android 8.0 (API level 26). Consider using {@link androidx.work.WorkManager}
- *   or {@link androidx.core.app.JobIntentService}, which uses jobs
- *   instead of services when running on Android 8.0 or higher.
+ *   instead.
  */
 @Deprecated
 public abstract class IntentService extends Service {
diff --git a/core/java/android/app/SearchableInfo.java b/core/java/android/app/SearchableInfo.java
index 5388282..bd5d105 100644
--- a/core/java/android/app/SearchableInfo.java
+++ b/core/java/android/app/SearchableInfo.java
@@ -396,6 +396,17 @@
         private final String mSuggestActionMsg;
         private final String mSuggestActionMsgColumn;
 
+        public static final Parcelable.Creator<ActionKeyInfo> CREATOR =
+                new Parcelable.Creator<ActionKeyInfo>() {
+                    public ActionKeyInfo createFromParcel(Parcel in) {
+                        return new ActionKeyInfo(in);
+                    }
+
+                    public ActionKeyInfo[] newArray(int size) {
+                        return new ActionKeyInfo[size];
+                    }
+                };
+
         /**
          * Create one object using attributeset as input data.
          * @param activityContext runtime context of the activity that the action key information
diff --git a/core/java/android/app/Service.java b/core/java/android/app/Service.java
index 7635138..01e4b15 100644
--- a/core/java/android/app/Service.java
+++ b/core/java/android/app/Service.java
@@ -21,6 +21,7 @@
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.annotation.RequiresPermission;
 import android.compat.annotation.UnsupportedAppUsage;
 import android.content.ComponentCallbacks2;
 import android.content.ComponentName;
@@ -726,10 +727,32 @@
      * for more details.
      * </div>
      *
+     * <div class="caution">
+     * <p><strong>Note:</strong>
+     * Beginning with SDK Version {@link android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE},
+     * apps targeting SDK Version {@link android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE}
+     * or higher are not allowed to start foreground services without specifying a valid
+     * foreground service type in the manifest attribute
+     * {@link android.R.attr#foregroundServiceType}.
+     * See
+     * <a href="{@docRoot}/about/versions/14/behavior-changes-14">
+     * Behavior changes: Apps targeting Android 14
+     * </a>
+     * for more details.
+     * </div>
+     *
      * @throws ForegroundServiceStartNotAllowedException
      * If the app targeting API is
      * {@link android.os.Build.VERSION_CODES#S} or later, and the service is restricted from
      * becoming foreground service due to background restriction.
+     * @throws ForegroundServiceTypeNotAllowedException
+     * If the app targeting API is
+     * {@link android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE} or later, and the manifest attribute
+     * {@link android.R.attr#foregroundServiceType} is not set.
+     * @throws SecurityException If the app targeting API is
+     * {@link android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE} or later and doesn't have the
+     * permission to start the foreground service with the specified type in the manifest attribute
+     * {@link android.R.attr#foregroundServiceType}.
      *
      * @param id The identifier for this notification as per
      * {@link NotificationManager#notify(int, Notification)
@@ -748,51 +771,77 @@
         }
     }
 
-  /**
-   * An overloaded version of {@link #startForeground(int, Notification)} with additional
-   * foregroundServiceType parameter.
-   *
-   * <p>Apps built with SDK version {@link android.os.Build.VERSION_CODES#Q} or later can specify
-   * the foreground service types using attribute {@link android.R.attr#foregroundServiceType} in
-   * service element of manifest file. The value of attribute
-   * {@link android.R.attr#foregroundServiceType} can be multiple flags ORed together.</p>
-   *
-   * <p>The foregroundServiceType parameter must be a subset flags of what is specified in manifest
-   * attribute {@link android.R.attr#foregroundServiceType}, if not, an IllegalArgumentException is
-   * thrown. Specify foregroundServiceType parameter as
-   * {@link android.content.pm.ServiceInfo#FOREGROUND_SERVICE_TYPE_MANIFEST} to use all flags that
-   * is specified in manifest attribute foregroundServiceType.</p>
-   *
-   * <div class="caution">
-   * <p><strong>Note:</strong>
-   * Beginning with SDK Version {@link android.os.Build.VERSION_CODES#S},
-   * apps targeting SDK Version {@link android.os.Build.VERSION_CODES#S}
-   * or higher are not allowed to start foreground services from the background.
-   * See
-   * <a href="{@docRoot}/about/versions/12/behavior-changes-12">
-   * Behavior changes: Apps targeting Android 12
-   * </a>
-   * for more details.
-   * </div>
-   *
-   * @param id The identifier for this notification as per
-   * {@link NotificationManager#notify(int, Notification)
-   * NotificationManager.notify(int, Notification)}; must not be 0.
-   * @param notification The Notification to be displayed.
-   * @param foregroundServiceType must be a subset flags of manifest attribute
-   * {@link android.R.attr#foregroundServiceType} flags.
-   *
-   * @throws IllegalArgumentException if param foregroundServiceType is not subset of manifest
-   *     attribute {@link android.R.attr#foregroundServiceType}.
-   * @throws ForegroundServiceStartNotAllowedException
-   * If the app targeting API is
-   * {@link android.os.Build.VERSION_CODES#S} or later, and the service is restricted from
-   * becoming foreground service due to background restriction.
-   *
-   * @see android.content.pm.ServiceInfo#FOREGROUND_SERVICE_TYPE_MANIFEST
-   */
+    /**
+     * An overloaded version of {@link #startForeground(int, Notification)} with additional
+     * foregroundServiceType parameter.
+     *
+     * <p>Apps built with SDK version {@link android.os.Build.VERSION_CODES#Q} or later can specify
+     * the foreground service types using attribute {@link android.R.attr#foregroundServiceType} in
+     * service element of manifest file. The value of attribute
+     * {@link android.R.attr#foregroundServiceType} can be multiple flags ORed together.</p>
+     *
+     * <p>The foregroundServiceType parameter must be a subset flags of what is specified in
+     * manifest attribute {@link android.R.attr#foregroundServiceType}, if not, an
+     * IllegalArgumentException is thrown. Specify foregroundServiceType parameter as
+     * {@link android.content.pm.ServiceInfo#FOREGROUND_SERVICE_TYPE_MANIFEST} to use all flags that
+     * is specified in manifest attribute foregroundServiceType.</p>
+     *
+     * <div class="caution">
+     * <p><strong>Note:</strong>
+     * Beginning with SDK Version {@link android.os.Build.VERSION_CODES#S},
+     * apps targeting SDK Version {@link android.os.Build.VERSION_CODES#S}
+     * or higher are not allowed to start foreground services from the background.
+     * See
+     * <a href="{@docRoot}/about/versions/12/behavior-changes-12">
+     * Behavior changes: Apps targeting Android 12
+     * </a>
+     * for more details.
+     * </div>
+     *
+     * <div class="caution">
+     * <p><strong>Note:</strong>
+     * Beginning with SDK Version {@link android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE},
+     * apps targeting SDK Version {@link android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE}
+     * or higher are not allowed to start foreground services without specifying a valid
+     * foreground service type in the manifest attribute
+     * {@link android.R.attr#foregroundServiceType}, and the parameter {@code foregroundServiceType}
+     * here must not be the {@link ServiceInfo#FOREGROUND_SERVICE_TYPE_NONE}.
+     * See
+     * <a href="{@docRoot}/about/versions/14/behavior-changes-14">
+     * Behavior changes: Apps targeting Android 14
+     * </a>
+     * for more details.
+     * </div>
+     *
+     * @param id The identifier for this notification as per
+     * {@link NotificationManager#notify(int, Notification)
+     * NotificationManager.notify(int, Notification)}; must not be 0.
+     * @param notification The Notification to be displayed.
+     * @param foregroundServiceType must be a subset flags of manifest attribute
+     * {@link android.R.attr#foregroundServiceType} flags; must not be
+     * {@link ServiceInfo#FOREGROUND_SERVICE_TYPE_NONE}.
+     *
+     * @throws IllegalArgumentException if param foregroundServiceType is not subset of manifest
+     *     attribute {@link android.R.attr#foregroundServiceType}.
+     * @throws ForegroundServiceStartNotAllowedException
+     * If the app targeting API is
+     * {@link android.os.Build.VERSION_CODES#S} or later, and the service is restricted from
+     * becoming foreground service due to background restriction.
+     * @throws ForegroundServiceTypeNotAllowedException
+     * If the app targeting API is
+     * {@link android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE} or later, and the manifest attribute
+     * {@link android.R.attr#foregroundServiceType} is not set, or the param
+     * {@code foregroundServiceType} is {@link ServiceInfo#FOREGROUND_SERVICE_TYPE_NONE}.
+     * @throws SecurityException If the app targeting API is
+     * {@link android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE} or later and doesn't have the
+     * permission to start the foreground service with the specified type in
+     * {@code foregroundServiceType}.
+     * {@link android.R.attr#foregroundServiceType}.
+     *
+     * @see android.content.pm.ServiceInfo#FOREGROUND_SERVICE_TYPE_MANIFEST
+     */
     public final void startForeground(int id, @NonNull Notification notification,
-            @ForegroundServiceType int foregroundServiceType) {
+            @RequiresPermission @ForegroundServiceType int foregroundServiceType) {
         try {
             mActivityManager.setServiceForeground(
                     new ComponentName(this, mClassName), mToken, id,
diff --git a/core/java/android/app/StatusBarManager.java b/core/java/android/app/StatusBarManager.java
index 6d28972..a035375 100644
--- a/core/java/android/app/StatusBarManager.java
+++ b/core/java/android/app/StatusBarManager.java
@@ -229,6 +229,8 @@
     public static final int CAMERA_LAUNCH_SOURCE_POWER_DOUBLE_TAP = 1;
     /** @hide */
     public static final int CAMERA_LAUNCH_SOURCE_LIFT_TRIGGER = 2;
+    /** @hide */
+    public static final int CAMERA_LAUNCH_SOURCE_QUICK_AFFORDANCE = 3;
 
     /**
      * Session flag for {@link #registerSessionListener} indicating the listener
diff --git a/core/java/android/app/backup/BackupRestoreEventLogger.java b/core/java/android/app/backup/BackupRestoreEventLogger.java
index 760c6f0..6f62c8a 100644
--- a/core/java/android/app/backup/BackupRestoreEventLogger.java
+++ b/core/java/android/app/backup/BackupRestoreEventLogger.java
@@ -53,6 +53,8 @@
 
     /**
      * Operation types for which this logger can be used.
+     *
+     * @hide
      */
     @Retention(RetentionPolicy.SOURCE)
     @IntDef({
@@ -87,6 +89,8 @@
      *                      {@link OperationType}. Attempts to use logging methods that don't match
      *                      the specified operation type will be rejected (e.g. use backup methods
      *                      for a restore logger and vice versa).
+     *
+     * @hide
      */
     public BackupRestoreEventLogger(@OperationType int operationType) {
         mOperationType = operationType;
@@ -111,11 +115,9 @@
      *
      * @param dataType the type of data being backed.
      * @param count number of items of the given type that have been successfully backed up.
-     *
-     * @return boolean, indicating whether the log has been accepted.
      */
-    public boolean logItemsBackedUp(@NonNull @BackupRestoreDataType String dataType, int count) {
-        return logSuccess(OperationType.BACKUP, dataType, count);
+    public void logItemsBackedUp(@NonNull @BackupRestoreDataType String dataType, int count) {
+        logSuccess(OperationType.BACKUP, dataType, count);
     }
 
     /**
@@ -130,12 +132,10 @@
      * @param dataType the type of data being backed.
      * @param count number of items of the given type that have failed to back up.
      * @param error optional, the error that has caused the failure.
-     *
-     * @return boolean, indicating whether the log has been accepted.
      */
-    public boolean logItemsBackupFailed(@NonNull @BackupRestoreDataType String dataType, int count,
+    public void logItemsBackupFailed(@NonNull @BackupRestoreDataType String dataType, int count,
             @Nullable @BackupRestoreError String error) {
-        return logFailure(OperationType.BACKUP, dataType, count, error);
+        logFailure(OperationType.BACKUP, dataType, count, error);
     }
 
     /**
@@ -151,12 +151,10 @@
      *
      * @param dataType the type of data being backed up.
      * @param metaData the metadata associated with the data type.
-     *
-     * @return boolean, indicating whether the log has been accepted.
      */
-    public boolean logBackupMetaData(@NonNull @BackupRestoreDataType String dataType,
+    public void logBackupMetaData(@NonNull @BackupRestoreDataType String dataType,
             @NonNull String metaData) {
-        return logMetaData(OperationType.BACKUP, dataType, metaData);
+        logMetaData(OperationType.BACKUP, dataType, metaData);
     }
 
     /**
@@ -172,11 +170,9 @@
      *
      * @param dataType the type of data being restored.
      * @param count number of items of the given type that have been successfully restored.
-     *
-     * @return boolean, indicating whether the log has been accepted.
      */
-    public boolean logItemsRestored(@NonNull @BackupRestoreDataType String dataType, int count) {
-        return logSuccess(OperationType.RESTORE, dataType, count);
+    public void logItemsRestored(@NonNull @BackupRestoreDataType String dataType, int count) {
+        logSuccess(OperationType.RESTORE, dataType, count);
     }
 
     /**
@@ -193,12 +189,10 @@
      * @param dataType the type of data being restored.
      * @param count number of items of the given type that have failed to restore.
      * @param error optional, the error that has caused the failure.
-     *
-     * @return boolean, indicating whether the log has been accepted.
      */
-    public boolean logItemsRestoreFailed(@NonNull @BackupRestoreDataType String dataType, int count,
+    public void logItemsRestoreFailed(@NonNull @BackupRestoreDataType String dataType, int count,
             @Nullable @BackupRestoreError String error) {
-        return logFailure(OperationType.RESTORE, dataType, count, error);
+        logFailure(OperationType.RESTORE, dataType, count, error);
     }
 
     /**
@@ -216,12 +210,10 @@
      *
      * @param dataType the type of data being restored.
      * @param metadata the metadata associated with the data type.
-     *
-     * @return boolean, indicating whether the log has been accepted.
      */
-    public boolean logRestoreMetadata(@NonNull @BackupRestoreDataType String dataType,
+    public void logRestoreMetadata(@NonNull @BackupRestoreDataType String dataType,
             @NonNull  String metadata) {
-        return logMetaData(OperationType.RESTORE, dataType, metadata);
+        logMetaData(OperationType.RESTORE, dataType, metadata);
     }
 
     /**
@@ -240,52 +232,47 @@
      *
      * @hide
      */
-    public @OperationType int getOperationType() {
+    @OperationType
+    public int getOperationType() {
         return mOperationType;
     }
 
-    private boolean logSuccess(@OperationType int operationType,
+    private void logSuccess(@OperationType int operationType,
             @BackupRestoreDataType String dataType, int count) {
         DataTypeResult dataTypeResult = getDataTypeResult(operationType, dataType);
         if (dataTypeResult == null) {
-            return false;
+            return;
         }
 
         dataTypeResult.mSuccessCount += count;
         mResults.put(dataType, dataTypeResult);
-
-        return true;
     }
 
-    private boolean logFailure(@OperationType int operationType,
+    private void logFailure(@OperationType int operationType,
             @NonNull @BackupRestoreDataType String dataType, int count,
             @Nullable @BackupRestoreError String error) {
         DataTypeResult dataTypeResult = getDataTypeResult(operationType, dataType);
         if (dataTypeResult == null) {
-            return false;
+            return;
         }
 
         dataTypeResult.mFailCount += count;
         if (error != null) {
             dataTypeResult.mErrors.merge(error, count, Integer::sum);
         }
-
-        return true;
     }
 
-    private boolean logMetaData(@OperationType int operationType,
+    private void logMetaData(@OperationType int operationType,
             @NonNull @BackupRestoreDataType String dataType, @NonNull String metaData) {
         if (mHashDigest == null) {
-            return false;
+            return;
         }
         DataTypeResult dataTypeResult = getDataTypeResult(operationType, dataType);
         if (dataTypeResult == null) {
-            return false;
+            return;
         }
 
         dataTypeResult.mMetadataHash = getMetaDataHash(metaData);
-
-        return true;
     }
 
     /**
diff --git a/core/java/android/app/search/SearchSession.java b/core/java/android/app/search/SearchSession.java
index 2cd1d96..10db337 100644
--- a/core/java/android/app/search/SearchSession.java
+++ b/core/java/android/app/search/SearchSession.java
@@ -23,9 +23,11 @@
 import android.content.Context;
 import android.content.pm.ParceledListSlice;
 import android.os.Binder;
+import android.os.Bundle;
 import android.os.IBinder;
 import android.os.RemoteException;
 import android.os.ServiceManager;
+import android.os.SystemClock;
 import android.util.Log;
 
 import dalvik.system.CloseGuard;
@@ -70,7 +72,7 @@
  * @hide
  */
 @SystemApi
-public final class SearchSession implements AutoCloseable{
+public final class SearchSession implements AutoCloseable {
 
     private static final String TAG = SearchSession.class.getSimpleName();
     private static final boolean DEBUG = false;
@@ -229,7 +231,14 @@
                 if (DEBUG) {
                     Log.d(TAG, "CallbackWrapper.onResult result=" + result.getList());
                 }
-                mExecutor.execute(() -> mCallback.accept(result.getList()));
+                List<SearchTarget> list = result.getList();
+                if (list.size() > 0) {
+                    Bundle bundle = list.get(0).getExtras();
+                    if (bundle != null) {
+                        bundle.putLong("key_ipc_start", SystemClock.elapsedRealtime());
+                    }
+                }
+                mExecutor.execute(() -> mCallback.accept(list));
             } finally {
                 Binder.restoreCallingIdentity(identity);
             }
diff --git a/core/java/android/companion/virtual/IVirtualDeviceManager.aidl b/core/java/android/companion/virtual/IVirtualDeviceManager.aidl
index 82d7534..7d6336a 100644
--- a/core/java/android/companion/virtual/IVirtualDeviceManager.aidl
+++ b/core/java/android/companion/virtual/IVirtualDeviceManager.aidl
@@ -52,6 +52,11 @@
     List<VirtualDevice> getVirtualDevices();
 
     /**
+     * Returns the device policy for the given virtual device and policy type.
+     */
+    int getDevicePolicy(int deviceId, int policyType);
+
+    /**
      * Creates a virtual display owned by a particular virtual device.
      *
      * @param virtualDisplayConfig The configuration used in creating the display
diff --git a/core/java/android/companion/virtual/VirtualDeviceManager.java b/core/java/android/companion/virtual/VirtualDeviceManager.java
index 0bb86fb..c14bb1b 100644
--- a/core/java/android/companion/virtual/VirtualDeviceManager.java
+++ b/core/java/android/companion/virtual/VirtualDeviceManager.java
@@ -182,6 +182,28 @@
     }
 
     /**
+     * Returns the device policy for the given virtual device and policy type.
+     *
+     * <p>In case the virtual device identifier is not valid, or there's no explicitly specified
+     * policy for that device and policy type, then
+     * {@link VirtualDeviceParams#DEVICE_POLICY_DEFAULT} is returned.
+     *
+     * @hide
+     */
+    public @VirtualDeviceParams.DevicePolicy int getDevicePolicy(
+            int deviceId, @VirtualDeviceParams.PolicyType int policyType) {
+        if (mService == null) {
+            Log.w(TAG, "Failed to retrieve device policy; no virtual device manager service.");
+            return VirtualDeviceParams.DEVICE_POLICY_DEFAULT;
+        }
+        try {
+            return mService.getDevicePolicy(deviceId, policyType);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
      * A virtual device has its own virtual display, audio output, microphone, and camera etc. The
      * creator of a virtual device can take the output from the virtual display and stream it over
      * to another device, and inject input events that are received from the remote device.
diff --git a/core/java/android/companion/virtual/VirtualDeviceParams.java b/core/java/android/companion/virtual/VirtualDeviceParams.java
index d40c9d6..c6e6f83 100644
--- a/core/java/android/companion/virtual/VirtualDeviceParams.java
+++ b/core/java/android/companion/virtual/VirtualDeviceParams.java
@@ -28,6 +28,7 @@
 import android.os.Parcelable;
 import android.os.UserHandle;
 import android.util.ArraySet;
+import android.util.SparseIntArray;
 
 import com.android.internal.util.Preconditions;
 
@@ -103,6 +104,47 @@
      */
     public static final int NAVIGATION_POLICY_DEFAULT_BLOCKED = 1;
 
+    /** @hide */
+    @IntDef(prefix = "DEVICE_POLICY_",  value = {DEVICE_POLICY_DEFAULT, DEVICE_POLICY_CUSTOM})
+    @Retention(RetentionPolicy.SOURCE)
+    @Target({ElementType.TYPE_PARAMETER, ElementType.TYPE_USE})
+    public @interface DevicePolicy {}
+
+    /**
+     * Indicates that there is no special logic for this virtual device and it should be treated
+     * the same way as the default device, keeping the default behavior unchanged.
+     */
+    public static final int DEVICE_POLICY_DEFAULT = 0;
+
+    /**
+     * Indicates that there is custom logic, specific to this virtual device, which should be
+     * triggered instead of the default behavior.
+     */
+    public static final int DEVICE_POLICY_CUSTOM = 1;
+
+    /**
+     * Any relevant component must be able to interpret the correct meaning of a custom policy for
+     * a given policy type.
+     * @hide
+     */
+    @IntDef(prefix = "POLICY_TYPE_",  value = {POLICY_TYPE_SENSORS})
+    @Retention(RetentionPolicy.SOURCE)
+    @Target({ElementType.TYPE_PARAMETER, ElementType.TYPE_USE})
+    public @interface PolicyType {}
+
+    /**
+     * Tells the sensor framework how to handle sensor requests from contexts associated with this
+     * virtual device, namely the sensors returned by
+     * {@link android.hardware.SensorManager#getSensorList}:
+     *
+     * <ul>
+     *     <li>{@link #DEVICE_POLICY_DEFAULT}: Return the sensors of the default device.
+     *     <li>{@link #DEVICE_POLICY_CUSTOM}: Return the sensors of the virtual device. Note that if
+     *     the virtual device did not create any virtual sensors, then an empty list is returned.
+     * </ul>
+     */
+    public static final int POLICY_TYPE_SENSORS = 0;
+
     private final int mLockState;
     @NonNull private final ArraySet<UserHandle> mUsersWithMatchingAccounts;
     @NonNull private final ArraySet<ComponentName> mAllowedCrossTaskNavigations;
@@ -114,6 +156,8 @@
     @ActivityPolicy
     private final int mDefaultActivityPolicy;
     @Nullable private final String mName;
+    // Mapping of @PolicyType to @DevicePolicy
+    @NonNull private final SparseIntArray mDevicePolicies;
 
     private VirtualDeviceParams(
             @LockState int lockState,
@@ -124,12 +168,14 @@
             @NonNull Set<ComponentName> allowedActivities,
             @NonNull Set<ComponentName> blockedActivities,
             @ActivityPolicy int defaultActivityPolicy,
-            @Nullable String name) {
+            @Nullable String name,
+            @NonNull SparseIntArray devicePolicies) {
         Preconditions.checkNotNull(usersWithMatchingAccounts);
         Preconditions.checkNotNull(allowedCrossTaskNavigations);
         Preconditions.checkNotNull(blockedCrossTaskNavigations);
         Preconditions.checkNotNull(allowedActivities);
         Preconditions.checkNotNull(blockedActivities);
+        Preconditions.checkNotNull(devicePolicies);
 
         mLockState = lockState;
         mUsersWithMatchingAccounts = new ArraySet<>(usersWithMatchingAccounts);
@@ -140,6 +186,7 @@
         mBlockedActivities = new ArraySet<>(blockedActivities);
         mDefaultActivityPolicy = defaultActivityPolicy;
         mName = name;
+        mDevicePolicies = devicePolicies;
     }
 
     @SuppressWarnings("unchecked")
@@ -153,6 +200,7 @@
         mBlockedActivities = (ArraySet<ComponentName>) parcel.readArraySet(null);
         mDefaultActivityPolicy = parcel.readInt();
         mName = parcel.readString8();
+        mDevicePolicies = parcel.readSparseIntArray();
     }
 
     /**
@@ -258,6 +306,16 @@
         return mName;
     }
 
+    /**
+     * Returns the policy specified for this policy type, or {@link #DEVICE_POLICY_DEFAULT} if no
+     * policy for this type has been explicitly specified.
+     *
+     * @see Builder#addDevicePolicy
+     */
+    public @DevicePolicy int getDevicePolicy(@PolicyType int policyType) {
+        return mDevicePolicies.get(policyType, DEVICE_POLICY_DEFAULT);
+    }
+
     @Override
     public int describeContents() {
         return 0;
@@ -274,6 +332,7 @@
         dest.writeArraySet(mBlockedActivities);
         dest.writeInt(mDefaultActivityPolicy);
         dest.writeString8(mName);
+        dest.writeSparseIntArray(mDevicePolicies);
     }
 
     @Override
@@ -285,6 +344,18 @@
             return false;
         }
         VirtualDeviceParams that = (VirtualDeviceParams) o;
+        final int devicePoliciesCount = mDevicePolicies.size();
+        if (devicePoliciesCount != that.mDevicePolicies.size()) {
+            return false;
+        }
+        for (int i = 0; i < devicePoliciesCount; i++) {
+            if (mDevicePolicies.keyAt(i) != that.mDevicePolicies.keyAt(i)) {
+                return false;
+            }
+            if (mDevicePolicies.valueAt(i) != that.mDevicePolicies.valueAt(i)) {
+                return false;
+            }
+        }
         return mLockState == that.mLockState
                 && mUsersWithMatchingAccounts.equals(that.mUsersWithMatchingAccounts)
                 && Objects.equals(mAllowedCrossTaskNavigations, that.mAllowedCrossTaskNavigations)
@@ -298,10 +369,15 @@
 
     @Override
     public int hashCode() {
-        return Objects.hash(
+        int hashCode = Objects.hash(
                 mLockState, mUsersWithMatchingAccounts, mAllowedCrossTaskNavigations,
                 mBlockedCrossTaskNavigations, mDefaultNavigationPolicy,  mAllowedActivities,
-                mBlockedActivities, mDefaultActivityPolicy, mName);
+                mBlockedActivities, mDefaultActivityPolicy, mName, mDevicePolicies);
+        for (int i = 0; i < mDevicePolicies.size(); i++) {
+            hashCode = 31 * hashCode + mDevicePolicies.keyAt(i);
+            hashCode = 31 * hashCode + mDevicePolicies.valueAt(i);
+        }
+        return hashCode;
     }
 
     @Override
@@ -317,6 +393,7 @@
                 + " mBlockedActivities=" + mBlockedActivities
                 + " mDefaultActivityPolicy=" + mDefaultActivityPolicy
                 + " mName=" + mName
+                + " mDevicePolicies=" + mDevicePolicies
                 + ")";
     }
 
@@ -350,6 +427,7 @@
         private int mDefaultActivityPolicy = ACTIVITY_POLICY_DEFAULT_ALLOWED;
         private boolean mDefaultActivityPolicyConfigured = false;
         @Nullable private String mName;
+        @NonNull private SparseIntArray mDevicePolicies = new SparseIntArray();
 
         /**
          * Sets the lock state of the device. The permission {@code ADD_ALWAYS_UNLOCKED_DISPLAY}
@@ -528,6 +606,18 @@
         }
 
         /**
+         * Specifies a policy for this virtual device.
+         *
+         * @param policyType the type of policy, i.e. which behavior to specify a policy for.
+         * @param devicePolicy the value of the policy, i.e. how to interpret the device behavior.
+         */
+        @NonNull
+        public Builder addDevicePolicy(@PolicyType int policyType, @DevicePolicy int devicePolicy) {
+            mDevicePolicies.put(policyType, devicePolicy);
+            return this;
+        }
+
+        /**
          * Builds the {@link VirtualDeviceParams} instance.
          */
         @NonNull
@@ -541,7 +631,8 @@
                     mAllowedActivities,
                     mBlockedActivities,
                     mDefaultActivityPolicy,
-                    mName);
+                    mName,
+                    mDevicePolicies);
         }
     }
 }
diff --git a/core/java/android/content/ActivityNotFoundException.java b/core/java/android/content/ActivityNotFoundException.java
index 16149bb..5b50189 100644
--- a/core/java/android/content/ActivityNotFoundException.java
+++ b/core/java/android/content/ActivityNotFoundException.java
@@ -31,5 +31,4 @@
     {
         super(name);
     }
-};
-
+}
diff --git a/core/java/android/content/integrity/AtomicFormula.java b/core/java/android/content/integrity/AtomicFormula.java
index f888813..1b5f64c 100644
--- a/core/java/android/content/integrity/AtomicFormula.java
+++ b/core/java/android/content/integrity/AtomicFormula.java
@@ -261,8 +261,8 @@
             }
             LongAtomicFormula that = (LongAtomicFormula) o;
             return getKey() == that.getKey()
-                    && mValue == that.mValue
-                    && mOperator == that.mOperator;
+                    && Objects.equals(mValue, that.mValue)
+                    && Objects.equals(mOperator, that.mOperator);
         }
 
         @Override
@@ -628,7 +628,7 @@
                 return false;
             }
             BooleanAtomicFormula that = (BooleanAtomicFormula) o;
-            return getKey() == that.getKey() && mValue == that.mValue;
+            return getKey() == that.getKey() && Objects.equals(mValue, that.mValue);
         }
 
         @Override
diff --git a/core/java/android/content/om/FabricatedOverlay.java b/core/java/android/content/om/FabricatedOverlay.java
index dbefa65..cc7977a 100644
--- a/core/java/android/content/om/FabricatedOverlay.java
+++ b/core/java/android/content/om/FabricatedOverlay.java
@@ -26,6 +26,7 @@
 import com.android.internal.util.Preconditions;
 
 import java.util.ArrayList;
+import java.util.Objects;
 
 /**
  * Fabricated Runtime Resource Overlays (FRROs) are overlays generated ar runtime.
@@ -89,6 +90,27 @@
         }
 
         /**
+         * Ensure the resource name is in the form [package]:type/entry.
+         *
+         * @param name name of the target resource to overlay (in the form [package]:type/entry)
+         * @return the valid name
+         */
+        private static String ensureValidResourceName(@NonNull String name) {
+            Objects.requireNonNull(name);
+            final int slashIndex = name.indexOf('/'); /* must contain '/' */
+            final int colonIndex = name.indexOf(':'); /* ':' should before '/' if ':' exist */
+
+            // The minimum length of resource type is "id".
+            Preconditions.checkArgument(
+                    slashIndex >= 0 /* It must contain the type name */
+                    && colonIndex != 0 /* 0 means the package name is empty */
+                    && (slashIndex - colonIndex) > 2 /* The shortest length of type is "id" */,
+                    "\"%s\" is invalid resource name",
+                    name);
+            return name;
+        }
+
+        /**
          * Sets the value of the fabricated overlay
          *
          * @param resourceName name of the target resource to overlay (in the form
@@ -99,6 +121,8 @@
          * @see android.util.TypedValue#type
          */
         public Builder setResourceValue(@NonNull String resourceName, int dataType, int value) {
+            ensureValidResourceName(resourceName);
+
             final FabricatedOverlayInternalEntry entry = new FabricatedOverlayInternalEntry();
             entry.resourceName = resourceName;
             entry.dataType = dataType;
@@ -120,6 +144,8 @@
          */
         public Builder setResourceValue(@NonNull String resourceName, int dataType, int value,
                 String configuration) {
+            ensureValidResourceName(resourceName);
+
             final FabricatedOverlayInternalEntry entry = new FabricatedOverlayInternalEntry();
             entry.resourceName = resourceName;
             entry.dataType = dataType;
@@ -140,6 +166,8 @@
          * @see android.util.TypedValue#type
          */
         public Builder setResourceValue(@NonNull String resourceName, int dataType, String value) {
+            ensureValidResourceName(resourceName);
+
             final FabricatedOverlayInternalEntry entry = new FabricatedOverlayInternalEntry();
             entry.resourceName = resourceName;
             entry.dataType = dataType;
@@ -161,6 +189,8 @@
          */
         public Builder setResourceValue(@NonNull String resourceName, int dataType, String value,
                 String configuration) {
+            ensureValidResourceName(resourceName);
+
             final FabricatedOverlayInternalEntry entry = new FabricatedOverlayInternalEntry();
             entry.resourceName = resourceName;
             entry.dataType = dataType;
@@ -180,6 +210,8 @@
          */
         public Builder setResourceValue(@NonNull String resourceName, ParcelFileDescriptor value,
                 String configuration) {
+            ensureValidResourceName(resourceName);
+
             final FabricatedOverlayInternalEntry entry = new FabricatedOverlayInternalEntry();
             entry.resourceName = resourceName;
             entry.binaryData = value;
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index aa86af9..485d04d 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -165,6 +165,21 @@
             "android.internal.PROPERTY_NO_APP_DATA_STORAGE";
 
     /**
+     * &lt;service&gt; level {@link android.content.pm.PackageManager.Property} tag specifying
+     * the actual use case of the service if it's foreground service with the type
+     * {@link ServiceInfo#FOREGROUND_SERVICE_TYPE_SPECIAL_USE}.
+     *
+     * <p>
+     * For example:
+     * &lt;service&gt;
+     *   &lt;property android:name="android.app.PROPERTY_SPECIAL_USE_FGS_SUBTYPE"
+     *     android:value="foo"/&gt;
+     * &lt;/service&gt;
+     */
+    public static final String PROPERTY_SPECIAL_USE_FGS_SUBTYPE =
+            "android.app.PROPERTY_SPECIAL_USE_FGS_SUBTYPE";
+
+    /**
      * A property value set within the manifest.
      * <p>
      * The value of a property will only have a single type, as defined by
@@ -189,12 +204,12 @@
         @VisibleForTesting
         public Property(@NonNull String name, int type,
                 @NonNull String packageName, @Nullable String className) {
-            assert name != null;
-            assert type >= TYPE_BOOLEAN && type <= TYPE_STRING;
-            assert packageName != null;
-            this.mName = name;
+            if (type < TYPE_BOOLEAN || type > TYPE_STRING) {
+                throw new IllegalArgumentException("Invalid type");
+            }
+            this.mName = Objects.requireNonNull(name);
             this.mType = type;
-            this.mPackageName = packageName;
+            this.mPackageName = Objects.requireNonNull(packageName);
             this.mClassName = className;
         }
         /** @hide */
@@ -442,9 +457,8 @@
          */
         public ComponentEnabledSetting(@NonNull ComponentName componentName,
                 @EnabledState int newState, @EnabledFlags int flags) {
-            Objects.nonNull(componentName);
             mPackageName = null;
-            mComponentName = componentName;
+            mComponentName = Objects.requireNonNull(componentName);
             mEnabledState = newState;
             mEnabledFlags = flags;
         }
@@ -460,8 +474,7 @@
          */
         public ComponentEnabledSetting(@NonNull String packageName,
                 @EnabledState int newState, @EnabledFlags int flags) {
-            Objects.nonNull(packageName);
-            mPackageName = packageName;
+            mPackageName = Objects.requireNonNull(packageName);
             mComponentName = null;
             mEnabledState = newState;
             mEnabledFlags = flags;
@@ -2946,6 +2959,18 @@
     public static final String FEATURE_OPENGLES_EXTENSION_PACK = "android.hardware.opengles.aep";
 
     /**
+     * Feature for {@link #getSystemAvailableFeatures()} and {@link #hasSystemFeature(String)}.
+     * This feature indicates whether device supports
+     * <a href="https://source.android.com/docs/core/virtualization">Android Virtualization Framework</a>.
+     *
+     * @hide
+     */
+    @SystemApi
+    @SdkConstant(SdkConstantType.FEATURE)
+    public static final String FEATURE_VIRTUALIZATION_FRAMEWORK =
+            "android.software.virtualization_framework";
+
+    /**
      * Feature for {@link #getSystemAvailableFeatures} and
      * {@link #hasSystemFeature(String, int)}: If this feature is supported, the Vulkan
      * implementation on this device is hardware accelerated, and the Vulkan native API will
@@ -3407,7 +3432,6 @@
      * Feature for {@link #getSystemAvailableFeatures} and
      * {@link #hasSystemFeature}: The device is capable of communicating with
      * other devices via ultra wideband.
-     * @hide
      */
     @SdkConstant(SdkConstantType.FEATURE)
     public static final String FEATURE_UWB = "android.hardware.uwb";
diff --git a/core/java/android/content/pm/ServiceInfo.java b/core/java/android/content/pm/ServiceInfo.java
index 88d7004..ab20b6f 100644
--- a/core/java/android/content/pm/ServiceInfo.java
+++ b/core/java/android/content/pm/ServiceInfo.java
@@ -16,7 +16,9 @@
 
 package android.content.pm;
 
+import android.Manifest;
 import android.annotation.IntDef;
+import android.annotation.RequiresPermission;
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.util.Printer;
@@ -100,7 +102,15 @@
 
     /**
      * The default foreground service type if not been set in manifest file.
+     *
+     * <p>Apps targeting API level {@link android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE} and
+     * later should NOT use this type,
+     * calling {@link android.app.Service#startForeground(int, android.app.Notification, int)} with
+     * this type will get a {@link android.app.ForegroundServiceTypeNotAllowedException}.</p>
+     *
+     * @deprecated Do not use.
      */
+    @Deprecated
     public static final int FOREGROUND_SERVICE_TYPE_NONE = 0;
 
     /**
@@ -108,14 +118,36 @@
      * the {@link android.R.attr#foregroundServiceType} attribute.
      * Data(photo, file, account) upload/download, backup/restore, import/export, fetch,
      * transfer over network between device and cloud.
+     *
+     * <p>Apps targeting API level {@link android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE} and
+     * later should NOT use this type:
+     * calling {@link android.app.Service#startForeground(int, android.app.Notification, int)} with
+     * this type on devices running {@link android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE} is still
+     * allowed, but calling it with this type on devices running future platform releases may get a
+     * {@link android.app.ForegroundServiceTypeNotAllowedException}.</p>
+     *
+     * @deprecated Use {@link android.app.job.JobInfo.Builder} data transfer APIs instead.
      */
+    @RequiresPermission(
+            value = Manifest.permission.FOREGROUND_SERVICE_DATA_SYNC,
+            conditional = true
+    )
+    @Deprecated
     public static final int FOREGROUND_SERVICE_TYPE_DATA_SYNC = 1 << 0;
 
     /**
      * Constant corresponding to <code>mediaPlayback</code> in
      * the {@link android.R.attr#foregroundServiceType} attribute.
      * Music, video, news or other media playback.
+     *
+     * <p>Starting foreground service with this type from apps targeting API level
+     * {@link android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE} and later, will require permission
+     * {@link android.Manifest.permission#FOREGROUND_SERVICE_MEDIA_PLAYBACK}.
      */
+    @RequiresPermission(
+            value = Manifest.permission.FOREGROUND_SERVICE_MEDIA_PLAYBACK,
+            conditional = true
+    )
     public static final int FOREGROUND_SERVICE_TYPE_MEDIA_PLAYBACK = 1 << 1;
 
     /**
@@ -123,28 +155,94 @@
      * the {@link android.R.attr#foregroundServiceType} attribute.
      * Ongoing operations related to phone calls, video conferencing,
      * or similar interactive communication.
+     *
+     * <p>Starting foreground service with this type from apps targeting API level
+     * {@link android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE} and later, will require permission
+     * {@link android.Manifest.permission#FOREGROUND_SERVICE_PHONE_CALL} and
+     * {@link android.Manifest.permission#MANAGE_OWN_CALLS}.
      */
+    @RequiresPermission(
+            allOf = {
+                Manifest.permission.FOREGROUND_SERVICE_PHONE_CALL,
+            },
+            anyOf = {
+                Manifest.permission.MANAGE_OWN_CALLS,
+            },
+            conditional = true
+    )
     public static final int FOREGROUND_SERVICE_TYPE_PHONE_CALL = 1 << 2;
 
     /**
      * Constant corresponding to <code>location</code> in
      * the {@link android.R.attr#foregroundServiceType} attribute.
      * GPS, map, navigation location update.
+     *
+     * <p>Starting foreground service with this type from apps targeting API level
+     * {@link android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE} and later, will require permission
+     * {@link android.Manifest.permission#FOREGROUND_SERVICE_LOCATION} and one of the
+     * following permissions:
+     * {@link android.Manifest.permission#ACCESS_COARSE_LOCATION},
+     * {@link android.Manifest.permission#ACCESS_FINE_LOCATION}.
      */
+    @RequiresPermission(
+            allOf = {
+                Manifest.permission.FOREGROUND_SERVICE_LOCATION,
+            },
+            anyOf = {
+                Manifest.permission.ACCESS_COARSE_LOCATION,
+                Manifest.permission.ACCESS_FINE_LOCATION,
+            },
+            conditional = true
+    )
     public static final int FOREGROUND_SERVICE_TYPE_LOCATION = 1 << 3;
 
     /**
      * Constant corresponding to <code>connectedDevice</code> in
      * the {@link android.R.attr#foregroundServiceType} attribute.
      * Auto, bluetooth, TV or other devices connection, monitoring and interaction.
+     *
+     * <p>Starting foreground service with this type from apps targeting API level
+     * {@link android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE} and later, will require permission
+     * {@link android.Manifest.permission#FOREGROUND_SERVICE_CONNECTED_DEVICE} and one of the
+     * following permissions:
+     * {@link android.Manifest.permission#BLUETOOTH_CONNECT},
+     * {@link android.Manifest.permission#CHANGE_NETWORK_STATE},
+     * {@link android.Manifest.permission#CHANGE_WIFI_STATE},
+     * {@link android.Manifest.permission#CHANGE_WIFI_MULTICAST_STATE},
+     * {@link android.Manifest.permission#NFC},
+     * {@link android.Manifest.permission#TRANSMIT_IR},
+     * or has been granted the access to one of the attached USB devices/accessories.
      */
+    @RequiresPermission(
+            allOf = {
+                Manifest.permission.FOREGROUND_SERVICE_CONNECTED_DEVICE,
+            },
+            anyOf = {
+                Manifest.permission.BLUETOOTH_CONNECT,
+                Manifest.permission.CHANGE_NETWORK_STATE,
+                Manifest.permission.CHANGE_WIFI_STATE,
+                Manifest.permission.CHANGE_WIFI_MULTICAST_STATE,
+                Manifest.permission.NFC,
+                Manifest.permission.TRANSMIT_IR,
+            },
+            conditional = true
+    )
     public static final int FOREGROUND_SERVICE_TYPE_CONNECTED_DEVICE = 1 << 4;
 
     /**
      * Constant corresponding to {@code mediaProjection} in
      * the {@link android.R.attr#foregroundServiceType} attribute.
      * Managing a media projection session, e.g for screen recording or taking screenshots.
+     *
+     * <p>Starting foreground service with this type from apps targeting API level
+     * {@link android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE} and later, will require permission
+     * {@link android.Manifest.permission#FOREGROUND_SERVICE_MEDIA_PROJECTION}, and the user must
+     * have allowed the screen capture request from this app.
      */
+    @RequiresPermission(
+            value = Manifest.permission.FOREGROUND_SERVICE_MEDIA_PROJECTION,
+            conditional = true
+    )
     public static final int FOREGROUND_SERVICE_TYPE_MEDIA_PROJECTION = 1 << 5;
 
     /**
@@ -155,7 +253,21 @@
      * above, a foreground service will not be able to access the camera if this type is not
      * specified in the manifest and in
      * {@link android.app.Service#startForeground(int, android.app.Notification, int)}.
+     *
+     * <p>Starting foreground service with this type from apps targeting API level
+     * {@link android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE} and later, will require permission
+     * {@link android.Manifest.permission#FOREGROUND_SERVICE_CAMERA} and
+     * {@link android.Manifest.permission#CAMERA}.
      */
+    @RequiresPermission(
+            allOf = {
+                Manifest.permission.FOREGROUND_SERVICE_CAMERA,
+            },
+            anyOf = {
+                Manifest.permission.CAMERA,
+            },
+            conditional = true
+    )
     public static final int FOREGROUND_SERVICE_TYPE_CAMERA = 1 << 6;
 
     /**
@@ -166,17 +278,148 @@
      * above, a foreground service will not be able to access the microphone if this type is not
      * specified in the manifest and in
      * {@link android.app.Service#startForeground(int, android.app.Notification, int)}.
+     *
+     * <p>Starting foreground service with this type from apps targeting API level
+     * {@link android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE} and later, will require permission
+     * {@link android.Manifest.permission#FOREGROUND_SERVICE_MICROPHONE} and one of the following
+     * permissions:
+     * {@link android.Manifest.permission#CAPTURE_AUDIO_OUTPUT},
+     * {@link android.Manifest.permission#RECORD_AUDIO}.
      */
+    @RequiresPermission(
+            allOf = {
+                Manifest.permission.FOREGROUND_SERVICE_MICROPHONE,
+            },
+            anyOf = {
+                Manifest.permission.CAPTURE_AUDIO_OUTPUT,
+                Manifest.permission.RECORD_AUDIO,
+            },
+            conditional = true
+    )
     public static final int FOREGROUND_SERVICE_TYPE_MICROPHONE = 1 << 7;
 
     /**
-     * The number of foreground service types, this doesn't include
-     * the {@link #FOREGROUND_SERVICE_TYPE_MANIFEST} and {@link #FOREGROUND_SERVICE_TYPE_NONE}
-     * as they're not real service types.
+     * Constant corresponding to {@code health} in
+     * the {@link android.R.attr#foregroundServiceType} attribute.
+     * Health, wellness and fitness.
+     *
+     * <p>The caller app is required to have the permissions
+     * {@link android.Manifest.permission#FOREGROUND_SERVICE_HEALTH} and one of the following
+     * permissions:
+     * {@link android.Manifest.permission#ACTIVITY_RECOGNITION},
+     * {@link android.Manifest.permission#BODY_SENSORS},
+     * {@link android.Manifest.permission#HIGH_SAMPLING_RATE_SENSORS}.
+     */
+    @RequiresPermission(
+            allOf = {
+                Manifest.permission.FOREGROUND_SERVICE_HEALTH,
+            },
+            anyOf = {
+                Manifest.permission.ACTIVITY_RECOGNITION,
+                Manifest.permission.BODY_SENSORS,
+                Manifest.permission.HIGH_SAMPLING_RATE_SENSORS,
+            },
+            conditional = true
+    )
+    public static final int FOREGROUND_SERVICE_TYPE_HEALTH = 1 << 8;
+
+    /**
+     * Constant corresponding to {@code remoteMessaging} in
+     * the {@link android.R.attr#foregroundServiceType} attribute.
+     * Messaging use cases which host local server to relay messages across devices.
+     */
+    @RequiresPermission(
+            value = Manifest.permission.FOREGROUND_SERVICE_REMOTE_MESSAGING,
+            conditional = true
+    )
+    public static final int FOREGROUND_SERVICE_TYPE_REMOTE_MESSAGING = 1 << 9;
+
+    /**
+     * Constant corresponding to {@code systemExempted} in
+     * the {@link android.R.attr#foregroundServiceType} attribute.
+     * The system exmpted foreground service use cases.
+     *
+     * <p class="note">Note, apps are allowed to use this type only in the following cases:
+     * <ul>
+     *   <li>App has a UID &lt; {@link android.os.Process#FIRST_APPLICATION_UID}</li>
+     *   <li>App is on Doze allowlist</li>
+     *   <li>Device is running in <a href="https://android.googlesource.com/platform/frameworks/base/+/master/packages/SystemUI/docs/demo_mode.md">Demo Mode</a></li>
+     *   <li><a href="https://source.android.com/devices/tech/admin/provision">Device owner app</a><li>
+     *   <li><a href="https://source.android.com/devices/tech/admin/managed-profiles">Profile owner apps</a><li>
+     *   <li>Persistent apps</li>
+     *   <li><a href="https://source.android.com/docs/core/connect/carrier">Carrier privileged apps</a></li>
+     *   <li>Apps that have the {@code android.app.role.RoleManager#ROLE_EMERGENCY} role</li>
+     *   <li>Headless system apps</li>
+     *   <li><a href="{@docRoot}guide/topics/admin/device-admin">Device admin apps</a></li>
+     *   <li>Active VPN apps</li>
+     *   <li>Apps holding {@link Manifest.permission#SCHEDULE_EXACT_ALARM} or
+     *       {@link Manifest.permission#USE_EXACT_ALARM} permission.</li>
+     * </ul>
+     * </p>
+     */
+    @RequiresPermission(
+            value = Manifest.permission.FOREGROUND_SERVICE_SYSTEM_EXEMPTED,
+            conditional = true
+    )
+    public static final int FOREGROUND_SERVICE_TYPE_SYSTEM_EXEMPTED = 1 << 10;
+
+    /**
+     * Constant corresponding to {@code specialUse} in
+     * the {@link android.R.attr#foregroundServiceType} attribute.
+     * Use cases that can't be categorized into any other foreground service types, but also
+     * can't use {@link android.app.job.JobInfo.Builder} APIs.
+     *
+     * <p>The use of this foreground service type may be restricted. Additionally, apps must declare
+     * a service-level {@link PackageManager#PROPERTY_SPECIAL_USE_FGS_SUBTYPE &lt;property&gt;} in
+     * {@code AndroidManifest.xml} as a hint of what the exact use case here is.
+     * Here is an example:
+     * <pre>
+     *  &lt;uses-permission
+     *      android:name="android.permissions.FOREGROUND_SERVICE_SPECIAL_USE"
+     *  /&gt;
+     *  &lt;service
+     *      android:name=".MySpecialForegroundService"
+     *      android:foregroundServiceType="specialUse"&gt;
+     *      &lt;property
+     *          android:name="android.app.PROPERTY_SPECIAL_USE_FGS_SUBTYPE"
+     *          android:value="foo"
+     *      /&gt;
+     * &lt;/service&gt;
+     * </pre>
+     *
+     * In a future release of Android, if the above foreground service type {@code foo} is supported
+     * by the platform, to offer the backward compatibility, the app could specify
+     * the {@code android:maxSdkVersion} attribute in the &lt;uses-permission&gt; section,
+     * and also add the foreground service type {@code foo} into
+     * the {@code android:foregroundServiceType}, therefore the same app could be installed
+     * in both platforms.
+     * <pre>
+     *  &lt;uses-permission
+     *      android:name="android.permissions.FOREGROUND_SERVICE_SPECIAL_USE"
+     *      android:maxSdkVersion="last_sdk_version_without_type_foo"
+     *  /&gt;
+     *  &lt;service
+     *      android:name=".MySpecialForegroundService"
+     *      android:foregroundServiceType="specialUse|foo"&gt;
+     *      &lt;property
+     *          android:name="android.app.PROPERTY_SPECIAL_USE_FGS_SUBTYPE""
+     *          android:value="foo"
+     *      /&gt;
+     * &lt;/service&gt;
+     * </pre>
+     */
+    @RequiresPermission(
+            value = Manifest.permission.FOREGROUND_SERVICE_SPECIAL_USE,
+            conditional = true
+    )
+    public static final int FOREGROUND_SERVICE_TYPE_SPECIAL_USE = 1 << 30;
+
+    /**
+     * The max index being used in the definition of foreground service types.
      *
      * @hide
      */
-    public static final int NUM_OF_FOREGROUND_SERVICE_TYPES = 8;
+    public static final int FOREGROUND_SERVICE_TYPES_MAX_INDEX = 30;
 
     /**
      * A special value indicates to use all types set in manifest file.
@@ -199,7 +442,11 @@
             FOREGROUND_SERVICE_TYPE_CONNECTED_DEVICE,
             FOREGROUND_SERVICE_TYPE_MEDIA_PROJECTION,
             FOREGROUND_SERVICE_TYPE_CAMERA,
-            FOREGROUND_SERVICE_TYPE_MICROPHONE
+            FOREGROUND_SERVICE_TYPE_MICROPHONE,
+            FOREGROUND_SERVICE_TYPE_HEALTH,
+            FOREGROUND_SERVICE_TYPE_REMOTE_MESSAGING,
+            FOREGROUND_SERVICE_TYPE_SYSTEM_EXEMPTED,
+            FOREGROUND_SERVICE_TYPE_SPECIAL_USE
     })
     @Retention(RetentionPolicy.SOURCE)
     public @interface ForegroundServiceType {}
@@ -275,6 +522,14 @@
                 return "camera";
             case FOREGROUND_SERVICE_TYPE_MICROPHONE:
                 return "microphone";
+            case FOREGROUND_SERVICE_TYPE_HEALTH:
+                return "health";
+            case FOREGROUND_SERVICE_TYPE_REMOTE_MESSAGING:
+                return "remoteMessaging";
+            case FOREGROUND_SERVICE_TYPE_SYSTEM_EXEMPTED:
+                return "systemExempted";
+            case FOREGROUND_SERVICE_TYPE_SPECIAL_USE:
+                return "specialUse";
             default:
                 return "unknown";
         }
diff --git a/core/java/android/content/pm/ShortcutInfo.java b/core/java/android/content/pm/ShortcutInfo.java
index 1f83d75..295df5c 100644
--- a/core/java/android/content/pm/ShortcutInfo.java
+++ b/core/java/android/content/pm/ShortcutInfo.java
@@ -50,6 +50,7 @@
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.util.Preconditions;
 
+import java.lang.IllegalArgumentException;
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 import java.util.ArrayList;
@@ -1360,7 +1361,9 @@
         @NonNull
         public Builder setIntents(@NonNull Intent[] intents) {
             Objects.requireNonNull(intents, "intents cannot be null");
-            Objects.requireNonNull(intents.length, "intents cannot be empty");
+            if (intents.length == 0) {
+                throw new IllegalArgumentException("intents cannot be empty");
+            }
             for (Intent intent : intents) {
                 Objects.requireNonNull(intent, "intents cannot contain null");
                 Objects.requireNonNull(intent.getAction(), "intent's action must be set");
@@ -1398,7 +1401,9 @@
         @NonNull
         public Builder setPersons(@NonNull Person[] persons) {
             Objects.requireNonNull(persons, "persons cannot be null");
-            Objects.requireNonNull(persons.length, "persons cannot be empty");
+            if (persons.length == 0) {
+                throw new IllegalArgumentException("persons cannot be empty");
+            }
             for (Person person : persons) {
                 Objects.requireNonNull(person, "persons cannot contain null");
             }
diff --git a/core/java/android/content/pm/UserInfo.java b/core/java/android/content/pm/UserInfo.java
index 9baa6ba..2be0323 100644
--- a/core/java/android/content/pm/UserInfo.java
+++ b/core/java/android/content/pm/UserInfo.java
@@ -159,6 +159,18 @@
     public static final int FLAG_EPHEMERAL_ON_CREATE = 0x00002000;
 
     /**
+     * Indicates that this user is the designated main user on the device. This user may have access
+     * to certain features which are limited to at most one user.
+     *
+     * <p>Currently, this will be the first user to go through setup on the device, but in future
+     * releases this status may be transferable or may even not be given to any users.
+     *
+     * <p>This is not necessarily the system user. For example, it will not be the system user on
+     * devices for which {@link UserManager#isHeadlessSystemUserMode()} returns true.
+     */
+    public static final int FLAG_MAIN = 0x00004000;
+
+    /**
      * @hide
      */
     @IntDef(flag = true, prefix = "FLAG_", value = {
@@ -175,7 +187,8 @@
             FLAG_FULL,
             FLAG_SYSTEM,
             FLAG_PROFILE,
-            FLAG_EPHEMERAL_ON_CREATE
+            FLAG_EPHEMERAL_ON_CREATE,
+            FLAG_MAIN
     })
     @Retention(RetentionPolicy.SOURCE)
     public @interface UserInfoFlag {
@@ -369,6 +382,13 @@
     }
 
     /**
+     * @see #FLAG_MAIN
+     */
+    public boolean isMain() {
+        return (flags & FLAG_MAIN) == FLAG_MAIN;
+    }
+
+    /**
      * Returns true if the user is a split system user.
      * <p>If {@link UserManager#isSplitSystemUser split system user mode} is not enabled,
      * the method always returns false.
diff --git a/core/java/android/hardware/CameraInfo.java b/core/java/android/hardware/CameraInfo.java
index 072be50..41ef6aa 100644
--- a/core/java/android/hardware/CameraInfo.java
+++ b/core/java/android/hardware/CameraInfo.java
@@ -60,4 +60,4 @@
             return new CameraInfo[size];
         }
     };
-};
+}
diff --git a/core/java/android/hardware/CameraStatus.java b/core/java/android/hardware/CameraStatus.java
index 874af29..fa35efb 100644
--- a/core/java/android/hardware/CameraStatus.java
+++ b/core/java/android/hardware/CameraStatus.java
@@ -68,4 +68,4 @@
             return new CameraStatus[size];
         }
     };
-};
+}
diff --git a/core/java/android/hardware/biometrics/CryptoObject.java b/core/java/android/hardware/biometrics/CryptoObject.java
index d415706..267ef36 100644
--- a/core/java/android/hardware/biometrics/CryptoObject.java
+++ b/core/java/android/hardware/biometrics/CryptoObject.java
@@ -118,4 +118,4 @@
         }
         return AndroidKeyStoreProvider.getKeyStoreOperationHandle(mCrypto);
     }
-};
+}
diff --git a/core/java/android/hardware/camera2/impl/FrameNumberTracker.java b/core/java/android/hardware/camera2/impl/FrameNumberTracker.java
index 7b6a457..8304796 100644
--- a/core/java/android/hardware/camera2/impl/FrameNumberTracker.java
+++ b/core/java/android/hardware/camera2/impl/FrameNumberTracker.java
@@ -26,6 +26,7 @@
 import java.util.Iterator;
 import java.util.LinkedList;
 import java.util.List;
+import java.util.Map;
 import java.util.TreeMap;
 
 /**
@@ -63,11 +64,11 @@
     }
 
     private void update() {
-        Iterator iter = mFutureErrorMap.entrySet().iterator();
+        Iterator<Map.Entry<Long, Integer>> iter = mFutureErrorMap.entrySet().iterator();
         while (iter.hasNext()) {
-            TreeMap.Entry pair = (TreeMap.Entry)iter.next();
-            Long errorFrameNumber = (Long)pair.getKey();
-            int requestType = (int) pair.getValue();
+            Map.Entry<Long, Integer> pair = iter.next();
+            long errorFrameNumber = pair.getKey();
+            int requestType = pair.getValue();
             Boolean removeError = false;
             if (errorFrameNumber == mCompletedFrameNumber[requestType] + 1) {
                 removeError = true;
diff --git a/core/java/android/hardware/camera2/marshal/impl/MarshalQueryableEnum.java b/core/java/android/hardware/camera2/marshal/impl/MarshalQueryableEnum.java
index 621a418..92a2fb6 100644
--- a/core/java/android/hardware/camera2/marshal/impl/MarshalQueryableEnum.java
+++ b/core/java/android/hardware/camera2/marshal/impl/MarshalQueryableEnum.java
@@ -103,6 +103,7 @@
         return new MarshalerEnum(managedType, nativeType);
     }
 
+    @SuppressWarnings("ReturnValueIgnored")
     @Override
     public boolean isTypeMappingSupported(TypeReference<T> managedType, int nativeType) {
         if (nativeType == TYPE_INT32 || nativeType == TYPE_BYTE) {
diff --git a/core/java/android/hardware/camera2/params/OutputConfiguration.java b/core/java/android/hardware/camera2/params/OutputConfiguration.java
index a0d8f8d..f4b87b9 100644
--- a/core/java/android/hardware/camera2/params/OutputConfiguration.java
+++ b/core/java/android/hardware/camera2/params/OutputConfiguration.java
@@ -1344,7 +1344,8 @@
                 return false;
             }
             for (int j = 0; j < mSensorPixelModesUsed.size(); j++) {
-                if (mSensorPixelModesUsed.get(j) != other.mSensorPixelModesUsed.get(j)) {
+                if (!Objects.equals(
+                        mSensorPixelModesUsed.get(j), other.mSensorPixelModesUsed.get(j))) {
                     return false;
                 }
             }
diff --git a/core/java/android/hardware/fingerprint/Fingerprint.java b/core/java/android/hardware/fingerprint/Fingerprint.java
index 9ce834ca..c01c94c 100644
--- a/core/java/android/hardware/fingerprint/Fingerprint.java
+++ b/core/java/android/hardware/fingerprint/Fingerprint.java
@@ -69,4 +69,4 @@
             return new Fingerprint[size];
         }
     };
-};
\ No newline at end of file
+}
\ No newline at end of file
diff --git a/core/java/android/hardware/usb/IUsbManager.aidl b/core/java/android/hardware/usb/IUsbManager.aidl
index 5a44244..51236fe3 100644
--- a/core/java/android/hardware/usb/IUsbManager.aidl
+++ b/core/java/android/hardware/usb/IUsbManager.aidl
@@ -79,9 +79,20 @@
     /* Returns true if the caller has permission to access the device. */
     boolean hasDevicePermission(in UsbDevice device, String packageName);
 
+    /* Returns true if the given package/pid/uid has permission to access the device. */
+    @JavaPassthrough(annotation=
+            "@android.annotation.RequiresPermission(android.Manifest.permission.MANAGE_USB)")
+    boolean hasDevicePermissionWithIdentity(in UsbDevice device, String packageName,
+            int pid, int uid);
+
     /* Returns true if the caller has permission to access the accessory. */
     boolean hasAccessoryPermission(in UsbAccessory accessory);
 
+    /* Returns true if the given pid/uid has permission to access the accessory. */
+    @JavaPassthrough(annotation=
+            "@android.annotation.RequiresPermission(android.Manifest.permission.MANAGE_USB)")
+    boolean hasAccessoryPermissionWithIdentity(in UsbAccessory accessory, int pid, int uid);
+
     /* Requests permission for the given package to access the device.
      * Will display a system dialog to query the user if permission
      * had not already been given.
diff --git a/core/java/android/hardware/usb/UsbManager.java b/core/java/android/hardware/usb/UsbManager.java
index 2c38f70..50dd0064 100644
--- a/core/java/android/hardware/usb/UsbManager.java
+++ b/core/java/android/hardware/usb/UsbManager.java
@@ -838,6 +838,28 @@
     }
 
     /**
+     * Returns true if the caller has permission to access the device. It's similar to the
+     * {@link #hasPermission(UsbDevice)} but allows to specify a different package/uid/pid.
+     *
+     * <p>Not for third-party apps.</p>
+     *
+     * @hide
+     */
+    @RequiresPermission(Manifest.permission.MANAGE_USB)
+    @RequiresFeature(PackageManager.FEATURE_USB_HOST)
+    public boolean hasPermission(@NonNull UsbDevice device, @NonNull String packageName,
+            int pid, int uid) {
+        if (mService == null) {
+            return false;
+        }
+        try {
+            return mService.hasDevicePermissionWithIdentity(device, packageName, pid, uid);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
      * Returns true if the caller has permission to access the accessory.
      * Permission might have been granted temporarily via
      * {@link #requestPermission(UsbAccessory, PendingIntent)} or
@@ -859,6 +881,27 @@
     }
 
     /**
+     * Returns true if the caller has permission to access the accessory. It's similar to the
+     * {@link #hasPermission(UsbAccessory)} but allows to specify a different uid/pid.
+     *
+     * <p>Not for third-party apps.</p>
+     *
+     * @hide
+     */
+    @RequiresPermission(Manifest.permission.MANAGE_USB)
+    @RequiresFeature(PackageManager.FEATURE_USB_ACCESSORY)
+    public boolean hasPermission(@NonNull UsbAccessory accessory, int pid, int uid) {
+        if (mService == null) {
+            return false;
+        }
+        try {
+            return mService.hasAccessoryPermissionWithIdentity(accessory, pid, uid);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+   /**
      * Requests temporary permission for the given package to access the device.
      * This may result in a system dialog being displayed to the user
      * if permission had not already been granted.
diff --git a/core/java/android/net/netstats/NetworkStatsDataMigrationUtils.java b/core/java/android/net/netstats/NetworkStatsDataMigrationUtils.java
index f8e2558..d4b76c8 100644
--- a/core/java/android/net/netstats/NetworkStatsDataMigrationUtils.java
+++ b/core/java/android/net/netstats/NetworkStatsDataMigrationUtils.java
@@ -53,8 +53,8 @@
 import java.net.ProtocolException;
 import java.util.ArrayList;
 import java.util.Arrays;
-import java.util.HashMap;
 import java.util.HashSet;
+import java.util.Map;
 import java.util.Set;
 
 /**
@@ -89,12 +89,10 @@
     @Retention(RetentionPolicy.SOURCE)
     public @interface Prefix {}
 
-    private static final HashMap<String, String> sPrefixLegacyFileNameMap =
-            new HashMap<String, String>() {{
-                put(PREFIX_XT, "netstats_xt.bin");
-                put(PREFIX_UID, "netstats_uid.bin");
-                put(PREFIX_UID_TAG, "netstats_uid.bin");
-            }};
+    private static final Map<String, String> sPrefixLegacyFileNameMap = Map.of(
+            PREFIX_XT, "netstats_xt.bin",
+            PREFIX_UID, "netstats_uid.bin",
+            PREFIX_UID_TAG, "netstats_uid.bin");
 
     // These version constants are copied from NetworkStatsCollection/History, which is okay for
     // OEMs to modify to adapt their own logic.
diff --git a/core/java/android/net/vcn/persistablebundleutils/TunnelConnectionParamsUtils.java b/core/java/android/net/vcn/persistablebundleutils/TunnelConnectionParamsUtils.java
index 4bc5b49..0427742 100644
--- a/core/java/android/net/vcn/persistablebundleutils/TunnelConnectionParamsUtils.java
+++ b/core/java/android/net/vcn/persistablebundleutils/TunnelConnectionParamsUtils.java
@@ -53,7 +53,7 @@
         if (in.keySet().size() != EXPECTED_BUNDLE_KEY_CNT) {
             throw new IllegalArgumentException(
                     String.format(
-                            "Expect PersistableBundle to have %d element but found: %d",
+                            "Expect PersistableBundle to have %d element but found: %s",
                             EXPECTED_BUNDLE_KEY_CNT, in.keySet()));
         }
 
diff --git a/core/java/android/os/Process.java b/core/java/android/os/Process.java
index e483328..ac1583a 100644
--- a/core/java/android/os/Process.java
+++ b/core/java/android/os/Process.java
@@ -895,9 +895,21 @@
         return isIsolated(myUid());
     }
 
-    /** {@hide} */
-    @UnsupportedAppUsage
+    /**
+     * @deprecated Use {@link #isIsolatedUid(int)} instead.
+     * {@hide}
+     */
+    @Deprecated
+    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.TIRAMISU,
+            publicAlternatives = "Use {@link #isIsolatedUid(int)} instead.")
     public static final boolean isIsolated(int uid) {
+        return isIsolatedUid(uid);
+    }
+
+    /**
+     * Returns whether the process with the given {@code uid} is an isolated sandbox.
+     */
+    public static final boolean isIsolatedUid(int uid) {
         uid = UserHandle.getAppId(uid);
         return (uid >= FIRST_ISOLATED_UID && uid <= LAST_ISOLATED_UID)
                 || (uid >= FIRST_APP_ZYGOTE_ISOLATED_UID && uid <= LAST_APP_ZYGOTE_ISOLATED_UID);
diff --git a/core/java/android/os/Trace.java b/core/java/android/os/Trace.java
index 8bfa0e9..fb197f5 100644
--- a/core/java/android/os/Trace.java
+++ b/core/java/android/os/Trace.java
@@ -100,6 +100,7 @@
     /** @hide */
     public static final long TRACE_TAG_VIBRATOR = 1L << 23;
     /** @hide */
+    @SystemApi(client = MODULE_LIBRARIES)
     public static final long TRACE_TAG_AIDL = 1L << 24;
     /** @hide */
     public static final long TRACE_TAG_NNAPI = 1L << 25;
diff --git a/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java
index fbfe548..1f21bfe 100644
--- a/core/java/android/os/UserManager.java
+++ b/core/java/android/os/UserManager.java
@@ -2341,12 +2341,18 @@
     }
 
     /**
-     * Used to check if the context user is the primary user. The primary user
-     * is the first human user on a device. This is not supported in headless system user mode.
+     * Used to check if the context user is the primary user. The primary user is the first human
+     * user on a device. This is not supported in headless system user mode.
      *
      * @return whether the context user is the primary user.
+     *
+     * @deprecated This method always returns true for the system user, who may not be a full user
+     * if {@link #isHeadlessSystemUserMode} is true. Use {@link #isSystemUser}, {@link #isAdminUser}
+     * or {@link #isMainUser} instead.
+     *
      * @hide
      */
+    @Deprecated
     @SystemApi
     @RequiresPermission(anyOf = {
             Manifest.permission.MANAGE_USERS,
@@ -2371,6 +2377,29 @@
     }
 
     /**
+     * Returns true if the context user is the designated "main user" of the device. This user may
+     * have access to certain features which are limited to at most one user.
+     *
+     * <p>Currently, the first human user on the device will be the main user; in the future, the
+     * concept may be transferable, so a different user (or even no user at all) may be designated
+     * the main user instead.
+     *
+     * <p>Note that this will be the not be the system user on devices for which
+     * {@link #isHeadlessSystemUserMode()} returns true.
+     * @hide
+     */
+    @SystemApi
+    @RequiresPermission(anyOf = {
+            Manifest.permission.MANAGE_USERS,
+            Manifest.permission.CREATE_USERS,
+            Manifest.permission.QUERY_USERS})
+    @UserHandleAware
+    public boolean isMainUser() {
+        final UserInfo user = getUserInfo(mUserId);
+        return user != null && user.isMain();
+    }
+
+    /**
      * Used to check if the context user is an admin user. An admin user is allowed to
      * modify or configure certain settings that aren't available to non-admin users,
      * create and delete additional users, etc. There can be more than one admin users.
diff --git a/core/java/android/provider/DeviceConfig.java b/core/java/android/provider/DeviceConfig.java
index 7095d1b..6eeb5e7 100644
--- a/core/java/android/provider/DeviceConfig.java
+++ b/core/java/android/provider/DeviceConfig.java
@@ -131,6 +131,13 @@
     public static final String NAMESPACE_APP_STANDBY = "app_standby";
 
     /**
+     * Namespace for all App Cloning related features.
+     * @hide
+     */
+    @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+    public static final String NAMESPACE_APP_CLONING = "app_cloning";
+
+    /**
      * Namespace for AttentionManagerService related features.
      *
      * @hide
diff --git a/core/java/android/security/keymaster/ExportResult.java b/core/java/android/security/keymaster/ExportResult.java
index 2c382ef..c78fb1a 100644
--- a/core/java/android/security/keymaster/ExportResult.java
+++ b/core/java/android/security/keymaster/ExportResult.java
@@ -61,4 +61,4 @@
         out.writeInt(resultCode);
         out.writeByteArray(exportData);
     }
-};
+}
diff --git a/core/java/android/service/credentials/CreateCredentialResponse.java b/core/java/android/service/credentials/CreateCredentialResponse.java
index e330d1e..f69dca8 100644
--- a/core/java/android/service/credentials/CreateCredentialResponse.java
+++ b/core/java/android/service/credentials/CreateCredentialResponse.java
@@ -33,13 +33,11 @@
  * @hide
  */
 public final class CreateCredentialResponse implements Parcelable {
-    private final @Nullable CharSequence mHeader;
     private final @NonNull List<SaveEntry> mSaveEntries;
     private final @Nullable Action mRemoteSaveEntry;
     //TODO : Add actions if needed
 
     private CreateCredentialResponse(@NonNull Parcel in) {
-        mHeader = in.readCharSequence();
         List<SaveEntry> saveEntries = new ArrayList<>();
         in.readTypedList(saveEntries, SaveEntry.CREATOR);
         mSaveEntries = saveEntries;
@@ -48,7 +46,6 @@
 
     @Override
     public void writeToParcel(@NonNull Parcel dest, int flags) {
-        dest.writeCharSequence(mHeader);
         dest.writeTypedList(mSaveEntries);
         dest.writeTypedObject(mRemoteSaveEntry, flags);
     }
@@ -72,21 +69,14 @@
             };
 
     /* package-private */ CreateCredentialResponse(
-            @Nullable CharSequence header,
             @NonNull List<SaveEntry> saveEntries,
             @Nullable Action remoteSaveEntry) {
-        this.mHeader = header;
         this.mSaveEntries = saveEntries;
         com.android.internal.util.AnnotationValidations.validate(
                 NonNull.class, null, mSaveEntries);
         this.mRemoteSaveEntry = remoteSaveEntry;
     }
 
-    /** Returns the header to be displayed on the UI. */
-    public @Nullable CharSequence getHeader() {
-        return mHeader;
-    }
-
     /** Returns the list of save entries to be displayed on the UI. */
     public @NonNull List<SaveEntry> getSaveEntries() {
         return mSaveEntries;
@@ -102,17 +92,9 @@
      */
     @SuppressWarnings("WeakerAccess")
     public static final class Builder {
-
-        private @Nullable CharSequence mHeader;
         private @NonNull List<SaveEntry> mSaveEntries = new ArrayList<>();
         private @Nullable Action mRemoteSaveEntry;
 
-        /** Sets the header to be displayed on the UI. */
-        public @NonNull Builder setHeader(@Nullable CharSequence header) {
-            mHeader = header;
-            return this;
-        }
-
         /**
          * Sets the list of save entries to be shown on the UI.
          *
@@ -154,7 +136,6 @@
             Preconditions.checkCollectionNotEmpty(mSaveEntries, "saveEntries must "
                     + "not be empty");
             return new CreateCredentialResponse(
-                    mHeader,
                     mSaveEntries,
                     mRemoteSaveEntry);
         }
diff --git a/core/java/android/service/credentials/CredentialsDisplayContent.java b/core/java/android/service/credentials/CredentialsDisplayContent.java
index ab5b524..4b23800 100644
--- a/core/java/android/service/credentials/CredentialsDisplayContent.java
+++ b/core/java/android/service/credentials/CredentialsDisplayContent.java
@@ -34,9 +34,6 @@
  * @hide
  */
 public final class CredentialsDisplayContent implements Parcelable {
-    /** Header to be displayed on the UI. */
-    private final @Nullable CharSequence mHeader;
-
     /** List of credential entries to be displayed on the UI. */
     private final @NonNull List<CredentialEntry> mCredentialEntries;
 
@@ -46,18 +43,15 @@
     /** Remote credential entry to get the response from a different device. */
     private final @Nullable Action mRemoteCredentialEntry;
 
-    private CredentialsDisplayContent(@Nullable CharSequence header,
-            @NonNull List<CredentialEntry> credentialEntries,
+    private CredentialsDisplayContent(@NonNull List<CredentialEntry> credentialEntries,
             @NonNull List<Action> actions,
             @Nullable Action remoteCredentialEntry) {
-        mHeader = header;
         mCredentialEntries = credentialEntries;
         mActions = actions;
         mRemoteCredentialEntry = remoteCredentialEntry;
     }
 
     private CredentialsDisplayContent(@NonNull Parcel in) {
-        mHeader = in.readCharSequence();
         List<CredentialEntry> credentialEntries = new ArrayList<>();
         in.readTypedList(credentialEntries, CredentialEntry.CREATOR);
         mCredentialEntries = credentialEntries;
@@ -87,20 +81,12 @@
 
     @Override
     public void writeToParcel(@NonNull Parcel dest, int flags) {
-        dest.writeCharSequence(mHeader);
         dest.writeTypedList(mCredentialEntries, flags);
         dest.writeTypedList(mActions, flags);
         dest.writeTypedObject(mRemoteCredentialEntry, flags);
     }
 
     /**
-     * Returns the header to be displayed on the UI.
-     */
-    public @Nullable CharSequence getHeader() {
-        return mHeader;
-    }
-
-    /**
      * Returns the list of credential entries to be displayed on the UI.
      */
     public @NonNull List<CredentialEntry> getCredentialEntries() {
@@ -125,20 +111,11 @@
      * Builds an instance of {@link CredentialsDisplayContent}.
      */
     public static final class Builder {
-        private CharSequence mHeader;
         private List<CredentialEntry> mCredentialEntries = new ArrayList<>();
         private List<Action> mActions = new ArrayList<>();
         private Action mRemoteCredentialEntry;
 
         /**
-         * Sets the header to be displayed on the UI.
-         */
-        public @NonNull Builder setHeader(@Nullable CharSequence header) {
-            mHeader = header;
-            return this;
-        }
-
-        /**
          * Sets the remote credential entry to be displayed on the UI.
          */
         public @NonNull Builder setRemoteCredentialEntry(@Nullable Action remoteCredentialEntry) {
@@ -208,7 +185,7 @@
                 throw new IllegalStateException("credentialEntries and actions must not both "
                         + "be empty");
             }
-            return new CredentialsDisplayContent(mHeader, mCredentialEntries, mActions,
+            return new CredentialsDisplayContent(mCredentialEntries, mActions,
                     mRemoteCredentialEntry);
         }
     }
diff --git a/core/java/android/service/dreams/DreamOverlayService.java b/core/java/android/service/dreams/DreamOverlayService.java
index aa45c20..6e8198b 100644
--- a/core/java/android/service/dreams/DreamOverlayService.java
+++ b/core/java/android/service/dreams/DreamOverlayService.java
@@ -49,6 +49,17 @@
             mShowComplications = shouldShowComplications;
             onStartDream(layoutParams);
         }
+
+        @Override
+        public void wakeUp() {
+            onWakeUp(() -> {
+                try {
+                    mDreamOverlayCallback.onWakeUpComplete();
+                } catch (RemoteException e) {
+                    Log.e(TAG, "Could not notify dream of wakeUp:" + e);
+                }
+            });
+        }
     };
 
     IDreamOverlayCallback mDreamOverlayCallback;
@@ -71,6 +82,17 @@
     public abstract void onStartDream(@NonNull WindowManager.LayoutParams layoutParams);
 
     /**
+     * This method is overridden by implementations to handle when the dream has been requested
+     * to wakeup. This allows any overlay animations to run.
+     *
+     * @param onCompleteCallback The callback to trigger to notify the dream service that the
+     *                           overlay has completed waking up.
+     * @hide
+     */
+    public void onWakeUp(@NonNull Runnable onCompleteCallback) {
+    }
+
+    /**
      * This method is invoked to request the dream exit.
      */
     public final void requestExit() {
diff --git a/core/java/android/service/dreams/DreamService.java b/core/java/android/service/dreams/DreamService.java
index 32bdf79..8b9852a 100644
--- a/core/java/android/service/dreams/DreamService.java
+++ b/core/java/android/service/dreams/DreamService.java
@@ -312,7 +312,14 @@
         @Override
         public void onExitRequested() {
             // Simply finish dream when exit is requested.
-            finish();
+            mHandler.post(() -> finish());
+        }
+
+        @Override
+        public void onWakeUpComplete() {
+            // Finish the dream once overlay animations are complete. Execute on handler since
+            // this is coming in on the overlay binder.
+            mHandler.post(() -> finish());
         }
     };
 
@@ -975,7 +982,18 @@
      * </p>
      */
     public void onWakeUp() {
-        finish();
+        if (mOverlayConnection != null) {
+            mOverlayConnection.addConsumer(overlay -> {
+                try {
+                    overlay.wakeUp();
+                } catch (RemoteException e) {
+                    Slog.e(TAG, "Error waking the overlay service", e);
+                    finish();
+                }
+            });
+        } else {
+            finish();
+        }
     }
 
     /** {@inheritDoc} */
@@ -1294,7 +1312,7 @@
         if (!mWindowless) {
             Intent i = new Intent(this, DreamActivity.class);
             i.setPackage(getApplicationContext().getPackageName());
-            i.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+            i.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_NO_USER_ACTION);
             i.putExtra(DreamActivity.EXTRA_CALLBACK, new DreamActivityCallbacks(mDreamToken));
             final ServiceInfo serviceInfo = fetchServiceInfo(this,
                     new ComponentName(this, getClass()));
diff --git a/core/java/android/service/dreams/IDreamOverlay.aidl b/core/java/android/service/dreams/IDreamOverlay.aidl
index 05ebbfe..7aeceb2c 100644
--- a/core/java/android/service/dreams/IDreamOverlay.aidl
+++ b/core/java/android/service/dreams/IDreamOverlay.aidl
@@ -38,4 +38,7 @@
     */
     void startDream(in LayoutParams params, in IDreamOverlayCallback callback,
         in String dreamComponent, in boolean shouldShowComplications);
+
+    /** Called when the dream is waking, to do any exit animations */
+    void wakeUp();
 }
diff --git a/core/java/android/service/dreams/IDreamOverlayCallback.aidl b/core/java/android/service/dreams/IDreamOverlayCallback.aidl
index ec76a33..4ad63f1 100644
--- a/core/java/android/service/dreams/IDreamOverlayCallback.aidl
+++ b/core/java/android/service/dreams/IDreamOverlayCallback.aidl
@@ -28,4 +28,7 @@
     * Invoked to request the dream exit.
     */
     void onExitRequested();
+
+    /** Invoked when the dream overlay wakeUp animation is complete. */
+    void onWakeUpComplete();
 }
\ No newline at end of file
diff --git a/core/java/android/service/notification/NotificationListenerService.java b/core/java/android/service/notification/NotificationListenerService.java
index cfc79e4..e821af1 100644
--- a/core/java/android/service/notification/NotificationListenerService.java
+++ b/core/java/android/service/notification/NotificationListenerService.java
@@ -2365,6 +2365,7 @@
                     UserHandle user= (UserHandle) args.arg2;
                     NotificationChannel channel = (NotificationChannel) args.arg3;
                     int modificationType = (int) args.arg4;
+                    args.recycle();
                     onNotificationChannelModified(pkgName, user, channel, modificationType);
                 } break;
 
@@ -2374,6 +2375,7 @@
                     UserHandle user = (UserHandle) args.arg2;
                     NotificationChannelGroup group = (NotificationChannelGroup) args.arg3;
                     int modificationType = (int) args.arg4;
+                    args.recycle();
                     onNotificationChannelGroupModified(pkgName, user, group, modificationType);
                 } break;
 
diff --git a/core/java/android/service/timezone/TimeZoneProviderService.java b/core/java/android/service/timezone/TimeZoneProviderService.java
index 2cea95a..41ca94b 100644
--- a/core/java/android/service/timezone/TimeZoneProviderService.java
+++ b/core/java/android/service/timezone/TimeZoneProviderService.java
@@ -44,8 +44,8 @@
  *
  * <p>Once started, providers are expected to detect the time zone if possible, and report the
  * result via {@link #reportSuggestion(TimeZoneProviderSuggestion)} or {@link
- * #reportUncertain()}. Providers may also report that they have permanently failed
- * by calling {@link #reportPermanentFailure(Throwable)}. See the javadocs for each
+ * #reportUncertain(TimeZoneProviderStatus)}. Providers may also report that they have permanently
+ * failed by calling {@link #reportPermanentFailure(Throwable)}. See the javadocs for each
  * method for details.
  *
  * <p>After starting, providers are expected to issue their first callback within the timeout
@@ -213,8 +213,6 @@
      *
      * @param providerStatus provider status information that can influence detector service
      *   behavior and/or be reported via the device UI
-     *
-     * @hide
      */
     public final void reportSuggestion(@NonNull TimeZoneProviderSuggestion suggestion,
             @NonNull TimeZoneProviderStatus providerStatus) {
@@ -248,8 +246,9 @@
 
     /**
      * Indicates the time zone is not known because of an expected runtime state or error, e.g. when
-     * the provider is unable to detect location, or there was a problem when resolving the location
-     * to a time zone.
+     * the provider is unable to detect location, or there was connectivity issue.
+     *
+     * <p>See {@link #reportUncertain(TimeZoneProviderStatus)} for a more expressive version
      */
     public final void reportUncertain() {
         TimeZoneProviderStatus providerStatus = null;
@@ -264,8 +263,6 @@
      *
      * @param providerStatus provider status information that can influence detector service
      *   behavior and/or be reported via the device UI
-     *
-     * @hide
      */
     public final void reportUncertain(@NonNull TimeZoneProviderStatus providerStatus) {
         Objects.requireNonNull(providerStatus);
@@ -362,8 +359,8 @@
      * <p>Between {@link #onStartUpdates(long)} and {@link #onStopUpdates()} calls, the Android
      * system server holds the latest report from the provider in memory. After an initial report,
      * provider implementations are only required to send a report via {@link
-     * #reportSuggestion(TimeZoneProviderSuggestion)} or via {@link #reportUncertain()} when it
-     * differs from the previous report.
+     * #reportSuggestion(TimeZoneProviderSuggestion, TimeZoneProviderStatus)} or via {@link
+     * #reportUncertain(TimeZoneProviderStatus)} when it differs from the previous report.
      *
      * <p>{@link #reportPermanentFailure(Throwable)} can also be called by provider implementations
      * in rare cases, after which the provider should consider itself stopped and not make any
@@ -375,7 +372,8 @@
      * Android system server may move on to use other providers or detection methods. Providers
      * should therefore make best efforts during this time to generate a report, which could involve
      * increased power usage. Providers should preferably report an explicit {@link
-     * #reportUncertain()} if the time zone(s) cannot be detected within the initialization timeout.
+     * #reportUncertain(TimeZoneProviderStatus)} if the time zone(s) cannot be detected within the
+     * initialization timeout.
      *
      * @see #onStopUpdates() for the signal from the system server to stop sending reports
      */
diff --git a/core/java/android/service/timezone/TimeZoneProviderStatus.java b/core/java/android/service/timezone/TimeZoneProviderStatus.java
index 513068f..e0b78e9 100644
--- a/core/java/android/service/timezone/TimeZoneProviderStatus.java
+++ b/core/java/android/service/timezone/TimeZoneProviderStatus.java
@@ -19,6 +19,7 @@
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.annotation.SystemApi;
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.text.TextUtils;
@@ -65,6 +66,7 @@
  *
  * @hide
  */
+@SystemApi
 public final class TimeZoneProviderStatus implements Parcelable {
 
     /**
diff --git a/core/java/android/service/wallpaper/WallpaperService.java b/core/java/android/service/wallpaper/WallpaperService.java
index 104570d0..007478a 100644
--- a/core/java/android/service/wallpaper/WallpaperService.java
+++ b/core/java/android/service/wallpaper/WallpaperService.java
@@ -31,6 +31,7 @@
 import android.animation.AnimatorListenerAdapter;
 import android.animation.ValueAnimator;
 import android.annotation.FloatRange;
+import android.annotation.MainThread;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.SdkConstant;
@@ -657,6 +658,7 @@
          * Called once to initialize the engine.  After returning, the
          * engine's surface will be created by the framework.
          */
+        @MainThread
         public void onCreate(SurfaceHolder surfaceHolder) {
         }
 
@@ -665,6 +667,7 @@
          * surface will be destroyed and this Engine object is no longer
          * valid.
          */
+        @MainThread
         public void onDestroy() {
         }
 
@@ -673,6 +676,7 @@
          * hidden.  <em>It is very important that a wallpaper only use
          * CPU while it is visible.</em>.
          */
+        @MainThread
         public void onVisibilityChanged(boolean visible) {
         }
 
@@ -683,6 +687,7 @@
          *
          * @param insets Insets to apply.
          */
+        @MainThread
         public void onApplyWindowInsets(WindowInsets insets) {
         }
 
@@ -693,6 +698,7 @@
          * user is interacting with, so if it is slow you will get fewer
          * move events.
          */
+        @MainThread
         public void onTouchEvent(MotionEvent event) {
         }
 
@@ -702,6 +708,7 @@
          * call to {@link WallpaperManager#setWallpaperOffsets(IBinder, float, float)
          * WallpaperManager.setWallpaperOffsets()}.
          */
+        @MainThread
         public void onOffsetsChanged(float xOffset, float yOffset,
                 float xOffsetStep, float yOffsetStep,
                 int xPixelOffset, int yPixelOffset) {
@@ -724,6 +731,7 @@
          * @return If returning a result, create a Bundle and place the
          * result data in to it.  Otherwise return null.
          */
+        @MainThread
         public Bundle onCommand(String action, int x, int y, int z,
                 Bundle extras, boolean resultRequested) {
             return null;
@@ -742,6 +750,7 @@
          * @hide
          */
         @SystemApi
+        @MainThread
         public void onAmbientModeChanged(boolean inAmbientMode, long animationDuration) {
         }
 
@@ -749,6 +758,7 @@
          * Called when an application has changed the desired virtual size of
          * the wallpaper.
          */
+        @MainThread
         public void onDesiredSizeChanged(int desiredWidth, int desiredHeight) {
         }
 
@@ -756,6 +766,7 @@
          * Convenience for {@link SurfaceHolder.Callback#surfaceChanged
          * SurfaceHolder.Callback.surfaceChanged()}.
          */
+        @MainThread
         public void onSurfaceChanged(SurfaceHolder holder, int format, int width, int height) {
         }
 
@@ -763,6 +774,7 @@
          * Convenience for {@link SurfaceHolder.Callback2#surfaceRedrawNeeded
          * SurfaceHolder.Callback.surfaceRedrawNeeded()}.
          */
+        @MainThread
         public void onSurfaceRedrawNeeded(SurfaceHolder holder) {
         }
 
@@ -770,6 +782,7 @@
          * Convenience for {@link SurfaceHolder.Callback#surfaceCreated
          * SurfaceHolder.Callback.surfaceCreated()}.
          */
+        @MainThread
         public void onSurfaceCreated(SurfaceHolder holder) {
         }
 
@@ -777,6 +790,7 @@
          * Convenience for {@link SurfaceHolder.Callback#surfaceDestroyed
          * SurfaceHolder.Callback.surfaceDestroyed()}.
          */
+        @MainThread
         public void onSurfaceDestroyed(SurfaceHolder holder) {
         }
 
@@ -787,6 +801,7 @@
          * @param zoom the zoom level, between 0 indicating fully zoomed in and 1 indicating fully
          *             zoomed out.
          */
+        @MainThread
         public void onZoomChanged(@FloatRange(from = 0f, to = 1f) float zoom) {
         }
 
@@ -836,6 +851,7 @@
          *
          * @return Wallpaper colors.
          */
+        @MainThread
         public @Nullable WallpaperColors onComputeColors() {
             return null;
         }
@@ -2510,6 +2526,7 @@
      * when the wallpaper is currently set as the active wallpaper and the user
      * is in the wallpaper picker viewing a preview of it as well.
      */
+    @MainThread
     public abstract Engine onCreateEngine();
 
     @Override
diff --git a/core/java/android/text/style/AccessibilityURLSpan.java b/core/java/android/text/style/AccessibilityURLSpan.java
index bd81623..e280bdf 100644
--- a/core/java/android/text/style/AccessibilityURLSpan.java
+++ b/core/java/android/text/style/AccessibilityURLSpan.java
@@ -26,6 +26,7 @@
  * It is used to replace URLSpans in {@link AccessibilityNodeInfo#setText(CharSequence)}
  * @hide
  */
+@SuppressWarnings("ParcelableCreator")
 public class AccessibilityURLSpan extends URLSpan implements Parcelable {
     final AccessibilityClickableSpan mAccessibilityClickableSpan;
 
diff --git a/core/java/android/util/AndroidException.java b/core/java/android/util/AndroidException.java
index 1345ddf..d1b9d9f 100644
--- a/core/java/android/util/AndroidException.java
+++ b/core/java/android/util/AndroidException.java
@@ -40,5 +40,5 @@
             boolean writableStackTrace) {
         super(message, cause, enableSuppression, writableStackTrace);
     }
-};
+}
 
diff --git a/core/java/android/util/AndroidRuntimeException.java b/core/java/android/util/AndroidRuntimeException.java
index 2b824bf..72c34d8b 100644
--- a/core/java/android/util/AndroidRuntimeException.java
+++ b/core/java/android/util/AndroidRuntimeException.java
@@ -34,5 +34,4 @@
     public AndroidRuntimeException(Exception cause) {
         super(cause);
     }
-};
-
+}
diff --git a/core/java/android/util/FeatureFlagUtils.java b/core/java/android/util/FeatureFlagUtils.java
index 7b6a6d2..4afd268 100644
--- a/core/java/android/util/FeatureFlagUtils.java
+++ b/core/java/android/util/FeatureFlagUtils.java
@@ -132,6 +132,12 @@
      */
     public static final String SETTINGS_ADB_METRICS_WRITER = "settings_adb_metrics_writer";
 
+    /**
+     * Flag to enable/disable biometrics enrollment v2
+     * @hide
+     */
+    public static final String SETTINGS_BIOMETRICS2_ENROLLMENT = "settings_biometrics2_enrollment";
+
     private static final Map<String, String> DEFAULT_FLAGS;
 
     static {
@@ -167,6 +173,7 @@
         DEFAULT_FLAGS.put(SETTINGS_NEW_KEYBOARD_TRACKPAD, "false");
         DEFAULT_FLAGS.put(SETTINGS_ENABLE_SPA, "false");
         DEFAULT_FLAGS.put(SETTINGS_ADB_METRICS_WRITER, "false");
+        DEFAULT_FLAGS.put(SETTINGS_BIOMETRICS2_ENROLLMENT, "false");
     }
 
     private static final Set<String> PERSISTENT_FLAGS;
diff --git a/core/java/android/util/OWNERS b/core/java/android/util/OWNERS
index d4cf6e6..3772006 100644
--- a/core/java/android/util/OWNERS
+++ b/core/java/android/util/OWNERS
@@ -1,6 +1,6 @@
 per-file Dump* = file:/core/java/com/android/internal/util/dump/OWNERS
 per-file FeatureFlagUtils.java = sbasi@google.com
-per-file FeatureFlagUtils.java = tmfang@google.com
+per-file FeatureFlagUtils.java = edgarwang@google.com
 
 per-file AttributeSet.java = file:/core/java/android/content/res/OWNERS
 per-file TypedValue.java = file:/core/java/android/content/res/OWNERS
diff --git a/core/java/android/util/Range.java b/core/java/android/util/Range.java
index 9fd0ab9..41c171a 100644
--- a/core/java/android/util/Range.java
+++ b/core/java/android/util/Range.java
@@ -356,4 +356,4 @@
 
     private final T mLower;
     private final T mUpper;
-};
+}
diff --git a/core/java/android/util/TypedValue.java b/core/java/android/util/TypedValue.java
index 19de396..44318bb 100644
--- a/core/java/android/util/TypedValue.java
+++ b/core/java/android/util/TypedValue.java
@@ -696,5 +696,5 @@
         sb.append("}");
         return sb.toString();
     }
-};
+}
 
diff --git a/core/java/android/view/CutoutSpecification.java b/core/java/android/view/CutoutSpecification.java
index f8aa934..3fc3b6a 100644
--- a/core/java/android/view/CutoutSpecification.java
+++ b/core/java/android/view/CutoutSpecification.java
@@ -394,7 +394,6 @@
                 Log.e(TAG, "According to SVG definition, it shouldn't happen");
                 return;
             }
-            spec.trim();
             translateMatrix();
 
             final Path newPath = PathParser.createPathFromPathData(spec);
diff --git a/core/java/android/view/RemoteAnimationDefinition.java b/core/java/android/view/RemoteAnimationDefinition.java
index ea97995..ff282ba 100644
--- a/core/java/android/view/RemoteAnimationDefinition.java
+++ b/core/java/android/view/RemoteAnimationDefinition.java
@@ -19,6 +19,7 @@
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
 
 import android.annotation.Nullable;
+import android.annotation.NonNull;
 import android.app.WindowConfiguration.ActivityType;
 import android.compat.annotation.UnsupportedAppUsage;
 import android.os.IBinder;
@@ -157,7 +158,7 @@
         }
     }
 
-    public static final @android.annotation.NonNull Creator<RemoteAnimationDefinition> CREATOR =
+    public static final @NonNull Creator<RemoteAnimationDefinition> CREATOR =
             new Creator<RemoteAnimationDefinition>() {
         public RemoteAnimationDefinition createFromParcel(Parcel in) {
             return new RemoteAnimationDefinition(in);
@@ -199,18 +200,17 @@
             return 0;
         }
 
-        private static final @android.annotation.NonNull Creator<RemoteAnimationAdapterEntry> CREATOR
-                = new Creator<RemoteAnimationAdapterEntry>() {
+        public static final @NonNull Parcelable.Creator<RemoteAnimationAdapterEntry> CREATOR =
+                new Parcelable.Creator<RemoteAnimationAdapterEntry>() {
+                    @Override
+                    public RemoteAnimationAdapterEntry createFromParcel(Parcel in) {
+                        return new RemoteAnimationAdapterEntry(in);
+                    }
 
-            @Override
-            public RemoteAnimationAdapterEntry createFromParcel(Parcel in) {
-                return new RemoteAnimationAdapterEntry(in);
-            }
-
-            @Override
-            public RemoteAnimationAdapterEntry[] newArray(int size) {
-                return new RemoteAnimationAdapterEntry[size];
-            }
-        };
+                    @Override
+                    public RemoteAnimationAdapterEntry[] newArray(int size) {
+                        return new RemoteAnimationAdapterEntry[size];
+                    }
+                };
     }
 }
diff --git a/core/java/android/view/SurfaceView.java b/core/java/android/view/SurfaceView.java
index b24303b..720813a 100644
--- a/core/java/android/view/SurfaceView.java
+++ b/core/java/android/view/SurfaceView.java
@@ -1073,7 +1073,7 @@
     private void handleSyncBufferCallback(SurfaceHolder.Callback[] callbacks,
             SyncBufferTransactionCallback syncBufferTransactionCallback) {
 
-        getViewRootImpl().addToSync(syncBufferCallback ->
+        getViewRootImpl().addToSync((parentSyncGroup, syncBufferCallback) ->
                 redrawNeededAsync(callbacks, () -> {
                     Transaction t = null;
                     if (mBlastBufferQueue != null) {
@@ -1081,7 +1081,7 @@
                         t = syncBufferTransactionCallback.waitForTransaction();
                     }
 
-                    syncBufferCallback.onBufferReady(t);
+                    syncBufferCallback.onTransactionReady(t);
                     onDrawFinished();
                 }));
     }
@@ -1092,9 +1092,9 @@
             mSyncGroups.add(syncGroup);
         }
 
-        syncGroup.addToSync(syncBufferCallback -> redrawNeededAsync(callbacks,
-                () -> {
-                    syncBufferCallback.onBufferReady(null);
+        syncGroup.addToSync((parentSyncGroup, syncBufferCallback) ->
+                redrawNeededAsync(callbacks, () -> {
+                    syncBufferCallback.onTransactionReady(null);
                     onDrawFinished();
                     synchronized (mSyncGroups) {
                         mSyncGroups.remove(syncGroup);
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index 7e8ebd7..5e1dc34 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -231,6 +231,7 @@
 import java.util.Objects;
 import java.util.Queue;
 import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.Executor;
 
 /**
  * The top of a view hierarchy, implementing the needed protocol between View
@@ -851,7 +852,7 @@
     }
 
     private SurfaceSyncGroup mSyncGroup;
-    private SurfaceSyncGroup.SyncBufferCallback mSyncBufferCallback;
+    private SurfaceSyncGroup.TransactionReadyCallback mTransactionReadyCallback;
     private int mNumSyncsInProgress = 0;
 
     private HashSet<ScrollCaptureCallback> mRootScrollCaptureCallbacks;
@@ -3610,8 +3611,8 @@
                 mPendingTransitions.clear();
             }
 
-            if (mSyncBufferCallback != null) {
-                mSyncBufferCallback.onBufferReady(null);
+            if (mTransactionReadyCallback != null) {
+                mTransactionReadyCallback.onTransactionReady(null);
             }
         } else if (cancelAndRedraw) {
             mLastPerformTraversalsSkipDrawReason = cancelDueToPreDrawListener
@@ -3626,8 +3627,8 @@
                 }
                 mPendingTransitions.clear();
             }
-            if (!performDraw() && mSyncBufferCallback != null) {
-                mSyncBufferCallback.onBufferReady(null);
+            if (!performDraw() && mTransactionReadyCallback != null) {
+                mTransactionReadyCallback.onTransactionReady(null);
             }
         }
 
@@ -3641,7 +3642,7 @@
         if (!cancelAndRedraw) {
             mReportNextDraw = false;
             mLastReportNextDrawReason = null;
-            mSyncBufferCallback = null;
+            mTransactionReadyCallback = null;
             mSyncBuffer = false;
             if (isInLocalSync()) {
                 mSyncGroup.markSyncReady();
@@ -4388,7 +4389,7 @@
             return false;
         }
 
-        final boolean fullRedrawNeeded = mFullRedrawNeeded || mSyncBufferCallback != null;
+        final boolean fullRedrawNeeded = mFullRedrawNeeded || mTransactionReadyCallback != null;
         mFullRedrawNeeded = false;
 
         mIsDrawing = true;
@@ -4396,9 +4397,9 @@
 
         addFrameCommitCallbackIfNeeded();
 
-        boolean usingAsyncReport = isHardwareEnabled() && mSyncBufferCallback != null;
+        boolean usingAsyncReport = isHardwareEnabled() && mTransactionReadyCallback != null;
         if (usingAsyncReport) {
-            registerCallbacksForSync(mSyncBuffer, mSyncBufferCallback);
+            registerCallbacksForSync(mSyncBuffer, mTransactionReadyCallback);
         } else if (mHasPendingTransactions) {
             // These callbacks are only needed if there's no sync involved and there were calls to
             // applyTransactionOnDraw. These callbacks check if the draw failed for any reason and
@@ -4449,10 +4450,11 @@
             }
 
             if (mSurfaceHolder != null && mSurface.isValid()) {
-                final SurfaceSyncGroup.SyncBufferCallback syncBufferCallback = mSyncBufferCallback;
+                final SurfaceSyncGroup.TransactionReadyCallback transactionReadyCallback =
+                        mTransactionReadyCallback;
                 SurfaceCallbackHelper sch = new SurfaceCallbackHelper(() ->
-                        mHandler.post(() -> syncBufferCallback.onBufferReady(null)));
-                mSyncBufferCallback = null;
+                        mHandler.post(() -> transactionReadyCallback.onTransactionReady(null)));
+                mTransactionReadyCallback = null;
 
                 SurfaceHolder.Callback callbacks[] = mSurfaceHolder.getCallbacks();
 
@@ -4463,8 +4465,8 @@
                 }
             }
         }
-        if (mSyncBufferCallback != null && !usingAsyncReport) {
-            mSyncBufferCallback.onBufferReady(null);
+        if (mTransactionReadyCallback != null && !usingAsyncReport) {
+            mTransactionReadyCallback.onTransactionReady(null);
         }
         if (mPerformContentCapture) {
             performContentCaptureInitialReport();
@@ -11134,7 +11136,7 @@
     }
 
     private void registerCallbacksForSync(boolean syncBuffer,
-            final SurfaceSyncGroup.SyncBufferCallback syncBufferCallback) {
+            final SurfaceSyncGroup.TransactionReadyCallback transactionReadyCallback) {
         if (!isHardwareEnabled()) {
             return;
         }
@@ -11161,7 +11163,7 @@
                 // pendingDrawFinished.
                 if ((syncResult
                         & (SYNC_LOST_SURFACE_REWARD_IF_FOUND | SYNC_CONTEXT_IS_STOPPED)) != 0) {
-                    syncBufferCallback.onBufferReady(
+                    transactionReadyCallback.onTransactionReady(
                             mBlastBufferQueue.gatherPendingTransactions(frame));
                     return null;
                 }
@@ -11171,7 +11173,8 @@
                 }
 
                 if (syncBuffer) {
-                    mBlastBufferQueue.syncNextTransaction(syncBufferCallback::onBufferReady);
+                    mBlastBufferQueue.syncNextTransaction(
+                            transactionReadyCallback::onTransactionReady);
                 }
 
                 return didProduceBuffer -> {
@@ -11191,7 +11194,7 @@
                         // since the frame didn't draw on this vsync. It's possible the frame will
                         // draw later, but it's better to not be sync than to block on a frame that
                         // may never come.
-                        syncBufferCallback.onBufferReady(
+                        transactionReadyCallback.onTransactionReady(
                                 mBlastBufferQueue.gatherPendingTransactions(frame));
                         return;
                     }
@@ -11200,22 +11203,49 @@
                     // syncNextTransaction callback. Instead, just report back to the Syncer so it
                     // knows that this sync request is complete.
                     if (!syncBuffer) {
-                        syncBufferCallback.onBufferReady(null);
+                        transactionReadyCallback.onTransactionReady(null);
                     }
                 };
             }
         });
     }
 
+    private final Executor mPostAtFrontExecutor = new Executor() {
+        @Override
+        public void execute(Runnable command) {
+            mHandler.postAtFrontOfQueue(command);
+        }
+    };
+
     public final SurfaceSyncGroup.SyncTarget mSyncTarget = new SurfaceSyncGroup.SyncTarget() {
         @Override
-        public void onReadyToSync(SurfaceSyncGroup.SyncBufferCallback syncBufferCallback) {
-            readyToSync(syncBufferCallback);
+        public void onAddedToSyncGroup(SurfaceSyncGroup parentSyncGroup,
+                SurfaceSyncGroup.TransactionReadyCallback transactionReadyCallback) {
+            updateSyncInProgressCount(parentSyncGroup);
+            if (!isInLocalSync()) {
+                // Always sync the buffer if the sync request did not come from VRI.
+                mSyncBuffer = true;
+            }
+            if (mAttachInfo.mThreadedRenderer != null) {
+                HardwareRenderer.setRtAnimationsEnabled(false);
+            }
+
+            if (mTransactionReadyCallback != null) {
+                Log.d(mTag, "Already set sync for the next draw.");
+                mTransactionReadyCallback.onTransactionReady(null);
+            }
+            if (DEBUG_BLAST) {
+                Log.d(mTag, "Setting syncFrameCallback");
+            }
+            mTransactionReadyCallback = transactionReadyCallback;
+            if (!mIsInTraversal && !mTraversalScheduled) {
+                scheduleTraversals();
+            }
         }
 
-        @Override
-        public void onSyncComplete() {
-            mHandler.postAtFrontOfQueue(() -> {
+        private void updateSyncInProgressCount(SurfaceSyncGroup parentSyncGroup) {
+            mNumSyncsInProgress++;
+            parentSyncGroup.addSyncCompleteCallback(mPostAtFrontExecutor, () -> {
                 if (--mNumSyncsInProgress == 0 && mAttachInfo.mThreadedRenderer != null) {
                     HardwareRenderer.setRtAnimationsEnabled(true);
                 }
@@ -11228,29 +11258,6 @@
         return mSyncTarget;
     }
 
-    private void readyToSync(SurfaceSyncGroup.SyncBufferCallback syncBufferCallback) {
-        mNumSyncsInProgress++;
-        if (!isInLocalSync()) {
-            // Always sync the buffer if the sync request did not come from VRI.
-            mSyncBuffer = true;
-        }
-        if (mAttachInfo.mThreadedRenderer != null) {
-            HardwareRenderer.setRtAnimationsEnabled(false);
-        }
-
-        if (mSyncBufferCallback != null) {
-            Log.d(mTag, "Already set sync for the next draw.");
-            mSyncBufferCallback.onBufferReady(null);
-        }
-        if (DEBUG_BLAST) {
-            Log.d(mTag, "Setting syncFrameCallback");
-        }
-        mSyncBufferCallback = syncBufferCallback;
-        if (!mIsInTraversal && !mTraversalScheduled) {
-            scheduleTraversals();
-        }
-    }
-
     void mergeSync(SurfaceSyncGroup otherSyncGroup) {
         if (!isInLocalSync()) {
             return;
diff --git a/core/java/android/view/WindowManager.java b/core/java/android/view/WindowManager.java
index cc85181..16f6cea 100644
--- a/core/java/android/view/WindowManager.java
+++ b/core/java/android/view/WindowManager.java
@@ -3408,6 +3408,11 @@
          * alt="Screenshot of an activity on a display with a cutout on the long edge in portrait,
          *         letterbox is applied."/>
          *
+         * <p>
+         * Note: Android might not allow the content view to overlap the system bars in view level.
+         * To override this behavior and allow content to be able to extend into the cutout area,
+         * call {@link Window#setDecorFitsSystemWindows(boolean)} with {@code false}.
+         *
          * @see DisplayCutout
          * @see WindowInsets#getDisplayCutout()
          * @see #layoutInDisplayCutoutMode
@@ -3443,6 +3448,11 @@
          * In this mode, the window extends under cutouts on the all edges of the display in both
          * portrait and landscape, regardless of whether the window is hiding the system bars.
          *
+         * <p>
+         * Note: Android might not allow the content view to overlap the system bars in view level.
+         * To override this behavior and allow content to be able to extend into the cutout area,
+         * call {@link Window#setDecorFitsSystemWindows(boolean)} with {@code false}.
+         *
          * @see DisplayCutout
          * @see WindowInsets#getDisplayCutout()
          * @see #layoutInDisplayCutoutMode
diff --git a/core/java/android/webkit/ConsoleMessage.java b/core/java/android/webkit/ConsoleMessage.java
index 5474557..89cb6b2 100644
--- a/core/java/android/webkit/ConsoleMessage.java
+++ b/core/java/android/webkit/ConsoleMessage.java
@@ -68,4 +68,4 @@
     public int lineNumber() {
         return mLineNumber;
     }
-};
+}
diff --git a/core/java/android/webkit/ValueCallback.java b/core/java/android/webkit/ValueCallback.java
index 5c7d97f..3d5bb49 100644
--- a/core/java/android/webkit/ValueCallback.java
+++ b/core/java/android/webkit/ValueCallback.java
@@ -25,4 +25,4 @@
      * @param value The value.
      */
     public void onReceiveValue(T value);
-};
+}
diff --git a/core/java/android/window/SurfaceSyncGroup.java b/core/java/android/window/SurfaceSyncGroup.java
index 4248096..3950739 100644
--- a/core/java/android/window/SurfaceSyncGroup.java
+++ b/core/java/android/window/SurfaceSyncGroup.java
@@ -57,12 +57,13 @@
  * option is provided.
  *
  * The following is what happens within the {@link SurfaceSyncGroup}
- * 1. Each SyncTarget will get a {@link SyncTarget#onReadyToSync} callback that contains a
- * {@link SyncBufferCallback}.
- * 2. Each {@link SyncTarget} needs to invoke {@link SyncBufferCallback#onBufferReady(Transaction)}.
- * This makes sure the SurfaceSyncGroup knows when the SyncTarget is complete, allowing the
- * SurfaceSyncGroup to get the Transaction that contains the buffer.
- * 3. When the final SyncBufferCallback finishes for the SurfaceSyncGroup, in most cases the
+ * 1. Each SyncTarget will get a {@link SyncTarget#onAddedToSyncGroup} callback that contains a
+ * {@link TransactionReadyCallback}.
+ * 2. Each {@link SyncTarget} needs to invoke
+ * {@link TransactionReadyCallback#onTransactionReady(Transaction)}. This makes sure the
+ * SurfaceSyncGroup knows when the SyncTarget is complete, allowing the SurfaceSyncGroup to get the
+ * Transaction that contains the buffer.
+ * 3. When the final TransactionReadyCallback finishes for the SurfaceSyncGroup, in most cases the
  * transaction is applied and then the sync complete callbacks are invoked, letting the callers know
  * the sync is now complete.
  *
@@ -86,8 +87,6 @@
     private final Transaction mTransaction = sTransactionFactory.get();
     @GuardedBy("mLock")
     private boolean mSyncReady;
-    @GuardedBy("mLock")
-    private final Set<SyncTarget> mSyncTargets = new ArraySet<>();
 
     @GuardedBy("mLock")
     private Consumer<Transaction> mSyncRequestCompleteCallback;
@@ -197,14 +196,13 @@
      * Add a {@link SyncTarget} to a sync set. The sync set will wait for all
      * SyncableSurfaces to complete before notifying.
      *
-     * @param syncTarget A SyncableSurface that implements how to handle syncing
-     *                   buffers.
+     * @param syncTarget A SyncTarget that implements how to handle syncing transactions.
      * @return true if the SyncTarget was successfully added to the SyncGroup, false otherwise.
      */
     public boolean addToSync(SyncTarget syncTarget) {
-        SyncBufferCallback syncBufferCallback = new SyncBufferCallback() {
+        TransactionReadyCallback transactionReadyCallback = new TransactionReadyCallback() {
             @Override
-            public void onBufferReady(Transaction t) {
+            public void onTransactionReady(Transaction t) {
                 synchronized (mLock) {
                     if (t != null) {
                         mTransaction.merge(t);
@@ -221,10 +219,9 @@
                         + "SyncTargets can be added.");
                 return false;
             }
-            mPendingSyncs.add(syncBufferCallback.hashCode());
-            mSyncTargets.add(syncTarget);
+            mPendingSyncs.add(transactionReadyCallback.hashCode());
         }
-        syncTarget.onReadyToSync(syncBufferCallback);
+        syncTarget.onAddedToSyncGroup(this, transactionReadyCallback);
         return true;
     }
 
@@ -256,17 +253,13 @@
             Log.d(TAG, "Successfully finished sync id=" + this);
         }
 
-        for (SyncTarget syncTarget : mSyncTargets) {
-            syncTarget.onSyncComplete();
-        }
-        mSyncTargets.clear();
         mSyncRequestCompleteCallback.accept(mTransaction);
         mFinished = true;
     }
 
     /**
      * Add a Transaction to this sync set. This allows the caller to provide other info that
-     * should be synced with the buffers.
+     * should be synced with the transactions.
      */
     public void addTransactionToSync(Transaction t) {
         synchronized (mLock) {
@@ -334,9 +327,10 @@
         }
 
         @Override
-        public void onReadyToSync(SyncBufferCallback syncBufferCallback) {
+        public void onAddedToSyncGroup(SurfaceSyncGroup parentSyncGroup,
+                TransactionReadyCallback transactionReadyCallback) {
             mFrameCallbackConsumer.accept(
-                    () -> mSurfaceView.syncNextFrame(syncBufferCallback::onBufferReady));
+                    () -> mSurfaceView.syncNextFrame(transactionReadyCallback::onTransactionReady));
         }
     }
 
@@ -345,22 +339,19 @@
      */
     public interface SyncTarget {
         /**
-         * Called when the Syncable is ready to begin handing a sync request. When invoked, the
-         * implementor is required to call {@link SyncBufferCallback#onBufferReady(Transaction)}
-         * and {@link SyncBufferCallback#onBufferReady(Transaction)} in order for this Syncable
-         * to be marked as complete.
+         * Called when the SyncTarget has been added to a SyncGroup as is ready to begin handing a
+         * sync request. When invoked, the implementor is required to call
+         * {@link TransactionReadyCallback#onTransactionReady(Transaction)} in order for this
+         * SurfaceSyncGroup to fully complete.
          *
          * Always invoked on the thread that initiated the call to {@link #addToSync(SyncTarget)}
          *
-         * @param syncBufferCallback A SyncBufferCallback that the caller must invoke onBufferReady
+         * @param parentSyncGroup The sync group this target has been added to.
+         * @param transactionReadyCallback A TransactionReadyCallback that the caller must invoke
+         *                                 onTransactionReady
          */
-        void onReadyToSync(SyncBufferCallback syncBufferCallback);
-
-        /**
-         * There's no guarantee about the thread this callback is invoked on.
-         */
-        default void onSyncComplete() {
-        }
+        void onAddedToSyncGroup(SurfaceSyncGroup parentSyncGroup,
+                TransactionReadyCallback transactionReadyCallback);
     }
 
     /**
@@ -368,14 +359,14 @@
      * completed. The caller should invoke the calls when the rendering has started and finished a
      * frame.
      */
-    public interface SyncBufferCallback {
+    public interface TransactionReadyCallback {
         /**
-         * Invoked when the transaction contains the buffer and is ready to sync.
+         * Invoked when the transaction is ready to sync.
          *
-         * @param t The transaction that contains the buffer to be synced. This can be null if
-         *          there's nothing to sync
+         * @param t The transaction that contains the anything to be included in the synced. This
+         *          can be null if there's nothing to sync
          */
-        void onBufferReady(@Nullable Transaction t);
+        void onTransactionReady(@Nullable Transaction t);
     }
 
     /**
diff --git a/core/java/android/window/TransitionFilter.java b/core/java/android/window/TransitionFilter.java
index db15145..e62d5c9 100644
--- a/core/java/android/window/TransitionFilter.java
+++ b/core/java/android/window/TransitionFilter.java
@@ -296,7 +296,7 @@
                     out.append((i == 0 ? "" : ",") + TransitionInfo.modeToString(mModes[i]));
                 }
             }
-            out.append("]").toString();
+            out.append("]");
             out.append(" flags=" + TransitionInfo.flagsToString(mFlags));
             out.append(" mustBeTask=" + mMustBeTask);
             out.append(" order=" + containerOrderToString(mOrder));
diff --git a/core/java/com/android/internal/app/IAppOpsService.aidl b/core/java/com/android/internal/app/IAppOpsService.aidl
index 30da4b4..88447da 100644
--- a/core/java/com/android/internal/app/IAppOpsService.aidl
+++ b/core/java/com/android/internal/app/IAppOpsService.aidl
@@ -58,11 +58,12 @@
     SyncNotedAppOp noteProxyOperation(int code, in AttributionSource attributionSource,
             boolean shouldCollectAsyncNotedOp, String message, boolean shouldCollectMessage,
             boolean skipProxyOperation);
-    SyncNotedAppOp startProxyOperation(int code, in AttributionSource attributionSource,
-            boolean startIfModeDefault, boolean shouldCollectAsyncNotedOp, String message,
-            boolean shouldCollectMessage, boolean skipProxyOperation, int proxyAttributionFlags,
-            int proxiedAttributionFlags, int attributionChainId);
-    void finishProxyOperation(int code, in AttributionSource attributionSource,
+    SyncNotedAppOp startProxyOperation(IBinder clientId, int code,
+            in AttributionSource attributionSource, boolean startIfModeDefault,
+            boolean shouldCollectAsyncNotedOp, String message, boolean shouldCollectMessage,
+            boolean skipProxyOperation, int proxyAttributionFlags, int proxiedAttributionFlags,
+            int attributionChainId);
+    void finishProxyOperation(IBinder clientId, int code, in AttributionSource attributionSource,
             boolean skipProxyOperation);
 
     // Remaining methods are only used in Java.
diff --git a/core/java/com/android/internal/app/ResolverActivity.java b/core/java/com/android/internal/app/ResolverActivity.java
index e926605..a237e98 100644
--- a/core/java/com/android/internal/app/ResolverActivity.java
+++ b/core/java/com/android/internal/app/ResolverActivity.java
@@ -492,6 +492,12 @@
                 /* workProfileUserHandle= */ null);
     }
 
+    private UserHandle getIntentUser() {
+        return getIntent().hasExtra(EXTRA_CALLING_USER)
+                ? getIntent().getParcelableExtra(EXTRA_CALLING_USER, android.os.UserHandle.class)
+                : getUser();
+    }
+
     private ResolverMultiProfilePagerAdapter createResolverMultiProfilePagerAdapterForTwoProfiles(
             Intent[] initialIntents,
             List<ResolveInfo> rList,
@@ -500,9 +506,7 @@
         // the intent resolver is started in the other profile. Since this is the only case when
         // this happens, we check for it here and set the current profile's tab.
         int selectedProfile = getCurrentProfile();
-        UserHandle intentUser = getIntent().hasExtra(EXTRA_CALLING_USER)
-                ? getIntent().getParcelableExtra(EXTRA_CALLING_USER, android.os.UserHandle.class)
-                : getUser();
+        UserHandle intentUser = getIntentUser();
         if (!getUser().equals(intentUser)) {
             if (getPersonalProfileUserHandle().equals(intentUser)) {
                 selectedProfile = PROFILE_PERSONAL;
diff --git a/core/java/com/android/internal/app/procstats/AssociationState.java b/core/java/com/android/internal/app/procstats/AssociationState.java
index 97f4b0f..a21a842 100644
--- a/core/java/com/android/internal/app/procstats/AssociationState.java
+++ b/core/java/com/android/internal/app/procstats/AssociationState.java
@@ -59,6 +59,7 @@
     /**
      * The state of the source process of an association.
      */
+    @SuppressWarnings("ParcelableCreator")
     public static final class SourceState implements Parcelable {
         private @NonNull final ProcessStats mProcessStats;
         private @Nullable final AssociationState mAssociationState;
diff --git a/core/java/com/android/internal/os/BatteryStatsHistory.java b/core/java/com/android/internal/os/BatteryStatsHistory.java
index 696f0ff..556e146 100644
--- a/core/java/com/android/internal/os/BatteryStatsHistory.java
+++ b/core/java/com/android/internal/os/BatteryStatsHistory.java
@@ -403,7 +403,7 @@
      * Returns true if this instance only supports reading history.
      */
     public boolean isReadOnly() {
-        return mActiveFile == null;
+        return mActiveFile == null || mHistoryDir == null;
     }
 
     /**
@@ -1292,7 +1292,9 @@
                 && mHistoryLastWritten.batteryHealth == cur.batteryHealth
                 && mHistoryLastWritten.batteryPlugType == cur.batteryPlugType
                 && mHistoryLastWritten.batteryTemperature == cur.batteryTemperature
-                && mHistoryLastWritten.batteryVoltage == cur.batteryVoltage) {
+                && mHistoryLastWritten.batteryVoltage == cur.batteryVoltage
+                && mHistoryLastWritten.measuredEnergyDetails == null
+                && mHistoryLastWritten.cpuUsageDetails == null) {
             // We can merge this new change in with the last one.  Merging is
             // allowed as long as only the states have changed, and within those states
             // as long as no bit has changed both between now and the last entry, as
@@ -1761,8 +1763,8 @@
      * Saves the accumulated history buffer in the active file, see {@link #getActiveFile()} .
      */
     public void writeHistory() {
-        if (mActiveFile == null) {
-            Slog.w(TAG, "writeHistory: no history file associated with this instance");
+        if (isReadOnly()) {
+            Slog.w(TAG, "writeHistory: this instance instance is read-only");
             return;
         }
 
diff --git a/core/java/com/android/internal/os/BinderCallsStats.java b/core/java/com/android/internal/os/BinderCallsStats.java
index 0a29fc52..eb62cb0 100644
--- a/core/java/com/android/internal/os/BinderCallsStats.java
+++ b/core/java/com/android/internal/os/BinderCallsStats.java
@@ -735,7 +735,7 @@
     }
 
     protected boolean shouldRecordDetailedData() {
-        return mRandom.nextInt() % mPeriodicSamplingInterval == 0;
+        return mRandom.nextInt(mPeriodicSamplingInterval) == 0;
     }
 
     /**
diff --git a/core/java/com/android/internal/os/BinderLatencyObserver.java b/core/java/com/android/internal/os/BinderLatencyObserver.java
index e9d55db..1276fb9 100644
--- a/core/java/com/android/internal/os/BinderLatencyObserver.java
+++ b/core/java/com/android/internal/os/BinderLatencyObserver.java
@@ -236,7 +236,7 @@
     }
 
     protected boolean shouldKeepSample() {
-        return mRandom.nextInt() % mPeriodicSamplingInterval == 0;
+        return mRandom.nextInt(mPeriodicSamplingInterval) == 0;
     }
 
     /** Updates the sampling interval. */
diff --git a/core/java/com/android/internal/os/LooperStats.java b/core/java/com/android/internal/os/LooperStats.java
index 2805dcc..0645eb7 100644
--- a/core/java/com/android/internal/os/LooperStats.java
+++ b/core/java/com/android/internal/os/LooperStats.java
@@ -290,7 +290,7 @@
     }
 
     protected boolean shouldCollectDetailedData() {
-        return ThreadLocalRandom.current().nextInt() % mSamplingInterval == 0;
+        return ThreadLocalRandom.current().nextInt(mSamplingInterval) == 0;
     }
 
     private static class DispatchSession {
diff --git a/core/java/com/android/internal/os/ProcLocksReader.java b/core/java/com/android/internal/os/ProcLocksReader.java
index 2143bc1..9ddb8c7 100644
--- a/core/java/com/android/internal/os/ProcLocksReader.java
+++ b/core/java/com/android/internal/os/ProcLocksReader.java
@@ -16,6 +16,8 @@
 
 package com.android.internal.os;
 
+import android.util.IntArray;
+
 import com.android.internal.util.ProcFileReader;
 
 import java.io.FileInputStream;
@@ -35,6 +37,7 @@
 public class ProcLocksReader {
     private final String mPath;
     private ProcFileReader mReader = null;
+    private IntArray mPids = new IntArray();
 
     public ProcLocksReader() {
         mPath = "/proc/locks";
@@ -51,9 +54,13 @@
     public interface ProcLocksReaderCallback {
         /**
          * Call the callback function of handleBlockingFileLocks().
-         * @param pid Each process that hold file locks blocking other processes.
+         * @param pids Each process that hold file locks blocking other processes.
+         *             pids[0] is the process blocking others
+         *             pids[1..n-1] are the processes being blocked
+         * NOTE: pids are cleared immediately after onBlockingFileLock() returns. If the caller
+         * needs to cache it, please make a copy, e.g. by calling pids.toArray().
          */
-        void onBlockingFileLock(int pid);
+        void onBlockingFileLock(IntArray pids);
     }
 
     /**
@@ -64,8 +71,7 @@
     public void handleBlockingFileLocks(ProcLocksReaderCallback callback) throws IOException {
         long last = -1;
         long id; // ordinal position of the lock in the list
-        int owner = -1; // the PID of the process that owns the lock
-        int pid = -1; // the PID of the process blocking others
+        int pid = -1; // the PID of the process being blocked
 
         if (mReader == null) {
             mReader = new ProcFileReader(new FileInputStream(mPath));
@@ -73,26 +79,49 @@
             mReader.rewind();
         }
 
+        mPids.clear();
         while (mReader.hasMoreData()) {
             id = mReader.nextLong(true); // lock id
             if (id == last) {
-                mReader.finishLine(); // blocked lock
-                if (pid < 0) {
-                    pid = owner; // get pid from the previous line
-                    callback.onBlockingFileLock(pid);
+                // blocked lock found
+                mReader.nextIgnored(); // ->
+                mReader.nextIgnored(); // lock type: POSIX?
+                mReader.nextIgnored(); // lock type: MANDATORY?
+                mReader.nextIgnored(); // lock type: RW?
+
+                pid = mReader.nextInt(); // pid
+                if (pid > 0) {
+                    mPids.add(pid);
                 }
-                continue;
+
+                mReader.finishLine();
             } else {
-                pid = -1; // a new lock
+                // process blocking lock and move on to a new lock
+                if (mPids.size() > 1) {
+                    callback.onBlockingFileLock(mPids);
+                    mPids.clear();
+                }
+
+                // new lock found
+                mReader.nextIgnored(); // lock type: POSIX?
+                mReader.nextIgnored(); // lock type: MANDATORY?
+                mReader.nextIgnored(); // lock type: RW?
+
+                pid = mReader.nextInt(); // pid
+                if (pid > 0) {
+                    if (mPids.size() == 0) {
+                        mPids.add(pid);
+                    } else {
+                        mPids.set(0, pid);
+                    }
+                }
+                mReader.finishLine();
+                last = id;
             }
-
-            mReader.nextIgnored(); // lock type: POSIX?
-            mReader.nextIgnored(); // lock type: MANDATORY?
-            mReader.nextIgnored(); // lock type: RW?
-
-            owner = mReader.nextInt(); // pid
-            mReader.finishLine();
-            last = id;
+        }
+        // The last unprocessed blocking lock immediately before EOF
+        if (mPids.size() > 1) {
+            callback.onBlockingFileLock(mPids);
         }
     }
 }
diff --git a/core/java/com/android/internal/os/RuntimeInit.java b/core/java/com/android/internal/os/RuntimeInit.java
index 28b98d6..8a9445d 100644
--- a/core/java/com/android/internal/os/RuntimeInit.java
+++ b/core/java/com/android/internal/os/RuntimeInit.java
@@ -16,10 +16,14 @@
 
 package com.android.internal.os;
 
+import static com.android.internal.os.SafeZipPathValidatorCallback.VALIDATE_ZIP_PATH_FOR_PATH_TRAVERSAL;
+
+import android.annotation.TestApi;
 import android.app.ActivityManager;
 import android.app.ActivityThread;
 import android.app.ApplicationErrorReport;
 import android.app.IActivityManager;
+import android.app.compat.CompatChanges;
 import android.compat.annotation.UnsupportedAppUsage;
 import android.content.type.DefaultMimeMapFactory;
 import android.net.TrafficStats;
@@ -36,6 +40,7 @@
 
 import dalvik.system.RuntimeHooks;
 import dalvik.system.VMRuntime;
+import dalvik.system.ZipPathValidator;
 
 import libcore.content.type.MimeMap;
 
@@ -260,10 +265,31 @@
          */
         TrafficStats.attachSocketTagger();
 
+        /*
+         * Initialize the zip path validator callback depending on the targetSdk.
+         */
+        initZipPathValidatorCallback();
+
         initialized = true;
     }
 
     /**
+     * If targetSDK >= U: set the safe zip path validator callback which disallows dangerous zip
+     * entry names.
+     * Otherwise: clear the callback to the default validation.
+     *
+     * @hide
+     */
+    @TestApi
+    public static void initZipPathValidatorCallback() {
+        if (CompatChanges.isChangeEnabled(VALIDATE_ZIP_PATH_FOR_PATH_TRAVERSAL)) {
+            ZipPathValidator.setCallback(new SafeZipPathValidatorCallback());
+        } else {
+            ZipPathValidator.clearCallback();
+        }
+    }
+
+    /**
      * Returns an HTTP user agent of the form
      * "Dalvik/1.1.0 (Linux; U; Android Eclair Build/MAIN)".
      */
diff --git a/core/java/com/android/internal/os/SafeZipPathValidatorCallback.java b/core/java/com/android/internal/os/SafeZipPathValidatorCallback.java
new file mode 100644
index 0000000..a6ee108
--- /dev/null
+++ b/core/java/com/android/internal/os/SafeZipPathValidatorCallback.java
@@ -0,0 +1,67 @@
+/*
+ * 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.internal.os;
+
+import android.annotation.NonNull;
+import android.compat.annotation.ChangeId;
+import android.compat.annotation.EnabledSince;
+import android.os.Build;
+
+import dalvik.system.ZipPathValidator;
+
+import java.io.File;
+import java.util.zip.ZipException;
+
+/**
+ * A child implementation of the {@link dalvik.system.ZipPathValidator.Callback} that removes the
+ * risk of zip path traversal vulnerabilities.
+ *
+ * @hide
+ */
+public class SafeZipPathValidatorCallback implements ZipPathValidator.Callback {
+    /**
+     * This change targets zip path traversal vulnerabilities by throwing
+     * {@link java.util.zip.ZipException} if zip path entries contain ".." or start with "/".
+     * <p>
+     * The exception will be thrown in {@link java.util.zip.ZipInputStream#getNextEntry} or
+     * {@link java.util.zip.ZipFile#ZipFile(String)}.
+     * <p>
+     * This validation is enabled for apps with targetSDK >= U.
+     */
+    @ChangeId
+    @EnabledSince(targetSdkVersion = Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+    public static final long VALIDATE_ZIP_PATH_FOR_PATH_TRAVERSAL = 242716250L;
+
+    @Override
+    public void onZipEntryAccess(@NonNull String path) throws ZipException {
+        if (path.startsWith("/")) {
+            throw new ZipException("Invalid zip entry path: " + path);
+        }
+        if (path.contains("..")) {
+            // If the string does contain "..", break it down into its actual name elements to
+            // ensure it actually contains ".." as a name, not just a name like "foo..bar" or even
+            // "foo..", which should be fine.
+            File file = new File(path);
+            while (file != null) {
+                if (file.getName().equals("..")) {
+                    throw new ZipException("Invalid zip entry path: " + path);
+                }
+                file = file.getParentFile();
+            }
+        }
+    }
+}
diff --git a/core/java/com/android/internal/os/TimeoutRecord.java b/core/java/com/android/internal/os/TimeoutRecord.java
index be3f172..680f8fe 100644
--- a/core/java/com/android/internal/os/TimeoutRecord.java
+++ b/core/java/com/android/internal/os/TimeoutRecord.java
@@ -18,6 +18,7 @@
 
 import android.annotation.IntDef;
 import android.annotation.NonNull;
+import android.content.Intent;
 import android.os.SystemClock;
 
 import com.android.internal.os.anr.AnrLatencyTracker;
@@ -92,7 +93,17 @@
 
     /** Record for a broadcast receiver timeout. */
     @NonNull
-    public static TimeoutRecord forBroadcastReceiver(@NonNull String reason) {
+    public static TimeoutRecord forBroadcastReceiver(@NonNull Intent intent) {
+        String reason = "Broadcast of " + intent.toString();
+        return TimeoutRecord.endingNow(TimeoutKind.BROADCAST_RECEIVER, reason);
+    }
+
+    /** Record for a broadcast receiver timeout. */
+    @NonNull
+    public static TimeoutRecord forBroadcastReceiver(@NonNull Intent intent,
+            long timeoutDurationMs) {
+        String reason = "Broadcast of " + intent.toString() + ", waited " + timeoutDurationMs
+                + "ms";
         return TimeoutRecord.endingNow(TimeoutKind.BROADCAST_RECEIVER, reason);
     }
 
diff --git a/core/java/com/android/internal/util/LatencyTracker.java b/core/java/com/android/internal/util/LatencyTracker.java
index 8fcb6d5..4b7b91c 100644
--- a/core/java/com/android/internal/util/LatencyTracker.java
+++ b/core/java/com/android/internal/util/LatencyTracker.java
@@ -468,7 +468,7 @@
         boolean shouldSample;
         int traceThreshold;
         synchronized (mLock) {
-            shouldSample = ThreadLocalRandom.current().nextInt() % mSamplingInterval == 0;
+            shouldSample = ThreadLocalRandom.current().nextInt(mSamplingInterval) == 0;
             traceThreshold = mTraceThresholdPerAction[action];
         }
 
diff --git a/core/java/com/android/internal/view/BaseSurfaceHolder.java b/core/java/com/android/internal/view/BaseSurfaceHolder.java
index 32ce0fe..1ae1307 100644
--- a/core/java/com/android/internal/view/BaseSurfaceHolder.java
+++ b/core/java/com/android/internal/view/BaseSurfaceHolder.java
@@ -241,4 +241,4 @@
         mSurfaceFrame.right = width;
         mSurfaceFrame.bottom = height;
     }
-};
+}
diff --git a/core/jni/android_view_InputEventReceiver.cpp b/core/jni/android_view_InputEventReceiver.cpp
index 2b932cb..98814bf 100644
--- a/core/jni/android_view_InputEventReceiver.cpp
+++ b/core/jni/android_view_InputEventReceiver.cpp
@@ -380,8 +380,8 @@
                 if (kDebugDispatchCycle) {
                     ALOGD("channel '%s' ~ Received motion event.", getInputChannelName().c_str());
                 }
-                MotionEvent* motionEvent = static_cast<MotionEvent*>(inputEvent);
-                if ((motionEvent->getAction() & AMOTION_EVENT_ACTION_MOVE) && outConsumedBatch) {
+                const MotionEvent& motionEvent = static_cast<const MotionEvent&>(*inputEvent);
+                if ((motionEvent.getAction() & AMOTION_EVENT_ACTION_MOVE) && outConsumedBatch) {
                     *outConsumedBatch = true;
                 }
                 inputEventObj = android_view_MotionEvent_obtainAsCopy(env, motionEvent);
diff --git a/core/jni/android_view_MotionEvent.cpp b/core/jni/android_view_MotionEvent.cpp
index a30935b..80df0ea 100644
--- a/core/jni/android_view_MotionEvent.cpp
+++ b/core/jni/android_view_MotionEvent.cpp
@@ -82,7 +82,7 @@
             reinterpret_cast<jlong>(event));
 }
 
-jobject android_view_MotionEvent_obtainAsCopy(JNIEnv* env, const MotionEvent* event) {
+jobject android_view_MotionEvent_obtainAsCopy(JNIEnv* env, const MotionEvent& event) {
     jobject eventObj = env->CallStaticObjectMethod(gMotionEventClassInfo.clazz,
             gMotionEventClassInfo.obtain);
     if (env->ExceptionCheck() || !eventObj) {
@@ -98,7 +98,7 @@
         android_view_MotionEvent_setNativePtr(env, eventObj, destEvent);
     }
 
-    destEvent->copyFrom(event, true);
+    destEvent->copyFrom(&event, true);
     return eventObj;
 }
 
diff --git a/core/jni/android_view_MotionEvent.h b/core/jni/android_view_MotionEvent.h
index 9ce4bf3..32a280e 100644
--- a/core/jni/android_view_MotionEvent.h
+++ b/core/jni/android_view_MotionEvent.h
@@ -26,7 +26,7 @@
 
 /* Obtains an instance of a DVM MotionEvent object as a copy of a native MotionEvent instance.
  * Returns NULL on error. */
-extern jobject android_view_MotionEvent_obtainAsCopy(JNIEnv* env, const MotionEvent* event);
+extern jobject android_view_MotionEvent_obtainAsCopy(JNIEnv* env, const MotionEvent& event);
 
 /* Gets the underlying native MotionEvent instance within a DVM MotionEvent object.
  * Returns NULL if the event is NULL or if it is uninitialized. */
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 7ada548..196ea59 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -6184,6 +6184,116 @@
         android:label="@string/permlab_foregroundService"
         android:protectionLevel="normal|instant" />
 
+    <!-- Allows a regular application to use {@link android.app.Service#startForeground
+         Service.startForeground} with the type "camera".
+         <p>Protection level: normal|instant
+    -->
+    <permission android:name="android.permission.FOREGROUND_SERVICE_CAMERA"
+        android:description="@string/permdesc_foregroundServiceCamera"
+        android:label="@string/permlab_foregroundServiceCamera"
+        android:protectionLevel="normal|instant" />
+
+    <!-- Allows a regular application to use {@link android.app.Service#startForeground
+         Service.startForeground} with the type "connectedDevice".
+         <p>Protection level: normal|instant
+    -->
+    <permission android:name="android.permission.FOREGROUND_SERVICE_CONNECTED_DEVICE"
+        android:description="@string/permdesc_foregroundServiceConnectedDevice"
+        android:label="@string/permlab_foregroundServiceConnectedDevice"
+        android:protectionLevel="normal|instant" />
+
+    <!-- Allows a regular application to use {@link android.app.Service#startForeground
+         Service.startForeground} with the type "dataSync".
+         <p>Protection level: normal|instant
+    -->
+    <permission android:name="android.permission.FOREGROUND_SERVICE_DATA_SYNC"
+        android:description="@string/permdesc_foregroundServiceDataSync"
+        android:label="@string/permlab_foregroundServiceDataSync"
+        android:protectionLevel="normal|instant" />
+
+    <!-- Allows a regular application to use {@link android.app.Service#startForeground
+         Service.startForeground} with the type "location".
+         <p>Protection level: normal|instant
+    -->
+    <permission android:name="android.permission.FOREGROUND_SERVICE_LOCATION"
+        android:description="@string/permdesc_foregroundServiceLocation"
+        android:label="@string/permlab_foregroundServiceLocation"
+        android:protectionLevel="normal|instant" />
+
+    <!-- Allows a regular application to use {@link android.app.Service#startForeground
+         Service.startForeground} with the type "mediaPlayback".
+         <p>Protection level: normal|instant
+    -->
+    <permission android:name="android.permission.FOREGROUND_SERVICE_MEDIA_PLAYBACK"
+        android:description="@string/permdesc_foregroundServiceMediaPlayback"
+        android:label="@string/permlab_foregroundServiceMediaPlayback"
+        android:protectionLevel="normal|instant" />
+
+    <!-- Allows a regular application to use {@link android.app.Service#startForeground
+         Service.startForeground} with the type "mediaProjection".
+         <p>Protection level: normal|instant
+    -->
+    <permission android:name="android.permission.FOREGROUND_SERVICE_MEDIA_PROJECTION"
+        android:description="@string/permdesc_foregroundServiceMediaProjection"
+        android:label="@string/permlab_foregroundServiceMediaProjection"
+        android:protectionLevel="normal|instant" />
+
+    <!-- Allows a regular application to use {@link android.app.Service#startForeground
+         Service.startForeground} with the type "microphone".
+         <p>Protection level: normal|instant
+    -->
+    <permission android:name="android.permission.FOREGROUND_SERVICE_MICROPHONE"
+        android:description="@string/permdesc_foregroundServiceMicrophone"
+        android:label="@string/permlab_foregroundServiceMicrophone"
+        android:protectionLevel="normal|instant" />
+
+    <!-- Allows a regular application to use {@link android.app.Service#startForeground
+         Service.startForeground} with the type "phoneCall".
+         <p>Protection level: normal|instant
+    -->
+    <permission android:name="android.permission.FOREGROUND_SERVICE_PHONE_CALL"
+        android:description="@string/permdesc_foregroundServicePhoneCall"
+        android:label="@string/permlab_foregroundServicePhoneCall"
+        android:protectionLevel="normal|instant" />
+
+    <!-- Allows a regular application to use {@link android.app.Service#startForeground
+         Service.startForeground} with the type "health".
+         <p>Protection level: normal|instant
+    -->
+    <permission android:name="android.permission.FOREGROUND_SERVICE_HEALTH"
+        android:description="@string/permdesc_foregroundServiceHealth"
+        android:label="@string/permlab_foregroundServiceHealth"
+        android:protectionLevel="normal|instant" />
+
+    <!-- Allows a regular application to use {@link android.app.Service#startForeground
+         Service.startForeground} with the type "remoteMessaging".
+         <p>Protection level: normal|instant
+    -->
+    <permission android:name="android.permission.FOREGROUND_SERVICE_REMOTE_MESSAGING"
+        android:description="@string/permdesc_foregroundServiceRemoteMessaging"
+        android:label="@string/permlab_foregroundServiceRemoteMessaging"
+        android:protectionLevel="normal|instant" />
+
+    <!-- Allows a regular application to use {@link android.app.Service#startForeground
+         Service.startForeground} with the type "systemExempted".
+         Apps are allowed to use this type only in the use cases listed in
+         {@link android.content.pm.ServiceInfo#FOREGROUND_SERVICE_TYPE_SYSTEM_EXEMPTED}.
+         <p>Protection level: normal|instant
+    -->
+    <permission android:name="android.permission.FOREGROUND_SERVICE_SYSTEM_EXEMPTED"
+        android:description="@string/permdesc_foregroundServiceSystemExempted"
+        android:label="@string/permlab_foregroundServiceSystemExempted"
+        android:protectionLevel="normal|instant" />
+
+    <!-- Allows a regular application to use {@link android.app.Service#startForeground
+         Service.startForeground} with the type "specialUse".
+         <p>Protection level: signature|appop|instant
+    -->
+    <permission android:name="android.permission.FOREGROUND_SERVICE_SPECIAL_USE"
+        android:description="@string/permdesc_foregroundServiceSpecialUse"
+        android:label="@string/permlab_foregroundServiceSpecialUse"
+        android:protectionLevel="signature|appop|instant" />
+
     <!-- @SystemApi Allows to access all app shortcuts.
          @hide -->
     <permission android:name="android.permission.ACCESS_SHORTCUTS"
diff --git a/core/res/res/anim/dock_bottom_enter.xml b/core/res/res/anim/dock_bottom_enter.xml
deleted file mode 100644
index bfb97b6..0000000
--- a/core/res/res/anim/dock_bottom_enter.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-/* Copyright 2012, The Android Open Source Project
-**
-** Licensed under the Apache License, Version 2.0 (the "License"); 
-** you may not use this file except in compliance with the License. 
-** You may obtain a copy of the License at 
-**
-**     http://www.apache.org/licenses/LICENSE-2.0 
-**
-** Unless required by applicable law or agreed to in writing, software 
-** distributed under the License is distributed on an "AS IS" BASIS, 
-** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 
-** See the License for the specific language governing permissions and 
-** limitations under the License.
-*/
--->
-
-<!-- Animation for when a dock window at the bottom of the screen is entering. -->
-<set xmlns:android="http://schemas.android.com/apk/res/android"
-        android:interpolator="@android:interpolator/decelerate_cubic">
-    <translate android:fromYDelta="100%" android:toYDelta="0"
-        android:duration="@integer/dock_enter_exit_duration"/>
-</set>
diff --git a/core/res/res/anim/dock_bottom_exit.xml b/core/res/res/anim/dock_bottom_exit.xml
deleted file mode 100644
index 4e15448..0000000
--- a/core/res/res/anim/dock_bottom_exit.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-/* Copyright 2012, The Android Open Source Project
-**
-** Licensed under the Apache License, Version 2.0 (the "License"); 
-** you may not use this file except in compliance with the License. 
-** You may obtain a copy of the License at 
-**
-**     http://www.apache.org/licenses/LICENSE-2.0 
-**
-** Unless required by applicable law or agreed to in writing, software 
-** distributed under the License is distributed on an "AS IS" BASIS, 
-** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 
-** See the License for the specific language governing permissions and 
-** limitations under the License.
-*/
--->
-
-<!-- Animation for when a dock window at the bottom of the screen is exiting. -->
-<set xmlns:android="http://schemas.android.com/apk/res/android"
-        android:interpolator="@android:interpolator/accelerate_cubic">
-    <translate android:fromYDelta="0" android:toYDelta="100%"
-        android:startOffset="100" android:duration="@integer/dock_enter_exit_duration"/>
-</set>
diff --git a/core/res/res/anim/dock_bottom_exit_keyguard.xml b/core/res/res/anim/dock_bottom_exit_keyguard.xml
deleted file mode 100644
index 4de3ce5..0000000
--- a/core/res/res/anim/dock_bottom_exit_keyguard.xml
+++ /dev/null
@@ -1,22 +0,0 @@
-<!--
-  ~ Copyright (C) 2016 The Android Open Source Project
-  ~
-  ~ Licensed under the Apache License, Version 2.0 (the "License");
-  ~ you may not use this file except in compliance with the License.
-  ~ You may obtain a copy of the License at
-  ~
-  ~      http://www.apache.org/licenses/LICENSE-2.0
-  ~
-  ~ Unless required by applicable law or agreed to in writing, software
-  ~ distributed under the License is distributed on an "AS IS" BASIS,
-  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-  ~ See the License for the specific language governing permissions and
-  ~ limitations under the License
-  -->
-
-<!-- Animation for when a dock window at the bottom of the screen is exiting while on Keyguard -->
-<set xmlns:android="http://schemas.android.com/apk/res/android"
-    android:interpolator="@android:interpolator/fast_out_linear_in">
-    <translate android:fromYDelta="0" android:toYDelta="100%"
-        android:duration="200"/>
-</set>
\ No newline at end of file
diff --git a/core/res/res/anim/dock_left_enter.xml b/core/res/res/anim/dock_left_enter.xml
deleted file mode 100644
index 7f5dfd5..0000000
--- a/core/res/res/anim/dock_left_enter.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-/* Copyright 2012, The Android Open Source Project
-**
-** Licensed under the Apache License, Version 2.0 (the "License"); 
-** you may not use this file except in compliance with the License. 
-** You may obtain a copy of the License at 
-**
-**     http://www.apache.org/licenses/LICENSE-2.0 
-**
-** Unless required by applicable law or agreed to in writing, software 
-** distributed under the License is distributed on an "AS IS" BASIS, 
-** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 
-** See the License for the specific language governing permissions and 
-** limitations under the License.
-*/
--->
-
-<!-- Animation for when a dock window at the left of the screen is entering. -->
-<set xmlns:android="http://schemas.android.com/apk/res/android"
-        android:interpolator="@android:interpolator/decelerate_cubic">
-    <translate android:fromXDelta="-100%" android:toXDelta="0"
-        android:duration="250"/>
-</set>
diff --git a/core/res/res/anim/dock_left_exit.xml b/core/res/res/anim/dock_left_exit.xml
deleted file mode 100644
index 11cbc0b3..0000000
--- a/core/res/res/anim/dock_left_exit.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-/* Copyright 2012, The Android Open Source Project
-**
-** Licensed under the Apache License, Version 2.0 (the "License"); 
-** you may not use this file except in compliance with the License. 
-** You may obtain a copy of the License at 
-**
-**     http://www.apache.org/licenses/LICENSE-2.0 
-**
-** Unless required by applicable law or agreed to in writing, software 
-** distributed under the License is distributed on an "AS IS" BASIS, 
-** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 
-** See the License for the specific language governing permissions and 
-** limitations under the License.
-*/
--->
-
-<!-- Animation for when a dock window at the right of the screen is exiting. -->
-<set xmlns:android="http://schemas.android.com/apk/res/android"
-        android:interpolator="@android:interpolator/accelerate_cubic">
-    <translate android:fromXDelta="0" android:toXDelta="-100%"
-        android:startOffset="100" android:duration="250"/>
-</set>
diff --git a/core/res/res/anim/dock_right_enter.xml b/core/res/res/anim/dock_right_enter.xml
deleted file mode 100644
index a92c7d2..0000000
--- a/core/res/res/anim/dock_right_enter.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-/* Copyright 2012, The Android Open Source Project
-**
-** Licensed under the Apache License, Version 2.0 (the "License"); 
-** you may not use this file except in compliance with the License. 
-** You may obtain a copy of the License at 
-**
-**     http://www.apache.org/licenses/LICENSE-2.0 
-**
-** Unless required by applicable law or agreed to in writing, software 
-** distributed under the License is distributed on an "AS IS" BASIS, 
-** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 
-** See the License for the specific language governing permissions and 
-** limitations under the License.
-*/
--->
-
-<!-- Animation for when a dock window at the right of the screen is entering. -->
-<set xmlns:android="http://schemas.android.com/apk/res/android"
-        android:interpolator="@android:interpolator/decelerate_cubic">
-    <translate android:fromXDelta="100%" android:toXDelta="0"
-        android:duration="250"/>
-</set>
diff --git a/core/res/res/anim/dock_right_exit.xml b/core/res/res/anim/dock_right_exit.xml
deleted file mode 100644
index 80e4dc3..0000000
--- a/core/res/res/anim/dock_right_exit.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-/* Copyright 2012, The Android Open Source Project
-**
-** Licensed under the Apache License, Version 2.0 (the "License"); 
-** you may not use this file except in compliance with the License. 
-** You may obtain a copy of the License at 
-**
-**     http://www.apache.org/licenses/LICENSE-2.0 
-**
-** Unless required by applicable law or agreed to in writing, software 
-** distributed under the License is distributed on an "AS IS" BASIS, 
-** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 
-** See the License for the specific language governing permissions and 
-** limitations under the License.
-*/
--->
-
-<!-- Animation for when a dock window at the right of the screen is exiting. -->
-<set xmlns:android="http://schemas.android.com/apk/res/android"
-        android:interpolator="@android:interpolator/accelerate_cubic">
-    <translate android:fromXDelta="0" android:toXDelta="100%"
-        android:startOffset="100" android:duration="250"/>
-</set>
diff --git a/core/res/res/anim/dock_top_enter.xml b/core/res/res/anim/dock_top_enter.xml
deleted file mode 100644
index f763fb5..0000000
--- a/core/res/res/anim/dock_top_enter.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-/* Copyright 2007, The Android Open Source Project
-**
-** Licensed under the Apache License, Version 2.0 (the "License"); 
-** you may not use this file except in compliance with the License. 
-** You may obtain a copy of the License at 
-**
-**     http://www.apache.org/licenses/LICENSE-2.0 
-**
-** Unless required by applicable law or agreed to in writing, software 
-** distributed under the License is distributed on an "AS IS" BASIS, 
-** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 
-** See the License for the specific language governing permissions and 
-** limitations under the License.
-*/
--->
-
-<!-- Animation for when a dock window at the top of the screen is entering. -->
-<set xmlns:android="http://schemas.android.com/apk/res/android"
-        android:interpolator="@android:interpolator/decelerate_cubic">
-    <translate android:fromYDelta="-100%" android:toYDelta="0"
-        android:duration="@integer/dock_enter_exit_duration"/>
-</set>
diff --git a/core/res/res/anim/dock_top_exit.xml b/core/res/res/anim/dock_top_exit.xml
deleted file mode 100644
index 995b7d0..0000000
--- a/core/res/res/anim/dock_top_exit.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-/* Copyright 2007, The Android Open Source Project
-**
-** Licensed under the Apache License, Version 2.0 (the "License"); 
-** you may not use this file except in compliance with the License. 
-** You may obtain a copy of the License at 
-**
-**     http://www.apache.org/licenses/LICENSE-2.0 
-**
-** Unless required by applicable law or agreed to in writing, software 
-** distributed under the License is distributed on an "AS IS" BASIS, 
-** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 
-** See the License for the specific language governing permissions and 
-** limitations under the License.
-*/
--->
-
-<!-- Animation for when a dock window at the top of the screen is exiting. -->
-<set xmlns:android="http://schemas.android.com/apk/res/android"
-        android:interpolator="@android:interpolator/accelerate_cubic">
-    <translate android:fromYDelta="0" android:toYDelta="-100%"
-        android:startOffset="100" android:duration="@integer/dock_enter_exit_duration"/>
-</set>
diff --git a/core/res/res/values/attrs_manifest.xml b/core/res/res/values/attrs_manifest.xml
index eac2b94..607467a 100644
--- a/core/res/res/values/attrs_manifest.xml
+++ b/core/res/res/values/attrs_manifest.xml
@@ -1586,19 +1586,71 @@
          together. -->
     <attr name="foregroundServiceType">
         <!-- Data (photo, file, account) upload/download, backup/restore, import/export, fetch,
-        transfer over network between device and cloud.  -->
+            transfer over network between device and cloud.
+
+            <p>For apps with <code>targetSdkVersion</code>
+            {@link android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE} and above, this type should NOT
+            be used: calling
+            {@link android.app.Service#startForeground(int, android.app.Notification, int)} with
+            this type on devices running {@link android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE}
+            is still allowed, but calling it with this type on devices running future platform
+            releases may get a {@link android.app.ForegroundServiceTypeNotAllowedException}.
+        -->
         <flag name="dataSync" value="0x01" />
-        <!-- Music, video, news or other media play. -->
+        <!-- Music, video, news or other media play.
+
+            <p>For apps with <code>targetSdkVersion</code>
+            {@link android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE} and above, starting a foreground
+            service with this type will require permission
+            {@link android.Manifest.permission#FOREGROUND_SERVICE_MEDIA_PLAYBACK}.
+        -->
         <flag name="mediaPlayback" value="0x02" />
         <!-- Ongoing operations related to phone calls, video conferencing,
-             or similar interactive communication. -->
+            or similar interactive communication.
+
+            <p>For apps with <code>targetSdkVersion</code>
+            {@link android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE} and above, starting a foreground
+            service with this type will require permission
+            {@link android.Manifest.permission#FOREGROUND_SERVICE_PHONE_CALL} and
+            {@link android.Manifest.permission#MANAGE_OWN_CALLS}.
+        -->
         <flag name="phoneCall" value="0x04" />
-        <!-- GPS, map, navigation location update. -->
+        <!-- GPS, map, navigation location update.
+
+            <p>For apps with <code>targetSdkVersion</code>
+            {@link android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE} and above, starting a foreground
+            service with this type will require permission
+            {@link android.Manifest.permission#FOREGROUND_SERVICE_LOCATION} and one of the
+            following permissions:
+            {@link android.Manifest.permission#ACCESS_COARSE_LOCATION},
+            {@link android.Manifest.permission#ACCESS_FINE_LOCATION}.
+        -->
         <flag name="location" value="0x08" />
-        <!-- Auto, bluetooth, TV or other devices connection, monitoring and interaction. -->
+        <!-- Auto, bluetooth, TV or other devices connection, monitoring and interaction.
+
+            <p>For apps with <code>targetSdkVersion</code>
+            {@link android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE} and above, starting a foreground
+            service with this type will require permission
+            {@link android.Manifest.permission#FOREGROUND_SERVICE_CONNECTED_DEVICE} and one of the
+            following permissions:
+            {@link android.Manifest.permission#BLUETOOTH_CONNECT},
+            {@link android.Manifest.permission#CHANGE_NETWORK_STATE},
+            {@link android.Manifest.permission#CHANGE_WIFI_STATE},
+            {@link android.Manifest.permission#CHANGE_WIFI_MULTICAST_STATE},
+            {@link android.Manifest.permission#NFC},
+            {@link android.Manifest.permission#TRANSMIT_IR},
+            or has been granted the access to one of the attached USB devices/accessories.
+        -->
         <flag name="connectedDevice" value="0x10" />
         <!-- Managing a media projection session, e.g, for screen recording or taking
-             screenshots.-->
+            screenshots.
+
+            <p>For apps with <code>targetSdkVersion</code>
+            {@link android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE} and above, starting a foreground
+            service with this type will require permission
+            {@link android.Manifest.permission#FOREGROUND_SERVICE_MEDIA_PROJECTION}, and the user
+            must have allowed the screen capture request from this app.
+        -->
         <flag name="mediaProjection" value="0x20" />
         <!-- Use the camera device or record video.
 
@@ -1606,6 +1658,12 @@
             and above, a foreground service will not be able to access the camera if this type is
             not specified in the manifest and in
             {@link android.app.Service#startForeground(int, android.app.Notification, int)}.
+
+            <p>For apps with <code>targetSdkVersion</code>
+            {@link android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE} and above, starting a foreground
+            service with this type will require permission
+            {@link android.Manifest.permission#FOREGROUND_SERVICE_CAMERA} and
+            {@link android.Manifest.permission#CAMERA}.
             -->
         <flag name="camera" value="0x40" />
         <!--Use the microphone device or record audio.
@@ -1614,8 +1672,48 @@
             and above, a foreground service will not be able to access the microphone if this type
             is not specified in the manifest and in
             {@link android.app.Service#startForeground(int, android.app.Notification, int)}.
+
+            <p>For apps with <code>targetSdkVersion</code>
+            {@link android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE} and above, starting a foreground
+            service with this type will require permission
+            {@link android.Manifest.permission#FOREGROUND_SERVICE_MICROPHONE} and one of the
+            following permissions:
+            {@link android.Manifest.permission#CAPTURE_AUDIO_OUTPUT},
+            {@link android.Manifest.permission#RECORD_AUDIO}.
             -->
         <flag name="microphone" value="0x80" />
+        <!--Health, wellness and fitness.
+            <p>Requires the app to hold the permission
+            {@link android.Manifest.permission#FOREGROUND_SERVICE_HEALTH} and one of the following
+            permissions
+            {@link android.Manifest.permission#ACTIVITY_RECOGNITION},
+            {@link android.Manifest.permission#BODY_SENSORS},
+            {@link android.Manifest.permission#HIGH_SAMPLING_RATE_SENSORS}.
+        -->
+        <flag name="health" value="0x100" />
+        <!-- Messaging use cases which host local server to relay messages across devices.
+            <p>Requires the app to hold the permission
+            {@link android.Manifest.permission#FOREGROUND_SERVICE_REMOTE_MESSAGING} in order to use
+            this type.
+        -->
+        <flag name="remoteMessaging" value="0x200" />
+        <!-- The system exmpted foreground service use cases.
+            <p>Requires the app to hold the permission
+            {@link android.Manifest.permission#FOREGROUND_SERVICE_SYSTEM_EXEMPTED} in order to use
+            this type. Apps are allowed to use this type only in the use cases listed in
+            {@link android.content.pm.ServiceInfo#FOREGROUND_SERVICE_TYPE_SYSTEM_EXEMPTED}.
+        -->
+        <flag name="systemExempted" value="0x400" />
+        <!-- Use cases that can't be categorized into any other foreground service types, but also
+            can't use @link android.app.job.JobInfo.Builder} APIs.
+            See {@link android.content.pm.ServiceInfo#FOREGROUND_SERVICE_TYPE_SPECIAL_USE} for the
+            best practice of the use of this type.
+
+            <p>Requires the app to hold the permission
+            {@link android.Manifest.permission#FOREGROUND_SERVICE_SPECIAL_USE} in order to use
+            this type.
+        -->
+        <flag name="specialUse" value="0x40000000" />
     </attr>
 
     <!-- Enable sampled memory bug detection in this process.
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index ac383e6..6c18259 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -2434,7 +2434,7 @@
     <!-- The duration in milliseconds of the dream opening animation.  -->
     <integer name="config_dreamOpenAnimationDuration">250</integer>
     <!-- The duration in milliseconds of the dream closing animation.  -->
-    <integer name="config_dreamCloseAnimationDuration">100</integer>
+    <integer name="config_dreamCloseAnimationDuration">300</integer>
 
     <!-- Whether to dismiss the active dream when an activity is started. Doesn't apply to
          assistant activities (ACTIVITY_TYPE_ASSISTANT) -->
@@ -3688,6 +3688,12 @@
          experience while the device is non-interactive. -->
     <bool name="config_emergencyGestureEnabled">true</bool>
 
+    <!-- Default value for Use Emergency SOS in Settings false = disabled, true = enabled -->
+    <bool name="config_defaultEmergencyGestureEnabled">true</bool>
+
+    <!-- Default value for Use Play countdown alarm in Settings false = disabled, true = enabled -->
+    <bool name="config_defaultEmergencyGestureSoundEnabled">false</bool>
+
     <!-- Allow the gesture power + volume up to change the ringer mode while the device
          is interactive. -->
     <bool name="config_volumeHushGestureEnabled">true</bool>
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index 216975d..7714082 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -1143,6 +1143,66 @@
     <string name="permdesc_foregroundService">Allows the app to make use of foreground services.</string>
 
     <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+    <string name="permlab_foregroundServiceCamera">run foreground service with the type \"camera\"</string>
+    <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+    <string name="permdesc_foregroundServiceCamera">Allows the app to make use of foreground services with the type \"camera\"</string>
+
+    <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+    <string name="permlab_foregroundServiceConnectedDevice">run foreground service with the type \"connectedDevice\"</string>
+    <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+    <string name="permdesc_foregroundServiceConnectedDevice">Allows the app to make use of foreground services with the type \"connectedDevice\"</string>
+
+    <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+    <string name="permlab_foregroundServiceDataSync">run foreground service with the type \"dataSync\"</string>
+    <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+    <string name="permdesc_foregroundServiceDataSync">Allows the app to make use of foreground services with the type \"dataSync\"</string>
+
+    <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+    <string name="permlab_foregroundServiceLocation">run foreground service with the type \"location\"</string>
+    <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+    <string name="permdesc_foregroundServiceLocation">Allows the app to make use of foreground services with the type \"location\"</string>
+
+    <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+    <string name="permlab_foregroundServiceMediaPlayback">run foreground service with the type \"mediaPlayback\"</string>
+    <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+    <string name="permdesc_foregroundServiceMediaPlayback">Allows the app to make use of foreground services with the type \"mediaPlayback\"</string>
+
+    <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+    <string name="permlab_foregroundServiceMediaProjection">run foreground service with the type \"mediaProjection\"</string>
+    <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+    <string name="permdesc_foregroundServiceMediaProjection">Allows the app to make use of foreground services with the type \"mediaProjection\"</string>
+
+    <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+    <string name="permlab_foregroundServiceMicrophone">run foreground service with the type \"microphone\"</string>
+    <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+    <string name="permdesc_foregroundServiceMicrophone">Allows the app to make use of foreground services with the type \"microphone\"</string>
+
+    <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+    <string name="permlab_foregroundServicePhoneCall">run foreground service with the type \"phoneCall\"</string>
+    <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+    <string name="permdesc_foregroundServicePhoneCall">Allows the app to make use of foreground services with the type \"phoneCall\"</string>
+
+    <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+    <string name="permlab_foregroundServiceHealth">run foreground service with the type \"health\"</string>
+    <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+    <string name="permdesc_foregroundServiceHealth">Allows the app to make use of foreground services with the type \"health\"</string>
+
+    <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+    <string name="permlab_foregroundServiceRemoteMessaging">run foreground service with the type \"remoteMessaging\"</string>
+    <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+    <string name="permdesc_foregroundServiceRemoteMessaging">Allows the app to make use of foreground services with the type \"remoteMessaging\"</string>
+
+    <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+    <string name="permlab_foregroundServiceSystemExempted">run foreground service with the type \"systemExempted\"</string>
+    <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+    <string name="permdesc_foregroundServiceSystemExempted">Allows the app to make use of foreground services with the type \"systemExempted\"</string>
+
+    <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+    <string name="permlab_foregroundServiceSpecialUse">run foreground service with the type \"specialUse\"</string>
+    <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+    <string name="permdesc_foregroundServiceSpecialUse">Allows the app to make use of foreground services with the type \"specialUse\"</string>
+
+    <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
     <string name="permlab_getPackageSize">measure app storage space</string>
     <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
     <string name="permdesc_getPackageSize">Allows the app to retrieve its code, data, and cache sizes</string>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 89ec5ba..5811ed9 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -1691,15 +1691,6 @@
 
   <!-- From android.policy -->
   <java-symbol type="anim" name="app_starting_exit" />
-  <java-symbol type="anim" name="dock_top_enter" />
-  <java-symbol type="anim" name="dock_top_exit" />
-  <java-symbol type="anim" name="dock_bottom_enter" />
-  <java-symbol type="anim" name="dock_bottom_exit" />
-  <java-symbol type="anim" name="dock_bottom_exit_keyguard" />
-  <java-symbol type="anim" name="dock_left_enter" />
-  <java-symbol type="anim" name="dock_left_exit" />
-  <java-symbol type="anim" name="dock_right_enter" />
-  <java-symbol type="anim" name="dock_right_exit" />
   <java-symbol type="anim" name="fade_in" />
   <java-symbol type="anim" name="fade_out" />
   <java-symbol type="anim" name="voice_activity_close_exit" />
@@ -3024,6 +3015,8 @@
   <java-symbol type="integer" name="config_cameraLiftTriggerSensorType" />
   <java-symbol type="string" name="config_cameraLiftTriggerSensorStringType" />
   <java-symbol type="bool" name="config_emergencyGestureEnabled" />
+  <java-symbol type="bool" name="config_defaultEmergencyGestureEnabled" />
+  <java-symbol type="bool" name="config_defaultEmergencyGestureSoundEnabled" />
   <java-symbol type="bool" name="config_volumeHushGestureEnabled" />
 
   <java-symbol type="drawable" name="platlogo_m" />
diff --git a/core/tests/benchmarks/src/android/os/ParcelableBenchmark.java b/core/tests/benchmarks/src/android/os/ParcelableBenchmark.java
index 1cf4302..372bca4 100644
--- a/core/tests/benchmarks/src/android/os/ParcelableBenchmark.java
+++ b/core/tests/benchmarks/src/android/os/ParcelableBenchmark.java
@@ -88,6 +88,7 @@
         }
     }
 
+    @SuppressWarnings("ParcelableCreator")
     @SuppressLint("ParcelCreator")
     private static class PointArray implements Parcelable {
         Rect mBounds = new Rect();
diff --git a/core/tests/coretests/src/android/app/backup/BackupRestoreEventLoggerTest.java b/core/tests/coretests/src/android/app/backup/BackupRestoreEventLoggerTest.java
index 67b24ec..bbd2ef3 100644
--- a/core/tests/coretests/src/android/app/backup/BackupRestoreEventLoggerTest.java
+++ b/core/tests/coretests/src/android/app/backup/BackupRestoreEventLoggerTest.java
@@ -63,18 +63,22 @@
     public void testBackupLogger_rejectsRestoreLogs() {
         mLogger = new BackupRestoreEventLogger(BACKUP);
 
-        assertThat(mLogger.logItemsRestored(DATA_TYPE_1, /* count */ 5)).isFalse();
-        assertThat(mLogger.logItemsRestoreFailed(DATA_TYPE_1, /* count */ 5, ERROR_1)).isFalse();
-        assertThat(mLogger.logRestoreMetadata(DATA_TYPE_1, /* metadata */ "metadata")).isFalse();
+        mLogger.logItemsRestored(DATA_TYPE_1, /* count */ 5);
+        mLogger.logItemsRestoreFailed(DATA_TYPE_1, /* count */ 5, ERROR_1);
+        mLogger.logRestoreMetadata(DATA_TYPE_1, /* metadata */ "metadata");
+
+        assertThat(getResultForDataTypeIfPresent(mLogger, DATA_TYPE_1)).isEqualTo(Optional.empty());
     }
 
     @Test
     public void testRestoreLogger_rejectsBackupLogs() {
         mLogger = new BackupRestoreEventLogger(RESTORE);
 
-        assertThat(mLogger.logItemsBackedUp(DATA_TYPE_1, /* count */ 5)).isFalse();
-        assertThat(mLogger.logItemsBackupFailed(DATA_TYPE_1, /* count */ 5, ERROR_1)).isFalse();
-        assertThat(mLogger.logBackupMetaData(DATA_TYPE_1, /* metadata */ "metadata")).isFalse();
+        mLogger.logItemsBackedUp(DATA_TYPE_1, /* count */ 5);
+        mLogger.logItemsBackupFailed(DATA_TYPE_1, /* count */ 5, ERROR_1);
+        mLogger.logBackupMetaData(DATA_TYPE_1, /* metadata */ "metadata");
+
+        assertThat(getResultForDataTypeIfPresent(mLogger, DATA_TYPE_1)).isEqualTo(Optional.empty());
     }
 
     @Test
@@ -83,16 +87,17 @@
 
         for (int i = 0; i < DATA_TYPES_ALLOWED; i++) {
             String dataType = DATA_TYPE_1 + i;
-            assertThat(mLogger.logItemsBackedUp(dataType, /* count */ 5)).isTrue();
-            assertThat(mLogger.logItemsBackupFailed(dataType, /* count */ 5, /* error */ null))
-                    .isTrue();
-            assertThat(mLogger.logBackupMetaData(dataType, METADATA_1)).isTrue();
+            mLogger.logItemsBackedUp(dataType, /* count */ 5);
+            mLogger.logItemsBackupFailed(dataType, /* count */ 5, /* error */ null);
+            mLogger.logBackupMetaData(dataType, METADATA_1);
+
+            assertThat(getResultForDataTypeIfPresent(mLogger, dataType)).isNotEqualTo(
+                    Optional.empty());
         }
 
-        assertThat(mLogger.logItemsBackedUp(DATA_TYPE_2, /* count */ 5)).isFalse();
-        assertThat(mLogger.logItemsBackupFailed(DATA_TYPE_2, /* count */ 5, /* error */ null))
-                .isFalse();
-        assertThat(mLogger.logRestoreMetadata(DATA_TYPE_2, METADATA_1)).isFalse();
+        mLogger.logItemsBackedUp(DATA_TYPE_2, /* count */ 5);
+        mLogger.logItemsBackupFailed(DATA_TYPE_2, /* count */ 5, /* error */ null);
+        mLogger.logRestoreMetadata(DATA_TYPE_2, METADATA_1);
         assertThat(getResultForDataTypeIfPresent(mLogger, DATA_TYPE_2)).isEqualTo(Optional.empty());
     }
 
@@ -102,16 +107,17 @@
 
         for (int i = 0; i < DATA_TYPES_ALLOWED; i++) {
             String dataType = DATA_TYPE_1 + i;
-            assertThat(mLogger.logItemsRestored(dataType, /* count */ 5)).isTrue();
-            assertThat(mLogger.logItemsRestoreFailed(dataType, /* count */ 5, /* error */ null))
-                    .isTrue();
-            assertThat(mLogger.logRestoreMetadata(dataType, METADATA_1)).isTrue();
+            mLogger.logItemsRestored(dataType, /* count */ 5);
+            mLogger.logItemsRestoreFailed(dataType, /* count */ 5, /* error */ null);
+            mLogger.logRestoreMetadata(dataType, METADATA_1);
+
+            assertThat(getResultForDataTypeIfPresent(mLogger, dataType)).isNotEqualTo(
+                    Optional.empty());
         }
 
-        assertThat(mLogger.logItemsRestored(DATA_TYPE_2, /* count */ 5)).isFalse();
-        assertThat(mLogger.logItemsRestoreFailed(DATA_TYPE_2, /* count */ 5, /* error */ null))
-                .isFalse();
-        assertThat(mLogger.logRestoreMetadata(DATA_TYPE_2, METADATA_1)).isFalse();
+        mLogger.logItemsRestored(DATA_TYPE_2, /* count */ 5);
+        mLogger.logItemsRestoreFailed(DATA_TYPE_2, /* count */ 5, /* error */ null);
+        mLogger.logRestoreMetadata(DATA_TYPE_2, METADATA_1);
         assertThat(getResultForDataTypeIfPresent(mLogger, DATA_TYPE_2)).isEqualTo(Optional.empty());
     }
 
diff --git a/core/tests/coretests/src/android/app/timezonedetector/ParcelableTestSupport.java b/core/tests/coretests/src/android/app/time/ParcelableTestSupport.java
similarity index 82%
rename from core/tests/coretests/src/android/app/timezonedetector/ParcelableTestSupport.java
rename to core/tests/coretests/src/android/app/time/ParcelableTestSupport.java
index 0073d86..13e5e14 100644
--- a/core/tests/coretests/src/android/app/timezonedetector/ParcelableTestSupport.java
+++ b/core/tests/coretests/src/android/app/time/ParcelableTestSupport.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package android.app.timezonedetector;
+package android.app.time;
 
 import static org.junit.Assert.assertEquals;
 
@@ -48,6 +48,13 @@
     }
 
     public static <T extends Parcelable> void assertRoundTripParcelable(T instance) {
-        assertEquals(instance, roundTripParcelable(instance));
+        assertEqualsAndHashCode(instance, roundTripParcelable(instance));
+    }
+
+    /** Asserts that the objects are equal and return identical hash codes. */
+    public static void assertEqualsAndHashCode(Object one, Object two) {
+        assertEquals(one, two);
+        assertEquals(two, one);
+        assertEquals(one.hashCode(), two.hashCode());
     }
 }
diff --git a/core/tests/coretests/src/android/app/time/TimeCapabilitiesTest.java b/core/tests/coretests/src/android/app/time/TimeCapabilitiesTest.java
index c9b96c6..1a276ad 100644
--- a/core/tests/coretests/src/android/app/time/TimeCapabilitiesTest.java
+++ b/core/tests/coretests/src/android/app/time/TimeCapabilitiesTest.java
@@ -21,7 +21,8 @@
 import static android.app.time.Capabilities.CAPABILITY_NOT_APPLICABLE;
 import static android.app.time.Capabilities.CAPABILITY_NOT_SUPPORTED;
 import static android.app.time.Capabilities.CAPABILITY_POSSESSED;
-import static android.app.timezonedetector.ParcelableTestSupport.assertRoundTripParcelable;
+import static android.app.time.ParcelableTestSupport.assertEqualsAndHashCode;
+import static android.app.time.ParcelableTestSupport.assertRoundTripParcelable;
 
 import static com.google.common.truth.Truth.assertThat;
 
@@ -55,7 +56,7 @@
         {
             TimeCapabilities one = builder1.build();
             TimeCapabilities two = builder2.build();
-            assertEquals(one, two);
+            assertEqualsAndHashCode(one, two);
         }
 
         builder2.setConfigureAutoDetectionEnabledCapability(CAPABILITY_NOT_ALLOWED);
@@ -69,7 +70,7 @@
         {
             TimeCapabilities one = builder1.build();
             TimeCapabilities two = builder2.build();
-            assertEquals(one, two);
+            assertEqualsAndHashCode(one, two);
         }
 
         builder2.setSetManualTimeCapability(CAPABILITY_NOT_ALLOWED);
@@ -83,7 +84,7 @@
         {
             TimeCapabilities one = builder1.build();
             TimeCapabilities two = builder2.build();
-            assertEquals(one, two);
+            assertEqualsAndHashCode(one, two);
         }
     }
 
diff --git a/core/tests/coretests/src/android/app/time/TimeStateTest.java b/core/tests/coretests/src/android/app/time/TimeStateTest.java
index bce0909..25e6e2b 100644
--- a/core/tests/coretests/src/android/app/time/TimeStateTest.java
+++ b/core/tests/coretests/src/android/app/time/TimeStateTest.java
@@ -16,7 +16,8 @@
 
 package android.app.time;
 
-import static android.app.timezonedetector.ParcelableTestSupport.assertRoundTripParcelable;
+import static android.app.time.ParcelableTestSupport.assertEqualsAndHashCode;
+import static android.app.time.ParcelableTestSupport.assertRoundTripParcelable;
 import static android.app.timezonedetector.ShellCommandTestSupport.createShellCommandWithArgsAndOptions;
 
 import static org.junit.Assert.assertEquals;
@@ -52,11 +53,6 @@
         assertNotEquals(time1False_1, time2False);
     }
 
-    private static void assertEqualsAndHashCode(Object one, Object two) {
-        assertEquals(one, two);
-        assertEquals(one.hashCode(), two.hashCode());
-    }
-
     @Test
     public void testParceling() {
         UnixEpochTime time = new UnixEpochTime(1, 2);
diff --git a/core/tests/coretests/src/android/app/time/TimeZoneCapabilitiesTest.java b/core/tests/coretests/src/android/app/time/TimeZoneCapabilitiesTest.java
index 3f7da8a..8bed31f 100644
--- a/core/tests/coretests/src/android/app/time/TimeZoneCapabilitiesTest.java
+++ b/core/tests/coretests/src/android/app/time/TimeZoneCapabilitiesTest.java
@@ -18,7 +18,7 @@
 
 import static android.app.time.Capabilities.CAPABILITY_NOT_ALLOWED;
 import static android.app.time.Capabilities.CAPABILITY_POSSESSED;
-import static android.app.timezonedetector.ParcelableTestSupport.assertRoundTripParcelable;
+import static android.app.time.ParcelableTestSupport.assertRoundTripParcelable;
 
 import static com.google.common.truth.Truth.assertThat;
 
diff --git a/core/tests/coretests/src/android/app/time/TimeZoneStateTest.java b/core/tests/coretests/src/android/app/time/TimeZoneStateTest.java
index 35a9dbc..595b700 100644
--- a/core/tests/coretests/src/android/app/time/TimeZoneStateTest.java
+++ b/core/tests/coretests/src/android/app/time/TimeZoneStateTest.java
@@ -16,7 +16,8 @@
 
 package android.app.time;
 
-import static android.app.timezonedetector.ParcelableTestSupport.assertRoundTripParcelable;
+import static android.app.time.ParcelableTestSupport.assertEqualsAndHashCode;
+import static android.app.time.ParcelableTestSupport.assertRoundTripParcelable;
 import static android.app.timezonedetector.ShellCommandTestSupport.createShellCommandWithArgsAndOptions;
 
 import static org.junit.Assert.assertEquals;
@@ -52,11 +53,6 @@
         assertNotEquals(zone1False_1, zone2False);
     }
 
-    private static void assertEqualsAndHashCode(Object one, Object two) {
-        assertEquals(one, two);
-        assertEquals(one.hashCode(), two.hashCode());
-    }
-
     @Test
     public void testParceling() {
         assertRoundTripParcelable(new TimeZoneState("Europe/London", true));
diff --git a/core/tests/coretests/src/android/app/timedetector/ManualTimeSuggestionTest.java b/core/tests/coretests/src/android/app/timedetector/ManualTimeSuggestionTest.java
index 0c7c8c1..28da164 100644
--- a/core/tests/coretests/src/android/app/timedetector/ManualTimeSuggestionTest.java
+++ b/core/tests/coretests/src/android/app/timedetector/ManualTimeSuggestionTest.java
@@ -16,8 +16,8 @@
 
 package android.app.timedetector;
 
-import static android.app.timezonedetector.ParcelableTestSupport.assertRoundTripParcelable;
-import static android.app.timezonedetector.ParcelableTestSupport.roundTripParcelable;
+import static android.app.time.ParcelableTestSupport.assertRoundTripParcelable;
+import static android.app.time.ParcelableTestSupport.roundTripParcelable;
 import static android.app.timezonedetector.ShellCommandTestSupport.createShellCommandWithArgsAndOptions;
 
 import static org.junit.Assert.assertEquals;
diff --git a/core/tests/coretests/src/android/app/timedetector/TelephonyTimeSuggestionTest.java b/core/tests/coretests/src/android/app/timedetector/TelephonyTimeSuggestionTest.java
index 26cb902..e9ca069 100644
--- a/core/tests/coretests/src/android/app/timedetector/TelephonyTimeSuggestionTest.java
+++ b/core/tests/coretests/src/android/app/timedetector/TelephonyTimeSuggestionTest.java
@@ -16,8 +16,8 @@
 
 package android.app.timedetector;
 
-import static android.app.timezonedetector.ParcelableTestSupport.assertRoundTripParcelable;
-import static android.app.timezonedetector.ParcelableTestSupport.roundTripParcelable;
+import static android.app.time.ParcelableTestSupport.assertRoundTripParcelable;
+import static android.app.time.ParcelableTestSupport.roundTripParcelable;
 import static android.app.timezonedetector.ShellCommandTestSupport.createShellCommandWithArgsAndOptions;
 
 import static org.junit.Assert.assertEquals;
diff --git a/core/tests/coretests/src/android/app/timezonedetector/ManualTimeZoneSuggestionTest.java b/core/tests/coretests/src/android/app/timezonedetector/ManualTimeZoneSuggestionTest.java
index 17838bb..b5bdea7 100644
--- a/core/tests/coretests/src/android/app/timezonedetector/ManualTimeZoneSuggestionTest.java
+++ b/core/tests/coretests/src/android/app/timezonedetector/ManualTimeZoneSuggestionTest.java
@@ -16,8 +16,8 @@
 
 package android.app.timezonedetector;
 
-import static android.app.timezonedetector.ParcelableTestSupport.assertRoundTripParcelable;
-import static android.app.timezonedetector.ParcelableTestSupport.roundTripParcelable;
+import static android.app.time.ParcelableTestSupport.assertRoundTripParcelable;
+import static android.app.time.ParcelableTestSupport.roundTripParcelable;
 import static android.app.timezonedetector.ShellCommandTestSupport.createShellCommandWithArgsAndOptions;
 
 import static org.junit.Assert.assertEquals;
diff --git a/core/tests/coretests/src/android/app/timezonedetector/TelephonyTimeZoneSuggestionTest.java b/core/tests/coretests/src/android/app/timezonedetector/TelephonyTimeZoneSuggestionTest.java
index 28009d4..d5dcac2 100644
--- a/core/tests/coretests/src/android/app/timezonedetector/TelephonyTimeZoneSuggestionTest.java
+++ b/core/tests/coretests/src/android/app/timezonedetector/TelephonyTimeZoneSuggestionTest.java
@@ -16,8 +16,8 @@
 
 package android.app.timezonedetector;
 
-import static android.app.timezonedetector.ParcelableTestSupport.assertRoundTripParcelable;
-import static android.app.timezonedetector.ParcelableTestSupport.roundTripParcelable;
+import static android.app.time.ParcelableTestSupport.assertRoundTripParcelable;
+import static android.app.time.ParcelableTestSupport.roundTripParcelable;
 import static android.app.timezonedetector.ShellCommandTestSupport.createShellCommandWithArgsAndOptions;
 
 import static org.junit.Assert.assertEquals;
diff --git a/core/tests/coretests/src/android/content/pm/PackageManagerPropertyTests.java b/core/tests/coretests/src/android/content/pm/PackageManagerPropertyTests.java
index d505492..86e95832 100644
--- a/core/tests/coretests/src/android/content/pm/PackageManagerPropertyTests.java
+++ b/core/tests/coretests/src/android/content/pm/PackageManagerPropertyTests.java
@@ -20,6 +20,7 @@
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.assertThrows;
 import static org.junit.Assert.fail;
 
 import android.content.pm.PackageManager.Property;
@@ -162,40 +163,30 @@
 
     @Test
     public void testProperty_invalidName() throws Exception {
-        try {
+        assertThrows(NullPointerException.class, () -> {
             final Property p = new Property(null, 1, "android", null);
-            fail("expected assertion error");
-        } catch (AssertionError expected) {
-        }
+        });
     }
 
     @Test
     public void testProperty_invalidType() throws Exception {
-        try {
+        assertThrows(IllegalArgumentException.class, () -> {
             final Property p = new Property("invalidTypeProperty", 0, "android", null);
-            fail("expected assertion error");
-        } catch (AssertionError expected) {
-        }
+        });
 
-        try {
+        assertThrows(IllegalArgumentException.class, () -> {
             final Property p = new Property("invalidTypeProperty", 6, "android", null);
-            fail("expected assertion error");
-        } catch (AssertionError expected) {
-        }
+        });
 
-        try {
+        assertThrows(IllegalArgumentException.class, () -> {
             final Property p = new Property("invalidTypeProperty", -1, "android", null);
-            fail("expected assertion error");
-        } catch (AssertionError expected) {
-        }
+        });
     }
 
     @Test
     public void testProperty_noPackageName() throws Exception {
-        try {
+        assertThrows(NullPointerException.class, () -> {
             final Property p = new Property(null, 1, null, null);
-            fail("expected assertion error");
-        } catch (AssertionError expected) {
-        }
+        });
     }
 }
diff --git a/core/tests/coretests/src/android/service/timezone/TimeZoneProviderEventTest.java b/core/tests/coretests/src/android/service/timezone/TimeZoneProviderEventTest.java
index 86ebdf3..7f772dd 100644
--- a/core/tests/coretests/src/android/service/timezone/TimeZoneProviderEventTest.java
+++ b/core/tests/coretests/src/android/service/timezone/TimeZoneProviderEventTest.java
@@ -16,7 +16,7 @@
 
 package android.service.timezone;
 
-import static android.app.timezonedetector.ParcelableTestSupport.assertRoundTripParcelable;
+import static android.app.time.ParcelableTestSupport.assertRoundTripParcelable;
 import static android.service.timezone.TimeZoneProviderEvent.EVENT_TYPE_PERMANENT_FAILURE;
 import static android.service.timezone.TimeZoneProviderEvent.EVENT_TYPE_SUGGESTION;
 import static android.service.timezone.TimeZoneProviderEvent.EVENT_TYPE_UNCERTAIN;
diff --git a/core/tests/coretests/src/android/service/timezone/TimeZoneProviderStatusTest.java b/core/tests/coretests/src/android/service/timezone/TimeZoneProviderStatusTest.java
index b7a595c..9006cd9 100644
--- a/core/tests/coretests/src/android/service/timezone/TimeZoneProviderStatusTest.java
+++ b/core/tests/coretests/src/android/service/timezone/TimeZoneProviderStatusTest.java
@@ -16,20 +16,15 @@
 
 package android.service.timezone;
 
-import static android.app.timezonedetector.ParcelableTestSupport.assertRoundTripParcelable;
-import static android.service.timezone.TimeZoneProviderStatus.DEPENDENCY_STATUS_BLOCKED_BY_ENVIRONMENT;
 import static android.service.timezone.TimeZoneProviderStatus.DEPENDENCY_STATUS_BLOCKED_BY_SETTINGS;
 import static android.service.timezone.TimeZoneProviderStatus.DEPENDENCY_STATUS_OK;
-import static android.service.timezone.TimeZoneProviderStatus.OPERATION_STATUS_FAILED;
 import static android.service.timezone.TimeZoneProviderStatus.OPERATION_STATUS_OK;
 
 import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotEquals;
-import static org.junit.Assert.assertNotSame;
-import static org.junit.Assert.assertThrows;
 
 import org.junit.Test;
 
+/** Non-SDK tests. See CTS for SDK API tests. */
 public class TimeZoneProviderStatusTest {
 
     @Test
@@ -42,81 +37,4 @@
 
         assertEquals(status, TimeZoneProviderStatus.parseProviderStatus(status.toString()));
     }
-
-    @Test
-    public void testStatusValidation() {
-        TimeZoneProviderStatus status = new TimeZoneProviderStatus.Builder()
-                .setLocationDetectionDependencyStatus(DEPENDENCY_STATUS_OK)
-                .setConnectivityDependencyStatus(DEPENDENCY_STATUS_OK)
-                .setTimeZoneResolutionOperationStatus(OPERATION_STATUS_OK)
-                .build();
-
-        assertThrows(IllegalArgumentException.class,
-                () -> new TimeZoneProviderStatus.Builder(status)
-                        .setLocationDetectionDependencyStatus(-1)
-                        .build());
-        assertThrows(IllegalArgumentException.class,
-                () -> new TimeZoneProviderStatus.Builder(status)
-                        .setConnectivityDependencyStatus(-1)
-                        .build());
-        assertThrows(IllegalArgumentException.class,
-                () -> new TimeZoneProviderStatus.Builder(status)
-                        .setTimeZoneResolutionOperationStatus(-1)
-                        .build());
-    }
-
-    @Test
-    public void testEqualsAndHashcode() {
-        TimeZoneProviderStatus status1_1 = new TimeZoneProviderStatus.Builder()
-                .setLocationDetectionDependencyStatus(DEPENDENCY_STATUS_OK)
-                .setConnectivityDependencyStatus(DEPENDENCY_STATUS_OK)
-                .setTimeZoneResolutionOperationStatus(OPERATION_STATUS_OK)
-                .build();
-        assertEqualsAndHashcode(status1_1, status1_1);
-        assertNotEquals(status1_1, null);
-
-        {
-            TimeZoneProviderStatus status1_2 =
-                    new TimeZoneProviderStatus.Builder(status1_1).build();
-            assertEqualsAndHashcode(status1_1, status1_2);
-            assertNotSame(status1_1, status1_2);
-        }
-
-        {
-            TimeZoneProviderStatus status2 = new TimeZoneProviderStatus.Builder(status1_1)
-                    .setLocationDetectionDependencyStatus(DEPENDENCY_STATUS_BLOCKED_BY_ENVIRONMENT)
-                    .build();
-            assertNotEquals(status1_1, status2);
-        }
-
-        {
-            TimeZoneProviderStatus status2 = new TimeZoneProviderStatus.Builder(status1_1)
-                    .setConnectivityDependencyStatus(DEPENDENCY_STATUS_BLOCKED_BY_ENVIRONMENT)
-                    .build();
-            assertNotEquals(status1_1, status2);
-        }
-
-        {
-            TimeZoneProviderStatus status2 = new TimeZoneProviderStatus.Builder(status1_1)
-                    .setTimeZoneResolutionOperationStatus(OPERATION_STATUS_FAILED)
-                    .build();
-            assertNotEquals(status1_1, status2);
-        }
-    }
-
-    private static void assertEqualsAndHashcode(Object one, Object two) {
-        assertEquals(one, two);
-        assertEquals(two, one);
-        assertEquals(one.hashCode(), two.hashCode());
-    }
-
-    @Test
-    public void testParcelable() {
-        TimeZoneProviderStatus status = new TimeZoneProviderStatus.Builder()
-                .setLocationDetectionDependencyStatus(DEPENDENCY_STATUS_OK)
-                .setConnectivityDependencyStatus(DEPENDENCY_STATUS_BLOCKED_BY_ENVIRONMENT)
-                .setTimeZoneResolutionOperationStatus(OPERATION_STATUS_FAILED)
-                .build();
-        assertRoundTripParcelable(status);
-    }
 }
diff --git a/core/tests/coretests/src/android/text/format/DateFormatTest.java b/core/tests/coretests/src/android/text/format/DateFormatTest.java
index 212cc44..8459330 100644
--- a/core/tests/coretests/src/android/text/format/DateFormatTest.java
+++ b/core/tests/coretests/src/android/text/format/DateFormatTest.java
@@ -156,8 +156,8 @@
     @DisableCompatChanges({DateFormat.DISALLOW_DUPLICATE_FIELD_IN_SKELETON})
     public void testGetBestDateTimePattern_enableDuplicateField() {
         // en-US uses 12-hour format by default.
-        assertEquals("h:mm a", DateFormat.getBestDateTimePattern(Locale.US, "jmma"));
-        assertEquals("h:mm a", DateFormat.getBestDateTimePattern(Locale.US, "ahmma"));
+        assertEquals("h:mm\u202fa", DateFormat.getBestDateTimePattern(Locale.US, "jmma"));
+        assertEquals("h:mm\u202fa", DateFormat.getBestDateTimePattern(Locale.US, "ahmma"));
     }
 
     private static void assertIllegalArgumentException(Locale l, String skeleton) {
diff --git a/core/tests/coretests/src/android/text/format/DateIntervalFormatTest.java b/core/tests/coretests/src/android/text/format/DateIntervalFormatTest.java
index 9c06395..de7244d 100644
--- a/core/tests/coretests/src/android/text/format/DateIntervalFormatTest.java
+++ b/core/tests/coretests/src/android/text/format/DateIntervalFormatTest.java
@@ -93,7 +93,8 @@
         assertEquals("January 19",
                 formatDateRange(en_US, tz, timeWithCurrentYear, timeWithCurrentYear + HOUR,
                         FORMAT_SHOW_DATE));
-        assertEquals("3:30 AM", formatDateRange(en_US, tz, fixedTime, fixedTime, FORMAT_SHOW_TIME));
+        assertEquals("3:30\u202fAM", formatDateRange(en_US, tz, fixedTime, fixedTime,
+                FORMAT_SHOW_TIME));
         assertEquals("January 19, 2009",
                 formatDateRange(en_US, tz, fixedTime, fixedTime + HOUR, FORMAT_SHOW_YEAR));
         assertEquals("January 19",
@@ -101,27 +102,27 @@
         assertEquals("January",
                 formatDateRange(en_US, tz, timeWithCurrentYear, timeWithCurrentYear + HOUR,
                         FORMAT_NO_MONTH_DAY));
-        assertEquals("3:30 AM",
+        assertEquals("3:30\u202fAM",
                 formatDateRange(en_US, tz, fixedTime, fixedTime, FORMAT_12HOUR | FORMAT_SHOW_TIME));
         assertEquals("03:30",
                 formatDateRange(en_US, tz, fixedTime, fixedTime, FORMAT_24HOUR | FORMAT_SHOW_TIME));
-        assertEquals("3:30 AM", formatDateRange(en_US, tz, fixedTime, fixedTime,
+        assertEquals("3:30\u202fAM", formatDateRange(en_US, tz, fixedTime, fixedTime,
                 FORMAT_12HOUR /*| FORMAT_CAP_AMPM*/ | FORMAT_SHOW_TIME));
-        assertEquals("12:00 PM",
+        assertEquals("12:00\u202fPM",
                 formatDateRange(en_US, tz, fixedTime + noonDuration, fixedTime + noonDuration,
                         FORMAT_12HOUR | FORMAT_SHOW_TIME));
-        assertEquals("12:00 PM",
+        assertEquals("12:00\u202fPM",
                 formatDateRange(en_US, tz, fixedTime + noonDuration, fixedTime + noonDuration,
                         FORMAT_12HOUR | FORMAT_SHOW_TIME /*| FORMAT_CAP_NOON*/));
-        assertEquals("12:00 PM",
+        assertEquals("12:00\u202fPM",
                 formatDateRange(en_US, tz, fixedTime + noonDuration, fixedTime + noonDuration,
                         FORMAT_12HOUR /*| FORMAT_NO_NOON*/ | FORMAT_SHOW_TIME));
-        assertEquals("12:00 AM", formatDateRange(en_US, tz, fixedTime - midnightDuration,
+        assertEquals("12:00\u202fAM", formatDateRange(en_US, tz, fixedTime - midnightDuration,
                 fixedTime - midnightDuration,
                 FORMAT_12HOUR | FORMAT_SHOW_TIME /*| FORMAT_NO_MIDNIGHT*/));
-        assertEquals("3:30 AM",
+        assertEquals("3:30\u202fAM",
                 formatDateRange(en_US, tz, fixedTime, fixedTime, FORMAT_SHOW_TIME | FORMAT_UTC));
-        assertEquals("3 AM", formatDateRange(en_US, tz, onTheHour, onTheHour,
+        assertEquals("3\u202fAM", formatDateRange(en_US, tz, onTheHour, onTheHour,
                 FORMAT_SHOW_TIME | FORMAT_ABBREV_TIME));
         assertEquals("Mon", formatDateRange(en_US, tz, fixedTime, fixedTime + HOUR,
                 FORMAT_SHOW_WEEKDAY | FORMAT_ABBREV_WEEKDAY));
@@ -134,13 +135,13 @@
 
         assertEquals("1/19/2009", formatDateRange(en_US, tz, fixedTime, fixedTime + 3 * HOUR,
                 FORMAT_SHOW_YEAR | FORMAT_NUMERIC_DATE));
-        assertEquals("1/19/2009 – 1/22/2009",
+        assertEquals("1/19/2009\u2009\u2013\u20091/22/2009",
                 formatDateRange(en_US, tz, fixedTime, fixedTime + 3 * DAY,
                         FORMAT_SHOW_YEAR | FORMAT_NUMERIC_DATE));
-        assertEquals("1/19/2009 – 4/22/2009",
+        assertEquals("1/19/2009\u2009\u2013\u20094/22/2009",
                 formatDateRange(en_US, tz, fixedTime, fixedTime + 3 * MONTH,
                         FORMAT_SHOW_YEAR | FORMAT_NUMERIC_DATE));
-        assertEquals("1/19/2009 – 2/9/2012",
+        assertEquals("1/19/2009\u2009\u2013\u20092/9/2012",
                 formatDateRange(en_US, tz, fixedTime, fixedTime + 3 * YEAR,
                         FORMAT_SHOW_YEAR | FORMAT_NUMERIC_DATE));
 
@@ -151,7 +152,7 @@
         assertEquals("19.01. – 22.04.2009",
                 formatDateRange(de_DE, tz, fixedTime, fixedTime + 3 * MONTH,
                         FORMAT_SHOW_YEAR | FORMAT_NUMERIC_DATE));
-        assertEquals("19.01.2009 – 09.02.2012",
+        assertEquals("19.01.2009\u2009\u2013\u200909.02.2012",
                 formatDateRange(de_DE, tz, fixedTime, fixedTime + 3 * YEAR,
                         FORMAT_SHOW_YEAR | FORMAT_NUMERIC_DATE));
 
@@ -169,48 +170,48 @@
 
         assertEquals("19/1/2009", formatDateRange(es_ES, tz, fixedTime, fixedTime + HOUR,
                 FORMAT_SHOW_YEAR | FORMAT_NUMERIC_DATE));
-        assertEquals("19/1/2009 – 22/1/2009",
+        assertEquals("19/1/2009\u2009\u2013\u200922/1/2009",
                 formatDateRange(es_ES, tz, fixedTime, fixedTime + 3 * DAY,
                         FORMAT_SHOW_YEAR | FORMAT_NUMERIC_DATE));
-        assertEquals("19/1/2009 – 22/4/2009",
+        assertEquals("19/1/2009\u2009\u2013\u200922/4/2009",
                 formatDateRange(es_ES, tz, fixedTime, fixedTime + 3 * MONTH,
                         FORMAT_SHOW_YEAR | FORMAT_NUMERIC_DATE));
-        assertEquals("19/1/2009 – 9/2/2012",
+        assertEquals("19/1/2009\u2009\u2013\u20099/2/2012",
                 formatDateRange(es_ES, tz, fixedTime, fixedTime + 3 * YEAR,
                         FORMAT_SHOW_YEAR | FORMAT_NUMERIC_DATE));
 
         // These are some random other test cases I came up with.
 
-        assertEquals("January 19 – 22, 2009",
+        assertEquals("January 19\u2009\u2013\u200922, 2009",
                 formatDateRange(en_US, tz, fixedTime, fixedTime + 3 * DAY, 0));
-        assertEquals("Jan 19 – 22, 2009", formatDateRange(en_US, tz, fixedTime, fixedTime + 3 * DAY,
-                FORMAT_SHOW_DATE | FORMAT_ABBREV_ALL));
-        assertEquals("Mon, Jan 19 – Thu, Jan 22, 2009",
+        assertEquals("Jan 19\u2009\u2013\u200922, 2009", formatDateRange(en_US, tz, fixedTime,
+                fixedTime + 3 * DAY, FORMAT_SHOW_DATE | FORMAT_ABBREV_ALL));
+        assertEquals("Mon, Jan 19\u2009\u2013\u2009Thu, Jan 22, 2009",
                 formatDateRange(en_US, tz, fixedTime, fixedTime + 3 * DAY,
                         FORMAT_SHOW_WEEKDAY | FORMAT_ABBREV_ALL));
-        assertEquals("Monday, January 19 – Thursday, January 22, 2009",
+        assertEquals("Monday, January 19\u2009\u2013\u2009Thursday, January 22, 2009",
                 formatDateRange(en_US, tz, fixedTime, fixedTime + 3 * DAY, FORMAT_SHOW_WEEKDAY));
 
-        assertEquals("January 19 – April 22, 2009",
+        assertEquals("January 19\u2009\u2013\u2009April 22, 2009",
                 formatDateRange(en_US, tz, fixedTime, fixedTime + 3 * MONTH, 0));
-        assertEquals("Jan 19 – Apr 22, 2009",
+        assertEquals("Jan 19\u2009\u2013\u2009Apr 22, 2009",
                 formatDateRange(en_US, tz, fixedTime, fixedTime + 3 * MONTH,
                         FORMAT_SHOW_DATE | FORMAT_ABBREV_ALL));
-        assertEquals("Mon, Jan 19 – Wed, Apr 22, 2009",
+        assertEquals("Mon, Jan 19\u2009\u2013\u2009Wed, Apr 22, 2009",
                 formatDateRange(en_US, tz, fixedTime, fixedTime + 3 * MONTH,
                         FORMAT_SHOW_WEEKDAY | FORMAT_ABBREV_ALL));
-        assertEquals("January – April 2009",
+        assertEquals("January\u2009\u2013\u2009April 2009",
                 formatDateRange(en_US, tz, fixedTime, fixedTime + 3 * MONTH, FORMAT_NO_MONTH_DAY));
 
-        assertEquals("Jan 19, 2009 – Feb 9, 2012",
+        assertEquals("Jan 19, 2009\u2009\u2013\u2009Feb 9, 2012",
                 formatDateRange(en_US, tz, fixedTime, fixedTime + 3 * YEAR,
                         FORMAT_SHOW_DATE | FORMAT_ABBREV_ALL));
-        assertEquals("Jan 2009 – Feb 2012",
+        assertEquals("Jan 2009\u2009\u2013\u2009Feb 2012",
                 formatDateRange(en_US, tz, fixedTime, fixedTime + 3 * YEAR,
                         FORMAT_NO_MONTH_DAY | FORMAT_ABBREV_ALL));
-        assertEquals("January 19, 2009 – February 9, 2012",
+        assertEquals("January 19, 2009\u2009\u2013\u2009February 9, 2012",
                 formatDateRange(en_US, tz, fixedTime, fixedTime + 3 * YEAR, 0));
-        assertEquals("Monday, January 19, 2009 – Thursday, February 9, 2012",
+        assertEquals("Monday, January 19, 2009\u2009\u2013\u2009Thursday, February 9, 2012",
                 formatDateRange(en_US, tz, fixedTime, fixedTime + 3 * YEAR, FORMAT_SHOW_WEEKDAY));
 
         // The same tests but for de_DE.
@@ -225,26 +226,26 @@
         assertEquals("Montag, 19. – Donnerstag, 22. Januar 2009",
                 formatDateRange(de_DE, tz, fixedTime, fixedTime + 3 * DAY, FORMAT_SHOW_WEEKDAY));
 
-        assertEquals("19. Januar – 22. April 2009",
+        assertEquals("19. Januar\u2009\u2013\u200922. April 2009",
                 formatDateRange(de_DE, tz, fixedTime, fixedTime + 3 * MONTH, 0));
-        assertEquals("19. Jan. – 22. Apr. 2009",
+        assertEquals("19. Jan.\u2009\u2013\u200922. Apr. 2009",
                 formatDateRange(de_DE, tz, fixedTime, fixedTime + 3 * MONTH,
                         FORMAT_SHOW_DATE | FORMAT_ABBREV_ALL));
-        assertEquals("Mo., 19. Jan. – Mi., 22. Apr. 2009",
+        assertEquals("Mo., 19. Jan.\u2009\u2013\u2009Mi., 22. Apr. 2009",
                 formatDateRange(de_DE, tz, fixedTime, fixedTime + 3 * MONTH,
                         FORMAT_SHOW_WEEKDAY | FORMAT_ABBREV_ALL));
         assertEquals("Januar–April 2009",
                 formatDateRange(de_DE, tz, fixedTime, fixedTime + 3 * MONTH, FORMAT_NO_MONTH_DAY));
 
-        assertEquals("19. Jan. 2009 – 9. Feb. 2012",
+        assertEquals("19. Jan. 2009\u2009\u2013\u20099. Feb. 2012",
                 formatDateRange(de_DE, tz, fixedTime, fixedTime + 3 * YEAR,
                         FORMAT_SHOW_DATE | FORMAT_ABBREV_ALL));
-        assertEquals("Jan. 2009 – Feb. 2012",
+        assertEquals("Jan. 2009\u2009\u2013\u2009Feb. 2012",
                 formatDateRange(de_DE, tz, fixedTime, fixedTime + 3 * YEAR,
                         FORMAT_NO_MONTH_DAY | FORMAT_ABBREV_ALL));
-        assertEquals("19. Januar 2009 – 9. Februar 2012",
+        assertEquals("19. Januar 2009\u2009\u2013\u20099. Februar 2012",
                 formatDateRange(de_DE, tz, fixedTime, fixedTime + 3 * YEAR, 0));
-        assertEquals("Montag, 19. Januar 2009 – Donnerstag, 9. Februar 2012",
+        assertEquals("Montag, 19. Januar 2009\u2009\u2013\u2009Donnerstag, 9. Februar 2012",
                 formatDateRange(de_DE, tz, fixedTime, fixedTime + 3 * YEAR, FORMAT_SHOW_WEEKDAY));
 
         // The same tests but for es_US.
@@ -254,32 +255,32 @@
         assertEquals("19–22 de ene de 2009",
                 formatDateRange(es_US, tz, fixedTime, fixedTime + 3 * DAY,
                         FORMAT_SHOW_DATE | FORMAT_ABBREV_ALL));
-        assertEquals("lun, 19 de ene – jue, 22 de ene de 2009",
+        assertEquals("lun, 19 de ene\u2009\u2013\u2009jue, 22 de ene de 2009",
                 formatDateRange(es_US, tz, fixedTime, fixedTime + 3 * DAY,
                         FORMAT_SHOW_WEEKDAY | FORMAT_ABBREV_ALL));
-        assertEquals("lunes, 19 de enero – jueves, 22 de enero de 2009",
+        assertEquals("lunes, 19 de enero\u2009\u2013\u2009jueves, 22 de enero de 2009",
                 formatDateRange(es_US, tz, fixedTime, fixedTime + 3 * DAY, FORMAT_SHOW_WEEKDAY));
 
-        assertEquals("19 de enero – 22 de abril de 2009",
+        assertEquals("19 de enero\u2009\u2013\u200922 de abril de 2009",
                 formatDateRange(es_US, tz, fixedTime, fixedTime + 3 * MONTH, 0));
-        assertEquals("19 de ene – 22 de abr 2009",
+        assertEquals("19 de ene\u2009\u2013\u200922 de abr 2009",
                 formatDateRange(es_US, tz, fixedTime, fixedTime + 3 * MONTH,
                         FORMAT_SHOW_DATE | FORMAT_ABBREV_ALL));
-        assertEquals("lun, 19 de ene – mié, 22 de abr de 2009",
+        assertEquals("lun, 19 de ene\u2009\u2013\u2009mié, 22 de abr de 2009",
                 formatDateRange(es_US, tz, fixedTime, fixedTime + 3 * MONTH,
                         FORMAT_SHOW_WEEKDAY | FORMAT_ABBREV_ALL));
         assertEquals("enero–abril de 2009",
                 formatDateRange(es_US, tz, fixedTime, fixedTime + 3 * MONTH, FORMAT_NO_MONTH_DAY));
 
-        assertEquals("19 de ene de 2009 – 9 de feb de 2012",
+        assertEquals("19 de ene de 2009\u2009\u2013\u20099 de feb de 2012",
                 formatDateRange(es_US, tz, fixedTime, fixedTime + 3 * YEAR,
                         FORMAT_SHOW_DATE | FORMAT_ABBREV_ALL));
-        assertEquals("ene de 2009 – feb de 2012",
+        assertEquals("ene de 2009\u2009\u2013\u2009feb de 2012",
                 formatDateRange(es_US, tz, fixedTime, fixedTime + 3 * YEAR,
                         FORMAT_NO_MONTH_DAY | FORMAT_ABBREV_ALL));
-        assertEquals("19 de enero de 2009 – 9 de febrero de 2012",
+        assertEquals("19 de enero de 2009\u2009\u2013\u20099 de febrero de 2012",
                 formatDateRange(es_US, tz, fixedTime, fixedTime + 3 * YEAR, 0));
-        assertEquals("lunes, 19 de enero de 2009 – jueves, 9 de febrero de 2012",
+        assertEquals("lunes, 19 de enero de 2009\u2009\u2013\u2009jueves, 9 de febrero de 2012",
                 formatDateRange(es_US, tz, fixedTime, fixedTime + 3 * YEAR, FORMAT_SHOW_WEEKDAY));
 
         // The same tests but for es_ES.
@@ -288,32 +289,32 @@
                 formatDateRange(es_ES, tz, fixedTime, fixedTime + 3 * DAY, 0));
         assertEquals("19–22 ene 2009", formatDateRange(es_ES, tz, fixedTime, fixedTime + 3 * DAY,
                 FORMAT_SHOW_DATE | FORMAT_ABBREV_ALL));
-        assertEquals("lun, 19 ene – jue, 22 ene 2009",
+        assertEquals("lun, 19 ene\u2009\u2013\u2009jue, 22 ene 2009",
                 formatDateRange(es_ES, tz, fixedTime, fixedTime + 3 * DAY,
                         FORMAT_SHOW_WEEKDAY | FORMAT_ABBREV_ALL));
-        assertEquals("lunes, 19 de enero – jueves, 22 de enero de 2009",
+        assertEquals("lunes, 19 de enero\u2009\u2013\u2009jueves, 22 de enero de 2009",
                 formatDateRange(es_ES, tz, fixedTime, fixedTime + 3 * DAY, FORMAT_SHOW_WEEKDAY));
 
-        assertEquals("19 de enero – 22 de abril de 2009",
+        assertEquals("19 de enero\u2009\u2013\u200922 de abril de 2009",
                 formatDateRange(es_ES, tz, fixedTime, fixedTime + 3 * MONTH, 0));
-        assertEquals("19 ene – 22 abr 2009",
+        assertEquals("19 ene\u2009\u2013\u200922 abr 2009",
                 formatDateRange(es_ES, tz, fixedTime, fixedTime + 3 * MONTH,
                         FORMAT_SHOW_DATE | FORMAT_ABBREV_ALL));
-        assertEquals("lun, 19 ene – mié, 22 abr 2009",
+        assertEquals("lun, 19 ene\u2009\u2013\u2009mié, 22 abr 2009",
                 formatDateRange(es_ES, tz, fixedTime, fixedTime + 3 * MONTH,
                         FORMAT_SHOW_WEEKDAY | FORMAT_ABBREV_ALL));
         assertEquals("enero–abril de 2009",
                 formatDateRange(es_ES, tz, fixedTime, fixedTime + 3 * MONTH, FORMAT_NO_MONTH_DAY));
 
-        assertEquals("19 ene 2009 – 9 feb 2012",
+        assertEquals("19 ene 2009\u2009\u2013\u20099 feb 2012",
                 formatDateRange(es_ES, tz, fixedTime, fixedTime + 3 * YEAR,
                         FORMAT_SHOW_DATE | FORMAT_ABBREV_ALL));
-        assertEquals("ene 2009 – feb 2012",
+        assertEquals("ene 2009\u2009\u2013\u2009feb 2012",
                 formatDateRange(es_ES, tz, fixedTime, fixedTime + 3 * YEAR,
                         FORMAT_NO_MONTH_DAY | FORMAT_ABBREV_ALL));
-        assertEquals("19 de enero de 2009 – 9 de febrero de 2012",
+        assertEquals("19 de enero de 2009\u2009\u2013\u20099 de febrero de 2012",
                 formatDateRange(es_ES, tz, fixedTime, fixedTime + 3 * YEAR, 0));
-        assertEquals("lunes, 19 de enero de 2009 – jueves, 9 de febrero de 2012",
+        assertEquals("lunes, 19 de enero de 2009\u2009\u2013\u2009jueves, 9 de febrero de 2012",
                 formatDateRange(es_ES, tz, fixedTime, fixedTime + 3 * YEAR, FORMAT_SHOW_WEEKDAY));
     }
 
@@ -330,7 +331,7 @@
         c.set(2046, Calendar.OCTOBER, 4, 3, 30);
         long oct_4_2046 = c.getTimeInMillis();
         int flags = FORMAT_SHOW_DATE | FORMAT_ABBREV_ALL;
-        assertEquals("Jan 19, 2042 – Oct 4, 2046",
+        assertEquals("Jan 19, 2042\u2009\u2013\u2009Oct 4, 2046",
                 formatDateRange(l, tz, jan_19_2042, oct_4_2046, flags));
     }
 
@@ -343,15 +344,15 @@
         int flags = FORMAT_SHOW_DATE | FORMAT_ABBREV_ALL | FORMAT_SHOW_TIME | FORMAT_24HOUR;
 
         // The Unix epoch is UTC, so 0 is 1970-01-01T00:00Z...
-        assertEquals("Jan 1, 1970, 00:00 – Jan 2, 1970, 00:00",
+        assertEquals("Jan 1, 1970, 00:00\u2009\u2013\u2009Jan 2, 1970, 00:00",
                 formatDateRange(l, utc, 0, DAY + 1, flags));
         // But MTV is hours behind, so 0 was still the afternoon of the previous day...
-        assertEquals("Dec 31, 1969, 16:00 – Jan 1, 1970, 16:00",
+        assertEquals("Dec 31, 1969, 16:00\u2009\u2013\u2009Jan 1, 1970, 16:00",
                 formatDateRange(l, pacific, 0, DAY, flags));
     }
 
     // http://b/10318326 - we can drop the minutes in a 12-hour time if they're zero,
-    // but not if we're using the 24-hour clock. That is: "4 PM" is reasonable, "16" is not.
+    // but not if we're using the 24-hour clock. That is: "4\u202fPM" is reasonable, "16" is not.
     @Test
     public void test10318326() throws Exception {
         long midnight = 0;
@@ -367,23 +368,26 @@
 
         // Full length on-the-hour times.
         assertEquals("00:00", formatDateRange(l, utc, midnight, midnight, time24));
-        assertEquals("12:00 AM", formatDateRange(l, utc, midnight, midnight, time12));
+        assertEquals("12:00\u202fAM", formatDateRange(l, utc, midnight, midnight, time12));
         assertEquals("16:00", formatDateRange(l, utc, teaTime, teaTime, time24));
-        assertEquals("4:00 PM", formatDateRange(l, utc, teaTime, teaTime, time12));
+        assertEquals("4:00\u202fPM", formatDateRange(l, utc, teaTime, teaTime, time12));
 
         // Abbreviated on-the-hour times.
         assertEquals("00:00", formatDateRange(l, utc, midnight, midnight, abbr24));
-        assertEquals("12 AM", formatDateRange(l, utc, midnight, midnight, abbr12));
+        assertEquals("12\u202fAM", formatDateRange(l, utc, midnight, midnight, abbr12));
         assertEquals("16:00", formatDateRange(l, utc, teaTime, teaTime, abbr24));
-        assertEquals("4 PM", formatDateRange(l, utc, teaTime, teaTime, abbr12));
+        assertEquals("4\u202fPM", formatDateRange(l, utc, teaTime, teaTime, abbr12));
 
         // Abbreviated on-the-hour ranges.
-        assertEquals("00:00 – 16:00", formatDateRange(l, utc, midnight, teaTime, abbr24));
-        assertEquals("12 AM – 4 PM", formatDateRange(l, utc, midnight, teaTime, abbr12));
+        assertEquals("00:00\u2009\u2013\u200916:00", formatDateRange(l, utc, midnight, teaTime,
+                abbr24));
+        assertEquals("12\u202fAM\u2009\u2013\u20094\u202fPM", formatDateRange(l, utc, midnight,
+                teaTime, abbr12));
 
         // Abbreviated mixed ranges.
-        assertEquals("00:00 – 16:01", formatDateRange(l, utc, midnight, teaTime + MINUTE, abbr24));
-        assertEquals("12:00 AM – 4:01 PM",
+        assertEquals("00:00\u2009\u2013\u200916:01", formatDateRange(l, utc, midnight,
+                teaTime + MINUTE, abbr24));
+        assertEquals("12:00\u202fAM\u2009\u2013\u20094:01\u202fPM",
                 formatDateRange(l, utc, midnight, teaTime + MINUTE, abbr12));
     }
 
@@ -406,12 +410,12 @@
 
         // Run one millisecond over, though, and you're into the next day.
         long nextMorning = 1 * DAY + 1;
-        assertEquals("Thursday, January 1 – Friday, January 2, 1970",
+        assertEquals("Thursday, January 1\u2009\u2013\u2009Friday, January 2, 1970",
                 formatDateRange(l, utc, midnight, nextMorning, flags));
 
         // But the same reasoning applies for that day.
         long nextMidnight = 2 * DAY;
-        assertEquals("Thursday, January 1 – Friday, January 2, 1970",
+        assertEquals("Thursday, January 1\u2009\u2013\u2009Friday, January 2, 1970",
                 formatDateRange(l, utc, midnight, nextMidnight, flags));
     }
 
@@ -424,9 +428,9 @@
 
         int flags = FORMAT_SHOW_TIME | FORMAT_24HOUR | FORMAT_SHOW_DATE;
 
-        assertEquals("January 1, 1970, 22:00 – 00:00",
+        assertEquals("January 1, 1970, 22:00\u2009\u2013\u200900:00",
                 formatDateRange(l, utc, 22 * HOUR, 24 * HOUR, flags));
-        assertEquals("January 1, 1970 at 22:00 – January 2, 1970 at 00:30",
+        assertEquals("January 1, 1970 at 22:00\u2009\u2013\u2009January 2, 1970 at 00:30",
                 formatDateRange(l, utc, 22 * HOUR, 24 * HOUR + 30 * MINUTE, flags));
     }
 
@@ -443,9 +447,9 @@
         c.clear();
         c.set(1980, Calendar.JANUARY, 1, 0, 0);
         long jan_1_1980 = c.getTimeInMillis();
-        assertEquals("January 1, 1980, 22:00 – 00:00",
+        assertEquals("January 1, 1980, 22:00\u2009\u2013\u200900:00",
                 formatDateRange(l, utc, jan_1_1980 + 22 * HOUR, jan_1_1980 + 24 * HOUR, flags));
-        assertEquals("January 1, 1980 at 22:00 – January 2, 1980 at 00:30",
+        assertEquals("January 1, 1980 at 22:00\u2009\u2013\u2009January 2, 1980 at 00:30",
                 formatDateRange(l, utc, jan_1_1980 + 22 * HOUR,
                         jan_1_1980 + 24 * HOUR + 30 * MINUTE, flags));
     }
@@ -463,12 +467,12 @@
         c.clear();
         c.set(1980, Calendar.JANUARY, 1, 0, 0);
         long jan_1_1980 = c.getTimeInMillis();
-        assertEquals("January 1, 1980, 22:00 – 00:00",
+        assertEquals("January 1, 1980, 22:00\u2009\u2013\u200900:00",
                 formatDateRange(l, pacific, jan_1_1980 + 22 * HOUR, jan_1_1980 + 24 * HOUR, flags));
 
         c.set(1980, Calendar.JULY, 1, 0, 0);
         long jul_1_1980 = c.getTimeInMillis();
-        assertEquals("July 1, 1980, 22:00 – 00:00",
+        assertEquals("July 1, 1980, 22:00\u2009\u2013\u200900:00",
                 formatDateRange(l, pacific, jul_1_1980 + 22 * HOUR, jul_1_1980 + 24 * HOUR, flags));
     }
 
@@ -531,11 +535,13 @@
                 formatDateRange(l, utc, oldYear, oldYear, FORMAT_SHOW_DATE | FORMAT_NO_YEAR));
 
         // ...or the start and end years aren't the same...
-        assertEquals(String.format("February 10, 1980 – February 10, %d", c.get(Calendar.YEAR)),
+        assertEquals(String.format("February 10, 1980\u2009\u2013\u2009February 10, %d",
+                        c.get(Calendar.YEAR)),
                 formatDateRange(l, utc, oldYear, thisYear, FORMAT_SHOW_DATE));
 
         // (And you can't avoid that --- icu4c steps in and overrides you.)
-        assertEquals(String.format("February 10, 1980 – February 10, %d", c.get(Calendar.YEAR)),
+        assertEquals(String.format("February 10, 1980\u2009\u2013\u2009February 10, %d",
+                        c.get(Calendar.YEAR)),
                 formatDateRange(l, utc, oldYear, thisYear, FORMAT_SHOW_DATE | FORMAT_NO_YEAR));
     }
 
@@ -595,7 +601,7 @@
                 formatDateRange(new ULocale("fa"), utc, thisYear, thisYear, flags));
         assertEquals("يونۍ د ۱۹۸۰ د فبروري ۱۰",
                 formatDateRange(new ULocale("ps"), utc, thisYear, thisYear, flags));
-        assertEquals("วันอาทิตย์ที่ 10 กุมภาพันธ์ ค.ศ. 1980",
+        assertEquals("วันอาทิตย์ที่ 10 กุมภาพันธ์ 1980",
                 formatDateRange(new ULocale("th"), utc, thisYear, thisYear, flags));
     }
 
@@ -607,9 +613,12 @@
 
         int flags = FORMAT_SHOW_TIME | FORMAT_ABBREV_ALL | FORMAT_12HOUR;
 
-        assertEquals("10 – 11 AM", formatDateRange(l, utc, 10 * HOUR, 11 * HOUR, flags));
-        assertEquals("11 AM – 1 PM", formatDateRange(l, utc, 11 * HOUR, 13 * HOUR, flags));
-        assertEquals("2 – 3 PM", formatDateRange(l, utc, 14 * HOUR, 15 * HOUR, flags));
+        assertEquals("10\u2009\u2013\u200911\u202fAM", formatDateRange(l, utc,
+                10 * HOUR, 11 * HOUR, flags));
+        assertEquals("11\u202fAM\u2009\u2013\u20091\u202fPM", formatDateRange(l, utc,
+                11 * HOUR, 13 * HOUR, flags));
+        assertEquals("2\u2009\u2013\u20093\u202fPM", formatDateRange(l, utc,
+                14 * HOUR, 15 * HOUR, flags));
     }
 
     // http://b/20708022
@@ -618,8 +627,8 @@
         final ULocale locale = new ULocale("en");
         final TimeZone timeZone = TimeZone.getTimeZone("UTC");
 
-        assertEquals("11:00 PM – 12:00 AM", formatDateRange(locale, timeZone,
-                1430434800000L, 1430438400000L, FORMAT_SHOW_TIME));
+        assertEquals("11:00\u202fPM\u2009\u2013\u200912:00\u202fAM", formatDateRange(locale,
+                timeZone, 1430434800000L, 1430438400000L, FORMAT_SHOW_TIME));
     }
 
     // http://b/68847519
@@ -629,23 +638,25 @@
                 ENGLISH, GMT_ZONE, from, to, FORMAT_SHOW_DATE | FORMAT_SHOW_TIME | FORMAT_24HOUR);
         // If we're showing times and the end-point is midnight the following day, we want the
         // behaviour of suppressing the date for the end...
-        assertEquals("February 27, 2007, 04:00 – 00:00", fmt.apply(1172548800000L, 1172620800000L));
+        assertEquals("February 27, 2007, 04:00\u2009\u2013\u200900:00", fmt.apply(1172548800000L,
+                1172620800000L));
         // ...unless the start-point is also midnight, in which case we need dates to disambiguate.
-        assertEquals("February 27, 2007 at 00:00 – February 28, 2007 at 00:00",
+        assertEquals("February 27, 2007 at 00:00\u2009\u2013\u2009February 28, 2007 at 00:00",
                 fmt.apply(1172534400000L, 1172620800000L));
         // We want to show the date if the end-point is a millisecond after midnight the following
         // day, or if it is exactly midnight the day after that.
-        assertEquals("February 27, 2007 at 04:00 – February 28, 2007 at 00:00",
+        assertEquals("February 27, 2007 at 04:00\u2009\u2013\u2009February 28, 2007 at 00:00",
                 fmt.apply(1172548800000L, 1172620800001L));
-        assertEquals("February 27, 2007 at 04:00 – March 1, 2007 at 00:00",
+        assertEquals("February 27, 2007 at 04:00\u2009\u2013\u2009March 1, 2007 at 00:00",
                 fmt.apply(1172548800000L, 1172707200000L));
         // We want to show the date if the start-point is anything less than a minute after
       // midnight,
         // since that gets displayed as midnight...
-        assertEquals("February 27, 2007 at 00:00 – February 28, 2007 at 00:00",
+        assertEquals("February 27, 2007 at 00:00\u2009\u2013\u2009February 28, 2007 at 00:00",
                 fmt.apply(1172534459999L, 1172620800000L));
         // ...but not if it is exactly one minute after midnight.
-        assertEquals("February 27, 2007, 00:01 – 00:00", fmt.apply(1172534460000L, 1172620800000L));
+        assertEquals("February 27, 2007, 00:01\u2009\u2013\u200900:00", fmt.apply(1172534460000L,
+                1172620800000L));
     }
 
     // http://b/68847519
@@ -656,16 +667,20 @@
         // If we're only showing dates and the end-point is midnight of any day, we want the
         // behaviour of showing an end date one earlier. So if the end-point is March 2, 2007 00:00,
         // show March 1, 2007 instead (whether the start-point is midnight or not).
-        assertEquals("February 27 – March 1, 2007", fmt.apply(1172534400000L, 1172793600000L));
-        assertEquals("February 27 – March 1, 2007", fmt.apply(1172548800000L, 1172793600000L));
+        assertEquals("February 27\u2009\u2013\u2009March 1, 2007",
+                fmt.apply(1172534400000L, 1172793600000L));
+        assertEquals("February 27\u2009\u2013\u2009March 1, 2007",
+                fmt.apply(1172548800000L, 1172793600000L));
         // We want to show the true date if the end-point is a millisecond after midnight.
-        assertEquals("February 27 – March 2, 2007", fmt.apply(1172534400000L, 1172793600001L));
+        assertEquals("February 27\u2009\u2013\u2009March 2, 2007",
+                fmt.apply(1172534400000L, 1172793600001L));
 
         // 2006-02-27 00:00:00.000 GMT - 2007-03-02 00:00:00.000 GMT
-        assertEquals("February 27, 2006 – March 1, 2007",
+        assertEquals("February 27, 2006\u2009\u2013\u2009March 1, 2007",
                 fmt.apply(1140998400000L, 1172793600000L));
 
         // Spans a leap year's Feb 29th.
-        assertEquals("February 27 – March 1, 2004", fmt.apply(1077840000000L, 1078185600000L));
+        assertEquals("February 27\u2009\u2013\u2009March 1, 2004",
+                fmt.apply(1077840000000L, 1078185600000L));
     }
 }
diff --git a/core/tests/coretests/src/android/text/format/DateUtilsTest.java b/core/tests/coretests/src/android/text/format/DateUtilsTest.java
index 381c051..39ed82ef 100644
--- a/core/tests/coretests/src/android/text/format/DateUtilsTest.java
+++ b/core/tests/coretests/src/android/text/format/DateUtilsTest.java
@@ -139,16 +139,16 @@
                 fixedTime, java.text.DateFormat.SHORT, java.text.DateFormat.FULL));
 
         final long hourDuration = 2 * 60 * 60 * 1000;
-        assertEquals("5:30:15 AM Greenwich Mean Time", DateUtils.formatSameDayTime(
+        assertEquals("5:30:15\u202fAM Greenwich Mean Time", DateUtils.formatSameDayTime(
                 fixedTime + hourDuration, fixedTime, java.text.DateFormat.FULL,
                 java.text.DateFormat.FULL));
-        assertEquals("5:30:15 AM", DateUtils.formatSameDayTime(fixedTime + hourDuration,
+        assertEquals("5:30:15\u202fAM", DateUtils.formatSameDayTime(fixedTime + hourDuration,
                 fixedTime, java.text.DateFormat.FULL, java.text.DateFormat.DEFAULT));
-        assertEquals("5:30:15 AM GMT", DateUtils.formatSameDayTime(fixedTime + hourDuration,
+        assertEquals("5:30:15\u202fAM GMT", DateUtils.formatSameDayTime(fixedTime + hourDuration,
                 fixedTime, java.text.DateFormat.FULL, java.text.DateFormat.LONG));
-        assertEquals("5:30:15 AM", DateUtils.formatSameDayTime(fixedTime + hourDuration,
+        assertEquals("5:30:15\u202fAM", DateUtils.formatSameDayTime(fixedTime + hourDuration,
                 fixedTime, java.text.DateFormat.FULL, java.text.DateFormat.MEDIUM));
-        assertEquals("5:30 AM", DateUtils.formatSameDayTime(fixedTime + hourDuration,
+        assertEquals("5:30\u202fAM", DateUtils.formatSameDayTime(fixedTime + hourDuration,
                 fixedTime, java.text.DateFormat.FULL, java.text.DateFormat.SHORT));
     }
 
diff --git a/core/tests/coretests/src/android/text/format/RelativeDateTimeFormatterTest.java b/core/tests/coretests/src/android/text/format/RelativeDateTimeFormatterTest.java
index b342516..2337802 100644
--- a/core/tests/coretests/src/android/text/format/RelativeDateTimeFormatterTest.java
+++ b/core/tests/coretests/src/android/text/format/RelativeDateTimeFormatterTest.java
@@ -468,37 +468,37 @@
         cal.set(2015, Calendar.FEBRUARY, 5, 10, 50, 0);
         final long base = cal.getTimeInMillis();
 
-        assertEquals("5 seconds ago, 10:49 AM",
+        assertEquals("5 seconds ago, 10:49\u202fAM",
                 getRelativeDateTimeString(en_US, tz, base - 5 * SECOND_IN_MILLIS, base, 0,
                         MINUTE_IN_MILLIS, 0));
-        assertEquals("5 min. ago, 10:45 AM",
+        assertEquals("5 min. ago, 10:45\u202fAM",
                 getRelativeDateTimeString(en_US, tz, base - 5 * MINUTE_IN_MILLIS, base, 0,
                         HOUR_IN_MILLIS, FORMAT_ABBREV_RELATIVE));
-        assertEquals("0 hr. ago, 10:45 AM",
+        assertEquals("0 hr. ago, 10:45\u202fAM",
                 getRelativeDateTimeString(en_US, tz, base - 5 * MINUTE_IN_MILLIS, base,
                         HOUR_IN_MILLIS, DAY_IN_MILLIS, FORMAT_ABBREV_RELATIVE));
-        assertEquals("5 hours ago, 5:50 AM",
+        assertEquals("5 hours ago, 5:50\u202fAM",
                 getRelativeDateTimeString(en_US, tz, base - 5 * HOUR_IN_MILLIS, base,
                         HOUR_IN_MILLIS, DAY_IN_MILLIS, 0));
-        assertEquals("Yesterday, 7:50 PM",
+        assertEquals("Yesterday, 7:50\u202fPM",
                 getRelativeDateTimeString(en_US, tz, base - 15 * HOUR_IN_MILLIS, base, 0,
                         WEEK_IN_MILLIS, FORMAT_ABBREV_RELATIVE));
-        assertEquals("5 days ago, 10:50 AM",
+        assertEquals("5 days ago, 10:50\u202fAM",
                 getRelativeDateTimeString(en_US, tz, base - 5 * DAY_IN_MILLIS, base, 0,
                         WEEK_IN_MILLIS, 0));
-        assertEquals("Jan 29, 10:50 AM",
+        assertEquals("Jan 29, 10:50\u202fAM",
                 getRelativeDateTimeString(en_US, tz, base - 7 * DAY_IN_MILLIS, base, 0,
                         WEEK_IN_MILLIS, 0));
-        assertEquals("11/27/2014, 10:50 AM",
+        assertEquals("11/27/2014, 10:50\u202fAM",
                 getRelativeDateTimeString(en_US, tz, base - 10 * WEEK_IN_MILLIS, base, 0,
                         WEEK_IN_MILLIS, 0));
-        assertEquals("11/27/2014, 10:50 AM",
+        assertEquals("11/27/2014, 10:50\u202fAM",
                 getRelativeDateTimeString(en_US, tz, base - 10 * WEEK_IN_MILLIS, base, 0,
                         YEAR_IN_MILLIS, 0));
 
         // User-supplied flags should be ignored when formatting the date clause.
         final int FORMAT_SHOW_WEEKDAY = 0x00002;
-        assertEquals("11/27/2014, 10:50 AM",
+        assertEquals("11/27/2014, 10:50\u202fAM",
                 getRelativeDateTimeString(en_US, tz, base - 10 * WEEK_IN_MILLIS, base, 0,
                         WEEK_IN_MILLIS,
                         FORMAT_ABBREV_ALL | FORMAT_SHOW_WEEKDAY));
@@ -514,14 +514,14 @@
         // So 5 hours before 3:15 AM should be formatted as 'Yesterday, 9:15 PM'.
         cal.set(2014, Calendar.MARCH, 9, 3, 15, 0);
         long base = cal.getTimeInMillis();
-        assertEquals("Yesterday, 9:15 PM",
+        assertEquals("Yesterday, 9:15\u202fPM",
                 getRelativeDateTimeString(en_US, tz, base - 5 * HOUR_IN_MILLIS, base, 0,
                         WEEK_IN_MILLIS, 0));
 
         // 1 hour after 2:00 AM should be formatted as 'In 1 hour, 4:00 AM'.
         cal.set(2014, Calendar.MARCH, 9, 2, 0, 0);
         base = cal.getTimeInMillis();
-        assertEquals("In 1 hour, 4:00 AM",
+        assertEquals("In 1 hour, 4:00\u202fAM",
                 getRelativeDateTimeString(en_US, tz, base + 1 * HOUR_IN_MILLIS, base, 0,
                         WEEK_IN_MILLIS, 0));
 
@@ -529,22 +529,22 @@
         // 1:00 AM. 8 hours before 5:20 AM should be 'Yesterday, 10:20 PM'.
         cal.set(2014, Calendar.NOVEMBER, 2, 5, 20, 0);
         base = cal.getTimeInMillis();
-        assertEquals("Yesterday, 10:20 PM",
+        assertEquals("Yesterday, 10:20\u202fPM",
                 getRelativeDateTimeString(en_US, tz, base - 8 * HOUR_IN_MILLIS, base, 0,
                         WEEK_IN_MILLIS, 0));
 
         cal.set(2014, Calendar.NOVEMBER, 2, 0, 45, 0);
         base = cal.getTimeInMillis();
         // 45 minutes after 0:45 AM should be 'In 45 minutes, 1:30 AM'.
-        assertEquals("In 45 minutes, 1:30 AM",
+        assertEquals("In 45 minutes, 1:30\u202fAM",
                 getRelativeDateTimeString(en_US, tz, base + 45 * MINUTE_IN_MILLIS, base, 0,
                         WEEK_IN_MILLIS, 0));
         // 45 minutes later, it should be 'In 45 minutes, 1:15 AM'.
-        assertEquals("In 45 minutes, 1:15 AM",
+        assertEquals("In 45 minutes, 1:15\u202fAM",
                 getRelativeDateTimeString(en_US, tz, base + 90 * MINUTE_IN_MILLIS,
                         base + 45 * MINUTE_IN_MILLIS, 0, WEEK_IN_MILLIS, 0));
         // Another 45 minutes later, it should be 'In 45 minutes, 2:00 AM'.
-        assertEquals("In 45 minutes, 2:00 AM",
+        assertEquals("In 45 minutes, 2:00\u202fAM",
                 getRelativeDateTimeString(en_US, tz, base + 135 * MINUTE_IN_MILLIS,
                         base + 90 * MINUTE_IN_MILLIS, 0, WEEK_IN_MILLIS, 0));
     }
@@ -593,7 +593,7 @@
         Calendar yesterdayCalendar1 = Calendar.getInstance(tz, en_US);
         yesterdayCalendar1.set(2011, Calendar.SEPTEMBER, 1, 10, 24, 0);
         long yesterday1 = yesterdayCalendar1.getTimeInMillis();
-        assertEquals("Yesterday, 10:24 AM",
+        assertEquals("Yesterday, 10:24\u202fAM",
                 getRelativeDateTimeString(en_US, tz, yesterday1, now, MINUTE_IN_MILLIS,
                         WEEK_IN_MILLIS, 0));
 
@@ -601,7 +601,7 @@
         Calendar yesterdayCalendar2 = Calendar.getInstance(tz, en_US);
         yesterdayCalendar2.set(2011, Calendar.SEPTEMBER, 1, 10, 22, 0);
         long yesterday2 = yesterdayCalendar2.getTimeInMillis();
-        assertEquals("Yesterday, 10:22 AM",
+        assertEquals("Yesterday, 10:22\u202fAM",
                 getRelativeDateTimeString(en_US, tz, yesterday2, now, MINUTE_IN_MILLIS,
                         WEEK_IN_MILLIS, 0));
 
@@ -609,7 +609,7 @@
         Calendar twoDaysAgoCalendar1 = Calendar.getInstance(tz, en_US);
         twoDaysAgoCalendar1.set(2011, Calendar.AUGUST, 31, 10, 24, 0);
         long twoDaysAgo1 = twoDaysAgoCalendar1.getTimeInMillis();
-        assertEquals("2 days ago, 10:24 AM",
+        assertEquals("2 days ago, 10:24\u202fAM",
                 getRelativeDateTimeString(en_US, tz, twoDaysAgo1, now, MINUTE_IN_MILLIS,
                         WEEK_IN_MILLIS, 0));
 
@@ -617,7 +617,7 @@
         Calendar twoDaysAgoCalendar2 = Calendar.getInstance(tz, en_US);
         twoDaysAgoCalendar2.set(2011, Calendar.AUGUST, 31, 10, 22, 0);
         long twoDaysAgo2 = twoDaysAgoCalendar2.getTimeInMillis();
-        assertEquals("2 days ago, 10:22 AM",
+        assertEquals("2 days ago, 10:22\u202fAM",
                 getRelativeDateTimeString(en_US, tz, twoDaysAgo2, now, MINUTE_IN_MILLIS,
                         WEEK_IN_MILLIS, 0));
 
@@ -625,7 +625,7 @@
         Calendar tomorrowCalendar1 = Calendar.getInstance(tz, en_US);
         tomorrowCalendar1.set(2011, Calendar.SEPTEMBER, 3, 10, 22, 0);
         long tomorrow1 = tomorrowCalendar1.getTimeInMillis();
-        assertEquals("Tomorrow, 10:22 AM",
+        assertEquals("Tomorrow, 10:22\u202fAM",
                 getRelativeDateTimeString(en_US, tz, tomorrow1, now, MINUTE_IN_MILLIS,
                         WEEK_IN_MILLIS, 0));
 
@@ -633,7 +633,7 @@
         Calendar tomorrowCalendar2 = Calendar.getInstance(tz, en_US);
         tomorrowCalendar2.set(2011, Calendar.SEPTEMBER, 3, 10, 24, 0);
         long tomorrow2 = tomorrowCalendar2.getTimeInMillis();
-        assertEquals("Tomorrow, 10:24 AM",
+        assertEquals("Tomorrow, 10:24\u202fAM",
                 getRelativeDateTimeString(en_US, tz, tomorrow2, now, MINUTE_IN_MILLIS,
                         WEEK_IN_MILLIS, 0));
 
@@ -641,7 +641,7 @@
         Calendar twoDaysLaterCalendar1 = Calendar.getInstance(tz, en_US);
         twoDaysLaterCalendar1.set(2011, Calendar.SEPTEMBER, 4, 10, 22, 0);
         long twoDaysLater1 = twoDaysLaterCalendar1.getTimeInMillis();
-        assertEquals("In 2 days, 10:22 AM",
+        assertEquals("In 2 days, 10:22\u202fAM",
                 getRelativeDateTimeString(en_US, tz, twoDaysLater1, now, MINUTE_IN_MILLIS,
                         WEEK_IN_MILLIS, 0));
 
@@ -649,7 +649,7 @@
         Calendar twoDaysLaterCalendar2 = Calendar.getInstance(tz, en_US);
         twoDaysLaterCalendar2.set(2011, Calendar.SEPTEMBER, 4, 10, 24, 0);
         long twoDaysLater2 = twoDaysLaterCalendar2.getTimeInMillis();
-        assertEquals("In 2 days, 10:24 AM",
+        assertEquals("In 2 days, 10:24\u202fAM",
                 getRelativeDateTimeString(en_US, tz, twoDaysLater2, now, MINUTE_IN_MILLIS,
                         WEEK_IN_MILLIS, 0));
     }
@@ -664,11 +664,11 @@
         cal.set(2012, Calendar.FEBRUARY, 5, 10, 50, 0);
         long base = cal.getTimeInMillis();
 
-        assertEquals("Feb 5, 5:50 AM", getRelativeDateTimeString(en_US, tz,
+        assertEquals("Feb 5, 5:50\u202fAM", getRelativeDateTimeString(en_US, tz,
                 base - 5 * HOUR_IN_MILLIS, base, 0, MINUTE_IN_MILLIS, 0));
-        assertEquals("Jan 29, 10:50 AM", getRelativeDateTimeString(en_US, tz,
+        assertEquals("Jan 29, 10:50\u202fAM", getRelativeDateTimeString(en_US, tz,
                 base - 7 * DAY_IN_MILLIS, base, 0, WEEK_IN_MILLIS, 0));
-        assertEquals("11/27/2011, 10:50 AM", getRelativeDateTimeString(en_US, tz,
+        assertEquals("11/27/2011, 10:50\u202fAM", getRelativeDateTimeString(en_US, tz,
                 base - 10 * WEEK_IN_MILLIS, base, 0, WEEK_IN_MILLIS, 0));
 
         assertEquals("January 6", getRelativeTimeSpanString(en_US, tz,
@@ -687,11 +687,11 @@
         // Feb 5, 2018 at 10:50 PST
         cal.set(2018, Calendar.FEBRUARY, 5, 10, 50, 0);
         base = cal.getTimeInMillis();
-        assertEquals("Feb 5, 5:50 AM", getRelativeDateTimeString(en_US, tz,
+        assertEquals("Feb 5, 5:50\u202fAM", getRelativeDateTimeString(en_US, tz,
                 base - 5 * HOUR_IN_MILLIS, base, 0, MINUTE_IN_MILLIS, 0));
-        assertEquals("Jan 29, 10:50 AM", getRelativeDateTimeString(en_US, tz,
+        assertEquals("Jan 29, 10:50\u202fAM", getRelativeDateTimeString(en_US, tz,
                 base - 7 * DAY_IN_MILLIS, base, 0, WEEK_IN_MILLIS, 0));
-        assertEquals("11/27/2017, 10:50 AM", getRelativeDateTimeString(en_US, tz,
+        assertEquals("11/27/2017, 10:50\u202fAM", getRelativeDateTimeString(en_US, tz,
                 base - 10 * WEEK_IN_MILLIS, base, 0, WEEK_IN_MILLIS, 0));
 
         assertEquals("January 6", getRelativeTimeSpanString(en_US, tz,
diff --git a/core/tests/coretests/src/android/widget/RemoteViewsTest.java b/core/tests/coretests/src/android/widget/RemoteViewsTest.java
index 00b3693..bbf9f3c 100644
--- a/core/tests/coretests/src/android/widget/RemoteViewsTest.java
+++ b/core/tests/coretests/src/android/widget/RemoteViewsTest.java
@@ -128,6 +128,7 @@
         RemoteViews clone = child.clone();
     }
 
+    @SuppressWarnings("ReturnValueIgnored")
     @Test
     public void clone_repeatedly() {
         RemoteViews original = new RemoteViews(mPackage, R.layout.remote_views_test);
@@ -485,6 +486,7 @@
         }
     }
 
+    @SuppressWarnings("ReturnValueIgnored")
     @Test
     public void nestedAddViews() {
         RemoteViews views = new RemoteViews(mPackage, R.layout.remote_views_test);
@@ -509,6 +511,7 @@
         parcelAndRecreate(views);
     }
 
+    @SuppressWarnings("ReturnValueIgnored")
     @Test
     public void nestedLandscapeViews() {
         RemoteViews views = new RemoteViews(mPackage, R.layout.remote_views_test);
diff --git a/core/tests/coretests/src/com/android/internal/os/BinderCallsStatsTest.java b/core/tests/coretests/src/com/android/internal/os/BinderCallsStatsTest.java
index 82b2bf4..8207c9e 100644
--- a/core/tests/coretests/src/com/android/internal/os/BinderCallsStatsTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/BinderCallsStatsTest.java
@@ -1054,10 +1054,23 @@
             super(new Injector() {
                 public Random getRandomGenerator() {
                     return new Random() {
-                        int mCallCount = 0;
+                        int mCallCount = -1;
 
                         public int nextInt() {
-                            return mCallCount++;
+                            throw new IllegalStateException("Should not use nextInt()");
+                        }
+
+                        public int nextInt(int x) {
+                            if (mCallCount == -1) {
+                                // The tests are written such that they expect
+                                // the first call to nextInt() to be on the first
+                                // callEnded(). However, the BinderCallsStats
+                                // constructor also calls nextInt(). Fake 0 being
+                                // rolled twice.
+                                mCallCount++;
+                                return 0;
+                            }
+                            return (mCallCount++) % x;
                         }
                     };
                 }
diff --git a/core/tests/coretests/src/com/android/internal/os/BinderLatencyObserverTest.java b/core/tests/coretests/src/com/android/internal/os/BinderLatencyObserverTest.java
index 5af7376..7bd53b9 100644
--- a/core/tests/coretests/src/com/android/internal/os/BinderLatencyObserverTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/BinderLatencyObserverTest.java
@@ -98,7 +98,7 @@
         assertEquals(1, latencyHistograms.size());
         LatencyDims dims = latencyHistograms.keySet().iterator().next();
         assertEquals(binder.getClass(), dims.getBinderClass());
-        assertEquals(1, dims.getTransactionCode());
+        assertEquals(2, dims.getTransactionCode()); // the first nextInt() is in the constructor
         assertThat(latencyHistograms.get(dims)).asList().containsExactly(1, 0, 0, 0, 0).inOrder();
     }
 
@@ -313,11 +313,11 @@
                                 int mCallCount = 0;
 
                                 public int nextInt() {
-                                    return mCallCount++;
+                                    throw new IllegalStateException("Should not use nextInt()");
                                 }
 
                                 public int nextInt(int x) {
-                                    return 1;
+                                    return (mCallCount++) % x;
                                 }
                             };
                         }
diff --git a/core/tests/coretests/src/com/android/internal/os/ProcLocksReaderTest.java b/core/tests/coretests/src/com/android/internal/os/ProcLocksReaderTest.java
index b34554c..c3d40eb 100644
--- a/core/tests/coretests/src/com/android/internal/os/ProcLocksReaderTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/ProcLocksReaderTest.java
@@ -20,6 +20,7 @@
 
 import android.content.Context;
 import android.os.FileUtils;
+import android.util.IntArray;
 
 import androidx.test.InstrumentationRegistry;
 import androidx.test.filters.SmallTest;
@@ -33,13 +34,15 @@
 import java.io.File;
 import java.nio.file.Files;
 import java.util.ArrayList;
+import java.util.Arrays;
 
 @SmallTest
 @RunWith(AndroidJUnit4.class)
 public class ProcLocksReaderTest implements
         ProcLocksReader.ProcLocksReaderCallback {
     private File mProcDirectory;
-    private ArrayList<Integer> mPids = new ArrayList<>();
+
+    private ArrayList<int[]> mPids = new ArrayList<>();
 
     @Before
     public void setUp() {
@@ -54,41 +57,51 @@
 
     @Test
     public void testRunSimpleLocks() throws Exception {
-        String simpleLocks =
-                "1: POSIX  ADVISORY  READ  18403 fd:09:9070 1073741826 1073742335\n" +
-                "2: POSIX  ADVISORY  WRITE 18292 fd:09:34062 0 EOF\n";
+        String simpleLocks = "1: POSIX  ADVISORY  READ  18403 fd:09:9070 1073741826 1073742335\n"
+                           + "2: POSIX  ADVISORY  WRITE 18292 fd:09:34062 0 EOF\n";
         runHandleBlockingFileLocks(simpleLocks);
         assertTrue(mPids.isEmpty());
     }
 
     @Test
     public void testRunBlockingLocks() throws Exception {
-        String blockedLocks =
-                "1: POSIX  ADVISORY  READ  18403 fd:09:9070 1073741826 1073742335\n" +
-                "2: POSIX  ADVISORY  WRITE 18292 fd:09:34062 0 EOF\n" +
-                "2: -> POSIX  ADVISORY  WRITE 18291 fd:09:34062 0 EOF\n" +
-                "2: -> POSIX  ADVISORY  WRITE 18293 fd:09:34062 0 EOF\n" +
-                "3: POSIX  ADVISORY  READ  3888 fd:09:13992 128 128\n" +
-                "4: POSIX  ADVISORY  READ  3888 fd:09:14230 1073741826 1073742335\n";
+        String blockedLocks = "1: POSIX  ADVISORY  READ  18403 fd:09:9070 1073741826 1073742335\n"
+                            + "2: POSIX  ADVISORY  WRITE 18292 fd:09:34062 0 EOF\n"
+                            + "2: -> POSIX  ADVISORY  WRITE 18291 fd:09:34062 0 EOF\n"
+                            + "2: -> POSIX  ADVISORY  WRITE 18293 fd:09:34062 0 EOF\n"
+                            + "3: POSIX  ADVISORY  READ  3888 fd:09:13992 128 128\n"
+                            + "4: POSIX  ADVISORY  READ  3888 fd:09:14230 1073741826 1073742335\n";
         runHandleBlockingFileLocks(blockedLocks);
-        assertTrue(mPids.remove(0).equals(18292));
+        assertTrue(Arrays.equals(mPids.remove(0), new int[]{18292, 18291, 18293}));
+        assertTrue(mPids.isEmpty());
+    }
+
+    @Test
+    public void testRunLastBlockingLocks() throws Exception {
+        String blockedLocks = "1: POSIX  ADVISORY  READ  18403 fd:09:9070 1073741826 1073742335\n"
+                            + "2: POSIX  ADVISORY  WRITE 18292 fd:09:34062 0 EOF\n"
+                            + "2: -> POSIX  ADVISORY  WRITE 18291 fd:09:34062 0 EOF\n"
+                            + "2: -> POSIX  ADVISORY  WRITE 18293 fd:09:34062 0 EOF\n";
+        runHandleBlockingFileLocks(blockedLocks);
+        assertTrue(Arrays.equals(mPids.remove(0), new int[]{18292, 18291, 18293}));
         assertTrue(mPids.isEmpty());
     }
 
     @Test
     public void testRunMultipleBlockingLocks() throws Exception {
-        String blockedLocks =
-                "1: POSIX  ADVISORY  READ  18403 fd:09:9070 1073741826 1073742335\n" +
-                "2: POSIX  ADVISORY  WRITE 18292 fd:09:34062 0 EOF\n" +
-                "2: -> POSIX  ADVISORY  WRITE 18291 fd:09:34062 0 EOF\n" +
-                "2: -> POSIX  ADVISORY  WRITE 18293 fd:09:34062 0 EOF\n" +
-                "3: POSIX  ADVISORY  READ  3888 fd:09:13992 128 128\n" +
-                "4: FLOCK  ADVISORY  WRITE 3840 fe:01:5111809 0 EOF\n" +
-                "4: -> FLOCK  ADVISORY  WRITE 3841 fe:01:5111809 0 EOF\n" +
-                "5: POSIX  ADVISORY  READ  3888 fd:09:14230 1073741826 1073742335\n";
+        String blockedLocks = "1: POSIX  ADVISORY  READ  18403 fd:09:9070 1073741826 1073742335\n"
+                            + "2: POSIX  ADVISORY  WRITE 18292 fd:09:34062 0 EOF\n"
+                            + "2: -> POSIX  ADVISORY  WRITE 18291 fd:09:34062 0 EOF\n"
+                            + "2: -> POSIX  ADVISORY  WRITE 18293 fd:09:34062 0 EOF\n"
+                            + "3: POSIX  ADVISORY  READ  3888 fd:09:13992 128 128\n"
+                            + "4: FLOCK  ADVISORY  WRITE 3840 fe:01:5111809 0 EOF\n"
+                            + "4: -> FLOCK  ADVISORY  WRITE 3841 fe:01:5111809 0 EOF\n"
+                            + "5: FLOCK  ADVISORY  READ  3888 fd:09:14230 0 EOF\n"
+                            + "5: -> FLOCK  ADVISORY  READ  3887 fd:09:14230 0 EOF\n";
         runHandleBlockingFileLocks(blockedLocks);
-        assertTrue(mPids.remove(0).equals(18292));
-        assertTrue(mPids.remove(0).equals(3840));
+        assertTrue(Arrays.equals(mPids.remove(0), new int[]{18292, 18291, 18293}));
+        assertTrue(Arrays.equals(mPids.remove(0), new int[]{3840, 3841}));
+        assertTrue(Arrays.equals(mPids.remove(0), new int[]{3888, 3887}));
         assertTrue(mPids.isEmpty());
     }
 
@@ -102,11 +115,12 @@
 
     /**
      * Call the callback function of handleBlockingFileLocks().
-     *
-     * @param pid Each process that hold file locks blocking other processes.
+     * @param pids Each process that hold file locks blocking other processes.
+     *             pids[0] is the process blocking others
+     *             pids[1..n-1] are the processes being blocked
      */
     @Override
-    public void onBlockingFileLock(int pid) {
-        mPids.add(pid);
+    public void onBlockingFileLock(IntArray pids) {
+        mPids.add(pids.toArray());
     }
 }
diff --git a/core/tests/coretests/src/com/android/internal/os/SafeZipPathValidatorCallbackTest.java b/core/tests/coretests/src/com/android/internal/os/SafeZipPathValidatorCallbackTest.java
new file mode 100644
index 0000000..c540a15
--- /dev/null
+++ b/core/tests/coretests/src/com/android/internal/os/SafeZipPathValidatorCallbackTest.java
@@ -0,0 +1,244 @@
+/*
+ * 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.internal.os;
+
+import static org.junit.Assert.assertThrows;
+
+import android.compat.testing.PlatformCompatChangeRule;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import libcore.junit.util.compat.CoreCompatChangeRule.DisableCompatChanges;
+import libcore.junit.util.compat.CoreCompatChangeRule.EnableCompatChanges;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TestRule;
+import org.junit.runner.RunWith;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipException;
+import java.util.zip.ZipFile;
+import java.util.zip.ZipInputStream;
+import java.util.zip.ZipOutputStream;
+
+/**
+ * Test SafeZipPathCallback.
+ */
+@RunWith(AndroidJUnit4.class)
+public class SafeZipPathValidatorCallbackTest {
+    @Rule
+    public TestRule mCompatChangeRule = new PlatformCompatChangeRule();
+
+    @Before
+    public void setUp() {
+        RuntimeInit.initZipPathValidatorCallback();
+    }
+
+    @Test
+    @EnableCompatChanges({SafeZipPathValidatorCallback.VALIDATE_ZIP_PATH_FOR_PATH_TRAVERSAL})
+    public void testNewZipFile_whenZipFileHasDangerousEntriesAndChangeEnabled_throws()
+            throws Exception {
+        final String[] dangerousEntryNames = {
+            "../foo.bar",
+            "foo/../bar.baz",
+            "foo/../../bar.baz",
+            "foo.bar/..",
+            "foo.bar/../",
+            "..",
+            "../",
+            "/foo",
+        };
+        for (String entryName : dangerousEntryNames) {
+            final File tempFile = File.createTempFile("smdc", "zip");
+            try {
+                writeZipFileOutputStreamWithEmptyEntry(tempFile, entryName);
+
+                assertThrows(
+                        "ZipException expected for entry: " + entryName,
+                        ZipException.class,
+                        () -> {
+                            new ZipFile(tempFile);
+                        });
+            } finally {
+                tempFile.delete();
+            }
+        }
+    }
+
+    @Test
+    @EnableCompatChanges({SafeZipPathValidatorCallback.VALIDATE_ZIP_PATH_FOR_PATH_TRAVERSAL})
+    public void
+            testZipInputStreamGetNextEntry_whenZipFileHasDangerousEntriesAndChangeEnabled_throws()
+                    throws Exception {
+        final String[] dangerousEntryNames = {
+            "../foo.bar",
+            "foo/../bar.baz",
+            "foo/../../bar.baz",
+            "foo.bar/..",
+            "foo.bar/../",
+            "..",
+            "../",
+            "/foo",
+        };
+        for (String entryName : dangerousEntryNames) {
+            byte[] badZipBytes = getZipBytesFromZipOutputStreamWithEmptyEntry(entryName);
+            try (ZipInputStream zis = new ZipInputStream(new ByteArrayInputStream(badZipBytes))) {
+                assertThrows(
+                        "ZipException expected for entry: " + entryName,
+                        ZipException.class,
+                        () -> {
+                            zis.getNextEntry();
+                        });
+            }
+        }
+    }
+
+    @Test
+    @EnableCompatChanges({SafeZipPathValidatorCallback.VALIDATE_ZIP_PATH_FOR_PATH_TRAVERSAL})
+    public void testNewZipFile_whenZipFileHasNormalEntriesAndChangeEnabled_doesNotThrow()
+            throws Exception {
+        final String[] normalEntryNames = {
+            "foo", "foo.bar", "foo..bar",
+        };
+        for (String entryName : normalEntryNames) {
+            final File tempFile = File.createTempFile("smdc", "zip");
+            try {
+                writeZipFileOutputStreamWithEmptyEntry(tempFile, entryName);
+                try {
+                    new ZipFile((tempFile));
+                } catch (ZipException e) {
+                    throw new AssertionError("ZipException not expected for entry: " + entryName);
+                }
+            } finally {
+                tempFile.delete();
+            }
+        }
+    }
+
+    @Test
+    @DisableCompatChanges({SafeZipPathValidatorCallback.VALIDATE_ZIP_PATH_FOR_PATH_TRAVERSAL})
+    public void
+            testZipInputStreamGetNextEntry_whenZipFileHasNormalEntriesAndChangeEnabled_doesNotThrow()
+                    throws Exception {
+        final String[] normalEntryNames = {
+            "foo", "foo.bar", "foo..bar",
+        };
+        for (String entryName : normalEntryNames) {
+            byte[] zipBytes = getZipBytesFromZipOutputStreamWithEmptyEntry(entryName);
+            try {
+                ZipInputStream zis = new ZipInputStream(new ByteArrayInputStream(zipBytes));
+                zis.getNextEntry();
+            } catch (ZipException e) {
+                throw new AssertionError("ZipException not expected for entry: " + entryName);
+            }
+        }
+    }
+
+    @Test
+    @DisableCompatChanges({SafeZipPathValidatorCallback.VALIDATE_ZIP_PATH_FOR_PATH_TRAVERSAL})
+    public void
+            testNewZipFile_whenZipFileHasNormalAndDangerousEntriesAndChangeDisabled_doesNotThrow()
+                    throws Exception {
+        final String[] entryNames = {
+            "../foo.bar",
+            "foo/../bar.baz",
+            "foo/../../bar.baz",
+            "foo.bar/..",
+            "foo.bar/../",
+            "..",
+            "../",
+            "/foo",
+            "foo",
+            "foo.bar",
+            "foo..bar",
+        };
+        for (String entryName : entryNames) {
+            final File tempFile = File.createTempFile("smdc", "zip");
+            try {
+                writeZipFileOutputStreamWithEmptyEntry(tempFile, entryName);
+                try {
+                    new ZipFile((tempFile));
+                } catch (ZipException e) {
+                    throw new AssertionError("ZipException not expected for entry: " + entryName);
+                }
+            } finally {
+                tempFile.delete();
+            }
+        }
+    }
+
+    @Test
+    @DisableCompatChanges({SafeZipPathValidatorCallback.VALIDATE_ZIP_PATH_FOR_PATH_TRAVERSAL})
+    public void
+            testZipInputStreamGetNextEntry_whenZipFileHasNormalAndDangerousEntriesAndChangeDisabled_doesNotThrow()
+                    throws Exception {
+        final String[] entryNames = {
+            "../foo.bar",
+            "foo/../bar.baz",
+            "foo/../../bar.baz",
+            "foo.bar/..",
+            "foo.bar/../",
+            "..",
+            "../",
+            "/foo",
+            "foo",
+            "foo.bar",
+            "foo..bar",
+        };
+        for (String entryName : entryNames) {
+            byte[] zipBytes = getZipBytesFromZipOutputStreamWithEmptyEntry(entryName);
+            try {
+                ZipInputStream zis = new ZipInputStream(new ByteArrayInputStream(zipBytes));
+                zis.getNextEntry();
+            } catch (ZipException e) {
+                throw new AssertionError("ZipException not expected for entry: " + entryName);
+            }
+        }
+    }
+
+    private void writeZipFileOutputStreamWithEmptyEntry(File tempFile, String entryName)
+            throws IOException {
+        FileOutputStream tempFileStream = new FileOutputStream(tempFile);
+        writeZipOutputStreamWithEmptyEntry(tempFileStream, entryName);
+        tempFileStream.close();
+    }
+
+    private byte[] getZipBytesFromZipOutputStreamWithEmptyEntry(String entryName)
+            throws IOException {
+        ByteArrayOutputStream bos = new ByteArrayOutputStream();
+        writeZipOutputStreamWithEmptyEntry(bos, entryName);
+        return bos.toByteArray();
+    }
+
+    private void writeZipOutputStreamWithEmptyEntry(OutputStream os, String entryName)
+            throws IOException {
+        ZipOutputStream zos = new ZipOutputStream(os);
+        ZipEntry entry = new ZipEntry(entryName);
+        zos.putNextEntry(entry);
+        zos.write(new byte[2]);
+        zos.closeEntry();
+        zos.close();
+    }
+}
diff --git a/core/tests/coretests/src/com/android/internal/util/TokenBucketTest.java b/core/tests/coretests/src/com/android/internal/util/TokenBucketTest.java
index 6c50bce..8b30828 100644
--- a/core/tests/coretests/src/com/android/internal/util/TokenBucketTest.java
+++ b/core/tests/coretests/src/com/android/internal/util/TokenBucketTest.java
@@ -16,6 +16,8 @@
 
 package com.android.internal.util;
 
+import static org.junit.Assert.assertThrows;
+
 import android.os.SystemClock;
 import android.text.format.DateUtils;
 
@@ -170,10 +172,9 @@
     }
 
     void assertThrow(Fn fn) {
-        try {
+        assertThrows(Throwable.class, () -> {
             fn.call();
-            fail("expected n exception to be thrown.");
-        } catch (Throwable t) { }
+        });
     }
 
     interface Fn { void call(); }
diff --git a/core/tests/hosttests/test-apps/MultiDexLegacyTestApp/src/com/android/multidexlegacytestapp/Test.java b/core/tests/hosttests/test-apps/MultiDexLegacyTestApp/src/com/android/multidexlegacytestapp/Test.java
index 41b8956f..a226325 100644
--- a/core/tests/hosttests/test-apps/MultiDexLegacyTestApp/src/com/android/multidexlegacytestapp/Test.java
+++ b/core/tests/hosttests/test-apps/MultiDexLegacyTestApp/src/com/android/multidexlegacytestapp/Test.java
@@ -31,6 +31,7 @@
         assertEquals(3366, getActivity().getValue());
     }
 
+    @SuppressWarnings("ReturnValueIgnored")
     public void testAnnotation() throws Exception {
         assertEquals(ReferencedByAnnotation.B,
                 ((AnnotationWithEnum) TestApplication.annotation).value());
diff --git a/core/tests/overlaytests/device/src/com/android/overlaytest/FabricatedOverlaysTest.java b/core/tests/overlaytests/device/src/com/android/overlaytest/FabricatedOverlaysTest.java
index 3465989..2da9a2e 100644
--- a/core/tests/overlaytests/device/src/com/android/overlaytest/FabricatedOverlaysTest.java
+++ b/core/tests/overlaytests/device/src/com/android/overlaytest/FabricatedOverlaysTest.java
@@ -18,7 +18,6 @@
 
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertTrue;
-import static org.testng.Assert.assertEquals;
 import static org.testng.Assert.assertFalse;
 import static org.testng.Assert.assertNull;
 import static org.testng.Assert.assertThrows;
@@ -45,7 +44,6 @@
 import org.junit.runners.JUnit4;
 
 import java.util.Collections;
-import java.util.List;
 import java.util.concurrent.TimeoutException;
 
 @RunWith(JUnit4.class)
@@ -221,11 +219,56 @@
     }
 
     @Test
+    public void setResourceValue_withNullResourceName() throws Exception {
+        final FabricatedOverlay.Builder builder = new FabricatedOverlay.Builder(
+                "android", TEST_OVERLAY_NAME, mContext.getPackageName());
+
+        assertThrows(NullPointerException.class,
+                () -> builder.setResourceValue(null, TypedValue.TYPE_INT_DEC, 1));
+    }
+
+    @Test
+    public void setResourceValue_withEmptyResourceName() throws Exception {
+        final FabricatedOverlay.Builder builder = new FabricatedOverlay.Builder(
+                "android", TEST_OVERLAY_NAME, mContext.getPackageName());
+
+        assertThrows(IllegalArgumentException.class,
+                () -> builder.setResourceValue("", TypedValue.TYPE_INT_DEC, 1));
+    }
+
+    @Test
+    public void setResourceValue_withEmptyPackageName() throws Exception {
+        final FabricatedOverlay.Builder builder = new FabricatedOverlay.Builder(
+                "android", TEST_OVERLAY_NAME, mContext.getPackageName());
+
+        assertThrows(IllegalArgumentException.class,
+                () -> builder.setResourceValue(":color/mycolor", TypedValue.TYPE_INT_DEC, 1));
+    }
+
+    @Test
+    public void setResourceValue_withInvalidTypeName() throws Exception {
+        final FabricatedOverlay.Builder builder = new FabricatedOverlay.Builder(
+                "android", TEST_OVERLAY_NAME, mContext.getPackageName());
+
+        assertThrows(IllegalArgumentException.class,
+                () -> builder.setResourceValue("c/mycolor", TypedValue.TYPE_INT_DEC, 1));
+    }
+
+    @Test
+    public void setResourceValue_withEmptyTypeName() throws Exception {
+        final FabricatedOverlay.Builder builder = new FabricatedOverlay.Builder(
+                "android", TEST_OVERLAY_NAME, mContext.getPackageName());
+
+        assertThrows(IllegalArgumentException.class,
+                () -> builder.setResourceValue("/mycolor", TypedValue.TYPE_INT_DEC, 1));
+    }
+
+    @Test
     public void testInvalidResourceValues() throws Exception {
         final FabricatedOverlay overlay = new FabricatedOverlay.Builder(
                 "android", TEST_OVERLAY_NAME, mContext.getPackageName())
                 .setResourceValue(TEST_RESOURCE, TypedValue.TYPE_INT_DEC, 1)
-                .setResourceValue("something", TypedValue.TYPE_INT_DEC, 1)
+                .setResourceValue("color/something", TypedValue.TYPE_INT_DEC, 1)
                 .build();
 
         waitForResourceValue(0);
diff --git a/core/tests/utiltests/src/com/android/internal/util/CallbackRegistryTest.java b/core/tests/utiltests/src/com/android/internal/util/CallbackRegistryTest.java
index c53f4cc..1581abb 100644
--- a/core/tests/utiltests/src/com/android/internal/util/CallbackRegistryTest.java
+++ b/core/tests/utiltests/src/com/android/internal/util/CallbackRegistryTest.java
@@ -20,6 +20,7 @@
 import org.junit.Test;
 
 import java.util.ArrayList;
+import java.util.Objects;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
@@ -39,11 +40,11 @@
     Integer argValue;
 
     private void addNotifyCount(Integer callback) {
-        if (callback == callback1) {
+        if (Objects.equals(callback, callback1)) {
             notify1++;
-        } else if (callback == callback2) {
+        } else if (Objects.equals(callback, callback2)) {
             notify2++;
-        } else if (callback == callback3) {
+        } else if (Objects.equals(callback, callback3)) {
             notify3++;
         }
         deepNotifyCount[callback]++;
@@ -114,7 +115,7 @@
                     public void onNotifyCallback(Integer callback, CallbackRegistryTest sender,
                             int arg1, Integer arg) {
                         addNotifyCount(callback);
-                        if (callback == callback1) {
+                        if (Objects.equals(callback, callback1)) {
                             registry.remove(callback1);
                             registry.remove(callback2);
                         }
@@ -166,9 +167,9 @@
                     public void onNotifyCallback(Integer callback, CallbackRegistryTest sender,
                             int arg1, Integer arg) {
                         addNotifyCount(callback);
-                        if (callback == callback1) {
+                        if (Objects.equals(callback, callback1)) {
                             registry.remove(callback2);
-                        } else if (callback == callback3) {
+                        } else if (Objects.equals(callback, callback3)) {
                             registry.add(callback2);
                         }
                     }
diff --git a/data/etc/privapp-permissions-platform.xml b/data/etc/privapp-permissions-platform.xml
index decfb9f..3e2b71f 100644
--- a/data/etc/privapp-permissions-platform.xml
+++ b/data/etc/privapp-permissions-platform.xml
@@ -495,6 +495,10 @@
         <permission name="android.permission.READ_SAFETY_CENTER_STATUS" />
         <!-- Permission required for CTS test - CtsTelephonyTestCases -->
         <permission name="android.permission.BIND_TELECOM_CONNECTION_SERVICE" />
+        <!-- Permission required for CTS test - CtsAppTestCases -->
+        <permission name="android.permission.CAPTURE_MEDIA_OUTPUT" />
+        <permission name="android.permission.CAPTURE_TUNER_AUDIO_INPUT" />
+        <permission name="android.permission.CAPTURE_VOICE_COMMUNICATION_OUTPUT" />
     </privapp-permissions>
 
     <privapp-permissions package="com.android.statementservice">
diff --git a/errorprone/java/com/google/errorprone/bugpatterns/android/EfficientXmlChecker.java b/errorprone/java/com/google/errorprone/bugpatterns/android/EfficientXmlChecker.java
index 8706a68..42e3046 100644
--- a/errorprone/java/com/google/errorprone/bugpatterns/android/EfficientXmlChecker.java
+++ b/errorprone/java/com/google/errorprone/bugpatterns/android/EfficientXmlChecker.java
@@ -168,6 +168,7 @@
      */
     private static final Matcher<ExpressionTree> CONVERT_PRIMITIVE_TO_STRING =
             new Matcher<ExpressionTree>() {
+        @SuppressWarnings("TreeToString") //TODO: Fix me
         @Override
         public boolean matches(ExpressionTree tree, VisitorState state) {
             if (PRIMITIVE_TO_STRING.matches(tree, state)) {
@@ -205,6 +206,7 @@
      */
     private static final Matcher<ExpressionTree> CONVERT_STRING_TO_PRIMITIVE =
             new Matcher<ExpressionTree>() {
+        @SuppressWarnings("TreeToString") //TODO: Fix me
         @Override
         public boolean matches(ExpressionTree tree, VisitorState state) {
             if (PRIMITIVE_PARSE.matches(tree, state)) {
diff --git a/graphics/java/android/graphics/drawable/RippleDrawable.java b/graphics/java/android/graphics/drawable/RippleDrawable.java
index 54c9f62..1a878df 100644
--- a/graphics/java/android/graphics/drawable/RippleDrawable.java
+++ b/graphics/java/android/graphics/drawable/RippleDrawable.java
@@ -766,7 +766,7 @@
         if (mBackground != null) {
             mBackground.onHotspotBoundsChanged();
         }
-        float newRadius = Math.round(getComputedRadius());
+        float newRadius = getComputedRadius();
         for (int i = 0; i < mRunningAnimations.size(); i++) {
             RippleAnimationSession s = mRunningAnimations.get(i);
             s.setRadius(newRadius);
diff --git a/graphics/java/android/graphics/drawable/VectorDrawable.java b/graphics/java/android/graphics/drawable/VectorDrawable.java
index 4065bd1..e25ee90 100644
--- a/graphics/java/android/graphics/drawable/VectorDrawable.java
+++ b/graphics/java/android/graphics/drawable/VectorDrawable.java
@@ -60,7 +60,7 @@
 import java.nio.ByteBuffer;
 import java.nio.ByteOrder;
 import java.util.ArrayList;
-import java.util.HashMap;
+import java.util.Map;
 import java.util.Stack;
 
 /**
@@ -1171,18 +1171,14 @@
 
         private static final int NATIVE_ALLOCATION_SIZE = 100;
 
-        private static final HashMap<String, Integer> sPropertyIndexMap =
-                new HashMap<String, Integer>() {
-                    {
-                        put("translateX", TRANSLATE_X_INDEX);
-                        put("translateY", TRANSLATE_Y_INDEX);
-                        put("scaleX", SCALE_X_INDEX);
-                        put("scaleY", SCALE_Y_INDEX);
-                        put("pivotX", PIVOT_X_INDEX);
-                        put("pivotY", PIVOT_Y_INDEX);
-                        put("rotation", ROTATION_INDEX);
-                    }
-                };
+        private static final Map<String, Integer> sPropertyIndexMap = Map.of(
+                "translateX", TRANSLATE_X_INDEX,
+                "translateY", TRANSLATE_Y_INDEX,
+                "scaleX", SCALE_X_INDEX,
+                "scaleY", SCALE_Y_INDEX,
+                "pivotX", PIVOT_X_INDEX,
+                "pivotY", PIVOT_Y_INDEX,
+                "rotation", ROTATION_INDEX);
 
         static int getPropertyIndex(String propertyName) {
             if (sPropertyIndexMap.containsKey(propertyName)) {
@@ -1285,18 +1281,15 @@
                     }
                 };
 
-        private static final HashMap<String, Property> sPropertyMap =
-                new HashMap<String, Property>() {
-                    {
-                        put("translateX", TRANSLATE_X);
-                        put("translateY", TRANSLATE_Y);
-                        put("scaleX", SCALE_X);
-                        put("scaleY", SCALE_Y);
-                        put("pivotX", PIVOT_X);
-                        put("pivotY", PIVOT_Y);
-                        put("rotation", ROTATION);
-                    }
-                };
+        private static final Map<String, Property> sPropertyMap = Map.of(
+                "translateX", TRANSLATE_X,
+                "translateY", TRANSLATE_Y,
+                "scaleX", SCALE_X,
+                "scaleY", SCALE_Y,
+                "pivotX", PIVOT_X,
+                "pivotY", PIVOT_Y,
+                "rotation", ROTATION);
+
         // Temp array to store transform values obtained from native.
         private float[] mTransform;
         /////////////////////////////////////////////////////
@@ -1762,19 +1755,15 @@
 
         private static final int NATIVE_ALLOCATION_SIZE = 264;
         // Property map for animatable attributes.
-        private final static HashMap<String, Integer> sPropertyIndexMap
-                = new HashMap<String, Integer> () {
-            {
-                put("strokeWidth", STROKE_WIDTH_INDEX);
-                put("strokeColor", STROKE_COLOR_INDEX);
-                put("strokeAlpha", STROKE_ALPHA_INDEX);
-                put("fillColor", FILL_COLOR_INDEX);
-                put("fillAlpha", FILL_ALPHA_INDEX);
-                put("trimPathStart", TRIM_PATH_START_INDEX);
-                put("trimPathEnd", TRIM_PATH_END_INDEX);
-                put("trimPathOffset", TRIM_PATH_OFFSET_INDEX);
-            }
-        };
+        private static final Map<String, Integer> sPropertyIndexMap = Map.of(
+                "strokeWidth", STROKE_WIDTH_INDEX,
+                "strokeColor", STROKE_COLOR_INDEX,
+                "strokeAlpha", STROKE_ALPHA_INDEX,
+                "fillColor", FILL_COLOR_INDEX,
+                "fillAlpha", FILL_ALPHA_INDEX,
+                "trimPathStart", TRIM_PATH_START_INDEX,
+                "trimPathEnd", TRIM_PATH_END_INDEX,
+                "trimPathOffset", TRIM_PATH_OFFSET_INDEX);
 
         // Below are the Properties that wrap the setters to avoid reflection overhead in animations
         private static final Property<VFullPath, Float> STROKE_WIDTH =
@@ -1881,19 +1870,15 @@
                     }
                 };
 
-        private final static HashMap<String, Property> sPropertyMap
-                = new HashMap<String, Property> () {
-            {
-                put("strokeWidth", STROKE_WIDTH);
-                put("strokeColor", STROKE_COLOR);
-                put("strokeAlpha", STROKE_ALPHA);
-                put("fillColor", FILL_COLOR);
-                put("fillAlpha", FILL_ALPHA);
-                put("trimPathStart", TRIM_PATH_START);
-                put("trimPathEnd", TRIM_PATH_END);
-                put("trimPathOffset", TRIM_PATH_OFFSET);
-            }
-        };
+        private static final Map<String, Property> sPropertyMap = Map.of(
+                "strokeWidth", STROKE_WIDTH,
+                "strokeColor", STROKE_COLOR,
+                "strokeAlpha", STROKE_ALPHA,
+                "fillColor", FILL_COLOR,
+                "fillAlpha", FILL_ALPHA,
+                "trimPathStart", TRIM_PATH_START,
+                "trimPathEnd", TRIM_PATH_END,
+                "trimPathOffset", TRIM_PATH_OFFSET);
 
         // Temp array to store property data obtained from native getter.
         private byte[] mPropertyData;
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
index a7fa2d9..16760e26 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
@@ -96,7 +96,7 @@
         ActivityEmbeddingComponent {
     static final String TAG = "SplitController";
     static final boolean ENABLE_SHELL_TRANSITIONS =
-            SystemProperties.getBoolean("persist.wm.debug.shell_transit", true);
+            SystemProperties.getBoolean("persist.wm.debug.shell_transit", false);
 
     @VisibleForTesting
     @GuardedBy("mLock")
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java
index d9eaeee..f811940 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java
@@ -116,7 +116,7 @@
     private final TouchTracker mTouchTracker = new TouchTracker();
 
     private final SparseArray<BackAnimationRunner> mAnimationDefinition = new SparseArray<>();
-
+    @Nullable
     private IOnBackInvokedCallback mActiveCallback;
 
     @VisibleForTesting
@@ -180,6 +180,10 @@
     }
 
     private void initBackAnimationRunners() {
+        if (!IS_U_ANIMATION_ENABLED) {
+            return;
+        }
+
         final CrossTaskBackAnimation crossTaskAnimation = new CrossTaskBackAnimation(mContext);
         mAnimationDefinition.set(BackNavigationInfo.TYPE_CROSS_TASK,
                 new BackAnimationRunner(crossTaskAnimation.mCallback, crossTaskAnimation.mRunner));
@@ -207,7 +211,7 @@
     private void updateEnableAnimationFromSetting() {
         int settingValue = Global.getInt(mContext.getContentResolver(),
                 Global.ENABLE_BACK_ANIMATION, SETTING_VALUE_OFF);
-        boolean isEnabled = settingValue == SETTING_VALUE_ON && IS_U_ANIMATION_ENABLED;
+        boolean isEnabled = settingValue == SETTING_VALUE_ON;
         mEnableAnimations.set(isEnabled);
         ProtoLog.d(WM_SHELL_BACK_PREVIEW, "Back animation enabled=%s", isEnabled);
     }
@@ -350,10 +354,14 @@
             return;
         }
         final int backType = backNavigationInfo.getType();
-        final boolean shouldDispatchToAnimator = shouldDispatchToAnimator(backType);
+        final boolean shouldDispatchToAnimator = shouldDispatchToAnimator();
         if (shouldDispatchToAnimator) {
-            mActiveCallback = mAnimationDefinition.get(backType).getCallback();
-            mAnimationDefinition.get(backType).startGesture();
+            if (mAnimationDefinition.contains(backType)) {
+                mActiveCallback = mAnimationDefinition.get(backType).getCallback();
+                mAnimationDefinition.get(backType).startGesture();
+            } else {
+                mActiveCallback = null;
+            }
         } else {
             mActiveCallback = mBackNavigationInfo.getOnBackInvokedCallback();
             dispatchOnBackStarted(mActiveCallback, mTouchTracker.createStartEvent(null));
@@ -361,9 +369,11 @@
     }
 
     private void onMove() {
-        if (!mBackGestureStarted || mBackNavigationInfo == null || !mEnableAnimations.get()) {
+        if (!mBackGestureStarted || mBackNavigationInfo == null || !mEnableAnimations.get()
+                || mActiveCallback == null) {
             return;
         }
+
         final BackEvent backEvent = mTouchTracker.createProgressEvent();
         dispatchOnBackProgressed(mActiveCallback, backEvent);
     }
@@ -387,11 +397,10 @@
         }
     }
 
-    private boolean shouldDispatchToAnimator(int backType) {
+    private boolean shouldDispatchToAnimator() {
         return mEnableAnimations.get()
                 && mBackNavigationInfo != null
-                && mBackNavigationInfo.isPrepareRemoteAnimation()
-                && mAnimationDefinition.contains(backType);
+                && mBackNavigationInfo.isPrepareRemoteAnimation();
     }
 
     private void dispatchOnBackStarted(IOnBackInvokedCallback callback,
@@ -461,6 +470,30 @@
         mTouchTracker.setProgressThreshold(progressThreshold);
     }
 
+    private void invokeOrCancelBack() {
+        // Make a synchronized call to core before dispatch back event to client side.
+        // If the close transition happens before the core receives onAnimationFinished, there will
+        // play a second close animation for that transition.
+        if (mBackAnimationFinishedCallback != null) {
+            try {
+                mBackAnimationFinishedCallback.onAnimationFinished(mTriggerBack);
+            } catch (RemoteException e) {
+                Log.e(TAG, "Failed call IBackAnimationFinishedCallback", e);
+            }
+            mBackAnimationFinishedCallback = null;
+        }
+
+        if (mBackNavigationInfo != null) {
+            final IOnBackInvokedCallback callback = mBackNavigationInfo.getOnBackInvokedCallback();
+            if (mTriggerBack) {
+                dispatchOnBackInvoked(callback);
+            } else {
+                dispatchOnBackCancelled(callback);
+            }
+        }
+        finishBackNavigation();
+    }
+
     /**
      * Called when the gesture is released, then it could start the post commit animation.
      */
@@ -493,15 +526,9 @@
         }
 
         final int backType = mBackNavigationInfo.getType();
-        // Directly finish back navigation if no animator defined.
-        if (!shouldDispatchToAnimator(backType)) {
-            if (mTriggerBack) {
-                dispatchOnBackInvoked(mActiveCallback);
-            } else {
-                dispatchOnBackCancelled(mActiveCallback);
-            }
-            // Animation missing. Simply finish back navigation.
-            finishBackNavigation();
+        // Simply trigger and finish back navigation when no animator defined.
+        if (!shouldDispatchToAnimator() || mActiveCallback == null) {
+            invokeOrCancelBack();
             return;
         }
 
@@ -549,16 +576,7 @@
         ProtoLog.d(WM_SHELL_BACK_PREVIEW, "BackAnimationController: onBackAnimationFinished()");
 
         // Trigger the real back.
-        if (mBackNavigationInfo != null) {
-            IOnBackInvokedCallback callback = mBackNavigationInfo.getOnBackInvokedCallback();
-            if (mTriggerBack) {
-                dispatchOnBackInvoked(callback);
-            } else {
-                dispatchOnBackCancelled(callback);
-            }
-        }
-
-        finishBackNavigation();
+        invokeOrCancelBack();
     }
 
     /**
@@ -567,25 +585,14 @@
     @VisibleForTesting
     void finishBackNavigation() {
         ProtoLog.d(WM_SHELL_BACK_PREVIEW, "BackAnimationController: finishBackNavigation()");
-        BackNavigationInfo backNavigationInfo = mBackNavigationInfo;
-        boolean triggerBack = mTriggerBack;
-        mBackNavigationInfo = null;
-        mTriggerBack = false;
         mShouldStartOnNextMoveEvent = false;
         mTouchTracker.reset();
         mActiveCallback = null;
-        if (backNavigationInfo == null) {
-            return;
+        if (mBackNavigationInfo != null) {
+            mBackNavigationInfo.onBackNavigationFinished(mTriggerBack);
+            mBackNavigationInfo = null;
         }
-        if (mBackAnimationFinishedCallback != null) {
-            try {
-                mBackAnimationFinishedCallback.onAnimationFinished(triggerBack);
-            } catch (RemoteException e) {
-                Log.e(TAG, "Failed call IBackAnimationFinishedCallback", e);
-            }
-            mBackAnimationFinishedCallback = null;
-        }
-        backNavigationInfo.onBackNavigationFinished(triggerBack);
+        mTriggerBack = false;
     }
 
     private void createAdapter() {
@@ -611,8 +618,8 @@
                     mBackAnimationFinishedCallback = finishedCallback;
 
                     ProtoLog.d(WM_SHELL_BACK_PREVIEW, "BackAnimationController: startAnimation()");
-                    runner.startAnimation(apps, wallpapers, nonApps,
-                            BackAnimationController.this::onBackAnimationFinished);
+                    runner.startAnimation(apps, wallpapers, nonApps, () -> mShellExecutor.execute(
+                            BackAnimationController.this::onBackAnimationFinished));
 
                     if (apps.length >= 1) {
                         dispatchOnBackStarted(
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitDecorManager.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitDecorManager.java
index 5b7ed27..6e116b9 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitDecorManager.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitDecorManager.java
@@ -163,7 +163,8 @@
 
     /** Showing resizing hint. */
     public void onResizing(ActivityManager.RunningTaskInfo resizingTask, Rect newBounds,
-            Rect sideBounds, SurfaceControl.Transaction t, int offsetX, int offsetY) {
+            Rect sideBounds, SurfaceControl.Transaction t, int offsetX, int offsetY,
+            boolean immediately) {
         if (mResizingIconView == null) {
             return;
         }
@@ -178,8 +179,8 @@
 
         final boolean show =
                 newBounds.width() > mBounds.width() || newBounds.height() > mBounds.height();
-        final boolean animate = show != mShown;
-        if (animate && mFadeAnimator != null && mFadeAnimator.isRunning()) {
+        final boolean update = show != mShown;
+        if (update && mFadeAnimator != null && mFadeAnimator.isRunning()) {
             // If we need to animate and animator still running, cancel it before we ensure both
             // background and icon surfaces are non null for next animation.
             mFadeAnimator.cancel();
@@ -192,7 +193,7 @@
                     .setLayer(mBackgroundLeash, Integer.MAX_VALUE - 1);
         }
 
-        if (mGapBackgroundLeash == null) {
+        if (mGapBackgroundLeash == null && !immediately) {
             final boolean isLandscape = newBounds.height() == sideBounds.height();
             final int left = isLandscape ? mBounds.width() : 0;
             final int top = isLandscape ? 0 : mBounds.height();
@@ -221,8 +222,13 @@
                 newBounds.width() / 2 - mIconSize / 2,
                 newBounds.height() / 2 - mIconSize / 2);
 
-        if (animate) {
-            startFadeAnimation(show, null /* finishedConsumer */);
+        if (update) {
+            if (immediately) {
+                t.setVisibility(mBackgroundLeash, show);
+                t.setVisibility(mIconLeash, show);
+            } else {
+                startFadeAnimation(show, null /* finishedConsumer */);
+            }
             mShown = show;
         }
     }
@@ -319,10 +325,12 @@
             @Override
             public void onAnimationStart(@NonNull Animator animation) {
                 if (show) {
-                    animT.show(mBackgroundLeash).show(mIconLeash).show(mGapBackgroundLeash).apply();
-                } else {
-                    animT.hide(mGapBackgroundLeash).apply();
+                    animT.show(mBackgroundLeash).show(mIconLeash);
                 }
+                if (mGapBackgroundLeash != null) {
+                    animT.setVisibility(mGapBackgroundLeash, show);
+                }
+                animT.apply();
             }
 
             @Override
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java
index 3de1045..ec9e6f7 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java
@@ -83,8 +83,8 @@
 
     private static final int FLING_RESIZE_DURATION = 250;
     private static final int FLING_SWITCH_DURATION = 350;
-    private static final int FLING_ENTER_DURATION = 350;
-    private static final int FLING_EXIT_DURATION = 350;
+    private static final int FLING_ENTER_DURATION = 450;
+    private static final int FLING_EXIT_DURATION = 450;
 
     private int mDividerWindowWidth;
     private int mDividerInsets;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
index 4459f57..f1670cd 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
@@ -27,7 +27,6 @@
 import com.android.internal.logging.UiEventLogger;
 import com.android.internal.statusbar.IStatusBarService;
 import com.android.launcher3.icons.IconProvider;
-import com.android.wm.shell.RootDisplayAreaOrganizer;
 import com.android.wm.shell.RootTaskDisplayAreaOrganizer;
 import com.android.wm.shell.ShellTaskOrganizer;
 import com.android.wm.shell.TaskViewTransitions;
@@ -272,12 +271,11 @@
             TaskStackListenerImpl taskStackListener,
             UiEventLogger uiEventLogger,
             InteractionJankMonitor jankMonitor,
-            RootDisplayAreaOrganizer rootDisplayAreaOrganizer,
             @ShellMainThread ShellExecutor mainExecutor,
             @ShellMainThread Handler mainHandler) {
         return OneHandedController.create(context, shellInit, shellCommandHandler, shellController,
                 windowManager, displayController, displayLayout, taskStackListener, jankMonitor,
-                uiEventLogger, rootDisplayAreaOrganizer, mainExecutor, mainHandler);
+                uiEventLogger, mainExecutor, mainHandler);
     }
 
     //
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/BackgroundWindowManager.java b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/BackgroundWindowManager.java
index b5ed509..b310ee2 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/BackgroundWindowManager.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/BackgroundWindowManager.java
@@ -34,7 +34,6 @@
 import android.os.Binder;
 import android.util.Slog;
 import android.view.ContextThemeWrapper;
-import android.view.Display;
 import android.view.IWindow;
 import android.view.LayoutInflater;
 import android.view.SurfaceControl;
@@ -48,7 +47,6 @@
 import androidx.annotation.Nullable;
 
 import com.android.wm.shell.R;
-import com.android.wm.shell.RootDisplayAreaOrganizer;
 import com.android.wm.shell.common.DisplayLayout;
 
 import java.io.PrintWriter;
@@ -69,14 +67,11 @@
     private SurfaceControl mLeash;
     private View mBackgroundView;
     private @OneHandedState.State int mCurrentState;
-    private RootDisplayAreaOrganizer mRootDisplayAreaOrganizer;
 
-    public BackgroundWindowManager(Context context,
-            RootDisplayAreaOrganizer rootDisplayAreaOrganizer) {
+    public BackgroundWindowManager(Context context) {
         super(context.getResources().getConfiguration(), null /* rootSurface */,
                 null /* hostInputToken */);
         mContext = context;
-        mRootDisplayAreaOrganizer = rootDisplayAreaOrganizer;
         mTransactionFactory = SurfaceControl.Transaction::new;
     }
 
@@ -117,7 +112,6 @@
                 .setOpaque(true)
                 .setName(TAG)
                 .setCallsite("BackgroundWindowManager#attachToParentSurface");
-        mRootDisplayAreaOrganizer.attachToDisplayArea(Display.DEFAULT_DISPLAY, builder);
         mLeash = builder.build();
         b.setParent(mLeash);
     }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedController.java
index ad135d1..679d4ca 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedController.java
@@ -47,7 +47,6 @@
 import com.android.internal.jank.InteractionJankMonitor;
 import com.android.internal.logging.UiEventLogger;
 import com.android.wm.shell.R;
-import com.android.wm.shell.RootDisplayAreaOrganizer;
 import com.android.wm.shell.common.DisplayChangeController;
 import com.android.wm.shell.common.DisplayController;
 import com.android.wm.shell.common.DisplayLayout;
@@ -205,14 +204,12 @@
             DisplayController displayController, DisplayLayout displayLayout,
             TaskStackListenerImpl taskStackListener,
             InteractionJankMonitor jankMonitor, UiEventLogger uiEventLogger,
-            RootDisplayAreaOrganizer rootDisplayAreaOrganizer,
             ShellExecutor mainExecutor, Handler mainHandler) {
         OneHandedSettingsUtil settingsUtil = new OneHandedSettingsUtil();
         OneHandedAccessibilityUtil accessibilityUtil = new OneHandedAccessibilityUtil(context);
         OneHandedTimeoutHandler timeoutHandler = new OneHandedTimeoutHandler(mainExecutor);
         OneHandedState oneHandedState = new OneHandedState();
-        BackgroundWindowManager backgroundWindowManager =
-                new BackgroundWindowManager(context, rootDisplayAreaOrganizer);
+        BackgroundWindowManager backgroundWindowManager = new BackgroundWindowManager(context);
         OneHandedTutorialHandler tutorialHandler = new OneHandedTutorialHandler(context,
                 settingsUtil, windowManager, backgroundWindowManager);
         OneHandedAnimationController animationController =
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
index c2ab7ef..3bb630d 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
@@ -169,6 +169,7 @@
     private ValueAnimator mDividerFadeInAnimator;
     private boolean mDividerVisible;
     private boolean mKeyguardShowing;
+    private boolean mShowDecorImmediately;
     private final SyncTransactionQueue mSyncQueue;
     private final ShellTaskOrganizer mTaskOrganizer;
     private final Context mContext;
@@ -1556,6 +1557,7 @@
                 if (mLogger.isEnterRequestedByDrag()) {
                     updateSurfaceBounds(mSplitLayout, t, false /* applyResizingOffset */);
                 } else {
+                    mShowDecorImmediately = true;
                     mSplitLayout.flingDividerToCenter();
                 }
             });
@@ -1631,14 +1633,16 @@
         updateSurfaceBounds(layout, t, true /* applyResizingOffset */);
         getMainStageBounds(mTempRect1);
         getSideStageBounds(mTempRect2);
-        mMainStage.onResizing(mTempRect1, mTempRect2, t, offsetX, offsetY);
-        mSideStage.onResizing(mTempRect2, mTempRect1, t, offsetX, offsetY);
+        mMainStage.onResizing(mTempRect1, mTempRect2, t, offsetX, offsetY, mShowDecorImmediately);
+        mSideStage.onResizing(mTempRect2, mTempRect1, t, offsetX, offsetY, mShowDecorImmediately);
         t.apply();
         mTransactionPool.release(t);
     }
 
     @Override
     public void onLayoutSizeChanged(SplitLayout layout) {
+        // Reset this flag every time onLayoutSizeChanged.
+        mShowDecorImmediately = false;
         final WindowContainerTransaction wct = new WindowContainerTransaction();
         updateWindowBounds(layout, wct);
         sendOnBoundsChanged();
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java
index acad5d9..bcf900b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java
@@ -289,10 +289,10 @@
     }
 
     void onResizing(Rect newBounds, Rect sideBounds, SurfaceControl.Transaction t, int offsetX,
-            int offsetY) {
+            int offsetY, boolean immediately) {
         if (mSplitDecorManager != null && mRootTaskInfo != null) {
             mSplitDecorManager.onResizing(mRootTaskInfo, newBounds, sideBounds, t, offsetX,
-                    offsetY);
+                    offsetY, immediately);
         }
     }
 
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java
index c6f31c2..56d51bd 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java
@@ -80,7 +80,7 @@
 
     /** Set to {@code true} to enable shell transitions. */
     public static final boolean ENABLE_SHELL_TRANSITIONS =
-            SystemProperties.getBoolean("persist.wm.debug.shell_transit", true);
+            SystemProperties.getBoolean("persist.wm.debug.shell_transit", false);
     public static final boolean SHELL_TRANSITIONS_ROTATION = ENABLE_SHELL_TRANSITIONS
             && SystemProperties.getBoolean("persist.wm.debug.shell_transit_rotate", false);
 
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java
index ca15f00..ebe5c5e 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java
@@ -124,7 +124,8 @@
         TaskPositioner taskPositioner = new TaskPositioner(mTaskOrganizer, windowDecoration,
                 mDragStartListener);
         CaptionTouchEventListener touchEventListener =
-                new CaptionTouchEventListener(taskInfo, taskPositioner);
+                new CaptionTouchEventListener(taskInfo, taskPositioner,
+                        windowDecoration.getDragDetector());
         windowDecoration.setCaptionListeners(touchEventListener, touchEventListener);
         windowDecoration.setDragResizeCallback(taskPositioner);
         setupWindowDecorationForTransition(taskInfo, startT, finishT);
@@ -173,16 +174,18 @@
         private final int mTaskId;
         private final WindowContainerToken mTaskToken;
         private final DragResizeCallback mDragResizeCallback;
+        private final DragDetector mDragDetector;
 
         private int mDragPointerId = -1;
-        private boolean mDragActive = false;
 
         private CaptionTouchEventListener(
                 RunningTaskInfo taskInfo,
-                DragResizeCallback dragResizeCallback) {
+                DragResizeCallback dragResizeCallback,
+                DragDetector dragDetector) {
             mTaskId = taskInfo.taskId;
             mTaskToken = taskInfo.token;
             mDragResizeCallback = dragResizeCallback;
+            mDragDetector = dragDetector;
         }
 
         @Override
@@ -231,19 +234,21 @@
 
         @Override
         public boolean onTouch(View v, MotionEvent e) {
+            boolean isDrag = false;
             int id = v.getId();
             if (id != R.id.caption_handle && id != R.id.caption) {
                 return false;
             }
-            if (id == R.id.caption_handle || mDragActive) {
+            if (id == R.id.caption_handle) {
+                isDrag = mDragDetector.detectDragEvent(e);
                 handleEventForMove(e);
             }
             if (e.getAction() != MotionEvent.ACTION_DOWN) {
-                return false;
+                return isDrag;
             }
             RunningTaskInfo taskInfo = mTaskOrganizer.getRunningTaskInfo(mTaskId);
             if (taskInfo.isFocused) {
-                return false;
+                return isDrag;
             }
             WindowContainerTransaction wct = new WindowContainerTransaction();
             wct.reorder(mTaskToken, true /* onTop */);
@@ -251,6 +256,10 @@
             return true;
         }
 
+        /**
+         * @param e {@link MotionEvent} to process
+         * @return {@code true} if a drag is happening; or {@code false} if it is not
+         */
         private void handleEventForMove(MotionEvent e) {
             RunningTaskInfo taskInfo = mTaskOrganizer.getRunningTaskInfo(mTaskId);
             int windowingMode =  mDesktopModeController
@@ -259,12 +268,12 @@
                 return;
             }
             switch (e.getActionMasked()) {
-                case MotionEvent.ACTION_DOWN:
-                    mDragActive = true;
-                    mDragPointerId  = e.getPointerId(0);
+                case MotionEvent.ACTION_DOWN: {
+                    mDragPointerId = e.getPointerId(0);
                     mDragResizeCallback.onDragResizeStart(
                             0 /* ctrlType */, e.getRawX(0), e.getRawY(0));
                     break;
+                }
                 case MotionEvent.ACTION_MOVE: {
                     int dragPointerIdx = e.findPointerIndex(mDragPointerId);
                     mDragResizeCallback.onDragResizeMove(
@@ -273,7 +282,6 @@
                 }
                 case MotionEvent.ACTION_UP:
                 case MotionEvent.ACTION_CANCEL: {
-                    mDragActive = false;
                     int dragPointerIdx = e.findPointerIndex(mDragPointerId);
                     int statusBarHeight = mDisplayController.getDisplayLayout(taskInfo.displayId)
                             .stableInsets().top;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java
index 03cad04..affde30 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java
@@ -62,6 +62,8 @@
 
     private boolean mDesktopActive;
 
+    private DragDetector mDragDetector;
+
     private AdditionalWindow mHandleMenu;
 
     CaptionWindowDecoration(
@@ -79,6 +81,7 @@
         mChoreographer = choreographer;
         mSyncQueue = syncQueue;
         mDesktopActive = DesktopModeStatus.isActive(mContext);
+        mDragDetector = new DragDetector(ViewConfiguration.get(context).getScaledTouchSlop());
     }
 
     void setCaptionListeners(
@@ -92,6 +95,10 @@
         mDragResizeCallback = dragResizeCallback;
     }
 
+    DragDetector getDragDetector() {
+        return mDragDetector;
+    }
+
     @Override
     void relayout(ActivityManager.RunningTaskInfo taskInfo) {
         final SurfaceControl.Transaction t = new SurfaceControl.Transaction();
@@ -182,6 +189,8 @@
         }
 
         int touchSlop = ViewConfiguration.get(mResult.mRootView.getContext()).getScaledTouchSlop();
+        mDragDetector.setTouchSlop(touchSlop);
+
         int resize_handle = mResult.mRootView.getResources()
                 .getDimensionPixelSize(R.dimen.freeform_resize_handle);
         int resize_corner = mResult.mRootView.getResources()
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragDetector.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragDetector.java
new file mode 100644
index 0000000..0abe8ab
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragDetector.java
@@ -0,0 +1,87 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.windowdecor;
+
+import static android.view.MotionEvent.ACTION_CANCEL;
+import static android.view.MotionEvent.ACTION_DOWN;
+import static android.view.MotionEvent.ACTION_MOVE;
+import static android.view.MotionEvent.ACTION_UP;
+
+import android.graphics.PointF;
+import android.view.MotionEvent;
+
+/**
+ * A detector for touch inputs that differentiates between drag and click inputs.
+ * All touch events must be passed through this class to track a drag event.
+ */
+public class DragDetector {
+    private int mTouchSlop;
+    private PointF mInputDownPoint;
+    private boolean mIsDragEvent;
+    private int mDragPointerId;
+    public DragDetector(int touchSlop) {
+        mTouchSlop = touchSlop;
+        mInputDownPoint = new PointF();
+        mIsDragEvent = false;
+        mDragPointerId = -1;
+    }
+
+    /**
+     * Determine if {@link MotionEvent} is part of a drag event.
+     * @return {@code true} if this is a drag event, {@code false} if not
+     */
+    public boolean detectDragEvent(MotionEvent ev) {
+        switch (ev.getAction()) {
+            case ACTION_DOWN: {
+                mDragPointerId = ev.getPointerId(0);
+                float rawX = ev.getRawX(0);
+                float rawY = ev.getRawY(0);
+                mInputDownPoint.set(rawX, rawY);
+                return false;
+            }
+            case ACTION_MOVE: {
+                if (!mIsDragEvent) {
+                    int dragPointerIndex = ev.findPointerIndex(mDragPointerId);
+                    float dx = ev.getRawX(dragPointerIndex) - mInputDownPoint.x;
+                    float dy = ev.getRawY(dragPointerIndex) - mInputDownPoint.y;
+                    if (Math.hypot(dx, dy) > mTouchSlop) {
+                        mIsDragEvent = true;
+                    }
+                }
+                return mIsDragEvent;
+            }
+            case ACTION_UP: {
+                boolean result = mIsDragEvent;
+                mIsDragEvent = false;
+                mInputDownPoint.set(0, 0);
+                mDragPointerId = -1;
+                return result;
+            }
+            case ACTION_CANCEL: {
+                mIsDragEvent = false;
+                mInputDownPoint.set(0, 0);
+                mDragPointerId = -1;
+                return false;
+            }
+        }
+        return mIsDragEvent;
+    }
+
+    public void setTouchSlop(int touchSlop) {
+        mTouchSlop = touchSlop;
+    }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeInputListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeInputListener.java
index b9f16b6..d3f1332 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeInputListener.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeInputListener.java
@@ -22,7 +22,6 @@
 import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION;
 
 import android.content.Context;
-import android.graphics.PointF;
 import android.graphics.Rect;
 import android.graphics.Region;
 import android.hardware.input.InputManager;
@@ -38,6 +37,7 @@
 import android.view.MotionEvent;
 import android.view.PointerIcon;
 import android.view.SurfaceControl;
+import android.view.ViewConfiguration;
 import android.view.WindowManagerGlobal;
 
 import com.android.internal.view.BaseIWindow;
@@ -76,7 +76,7 @@
     private Rect mRightBottomCornerBounds;
 
     private int mDragPointerId = -1;
-    private int mTouchSlop;
+    private DragDetector mDragDetector;
 
     DragResizeInputListener(
             Context context,
@@ -115,6 +115,7 @@
         mInputEventReceiver = new TaskResizeInputEventReceiver(
                 mInputChannel, mHandler, mChoreographer);
         mCallback = callback;
+        mDragDetector = new DragDetector(ViewConfiguration.get(context).getScaledTouchSlop());
     }
 
     /**
@@ -146,7 +147,7 @@
         mHeight = height;
         mResizeHandleThickness = resizeHandleThickness;
         mCornerSize = cornerSize;
-        mTouchSlop = touchSlop;
+        mDragDetector.setTouchSlop(touchSlop);
 
         Region touchRegion = new Region();
         final Rect topInputBounds = new Rect(0, 0, mWidth, mResizeHandleThickness);
@@ -228,7 +229,6 @@
         private boolean mConsumeBatchEventScheduled;
         private boolean mShouldHandleEvents;
         private boolean mDragging;
-        private final PointF mActionDownPoint = new PointF();
 
         private TaskResizeInputEventReceiver(
                 InputChannel inputChannel, Handler handler, Choreographer choreographer) {
@@ -276,7 +276,9 @@
             // Check if this is a touch event vs mouse event.
             // Touch events are tracked in four corners. Other events are tracked in resize edges.
             boolean isTouch = (e.getSource() & SOURCE_TOUCHSCREEN) == SOURCE_TOUCHSCREEN;
-
+            if (isTouch) {
+                mDragging = mDragDetector.detectDragEvent(e);
+            }
             switch (e.getActionMasked()) {
                 case MotionEvent.ACTION_DOWN: {
                     float x = e.getX(0);
@@ -290,7 +292,6 @@
                         mDragPointerId = e.getPointerId(0);
                         float rawX = e.getRawX(0);
                         float rawY = e.getRawY(0);
-                        mActionDownPoint.set(rawX, rawY);
                         int ctrlType = calculateCtrlType(isTouch, x, y);
                         mCallback.onDragResizeStart(ctrlType, rawX, rawY);
                         result = true;
@@ -304,14 +305,7 @@
                     int dragPointerIndex = e.findPointerIndex(mDragPointerId);
                     float rawX = e.getRawX(dragPointerIndex);
                     float rawY = e.getRawY(dragPointerIndex);
-                    if (isTouch) {
-                        // Check for touch slop for touch events
-                        float dx = rawX - mActionDownPoint.x;
-                        float dy = rawY - mActionDownPoint.y;
-                        if (!mDragging && Math.hypot(dx, dy) > mTouchSlop) {
-                            mDragging = true;
-                        }
-                    } else {
+                    if (!isTouch) {
                         // For all other types allow immediate dragging.
                         mDragging = true;
                     }
@@ -323,14 +317,13 @@
                 }
                 case MotionEvent.ACTION_UP:
                 case MotionEvent.ACTION_CANCEL: {
-                    if (mDragging) {
+                    if (mShouldHandleEvents && mDragging) {
                         int dragPointerIndex = e.findPointerIndex(mDragPointerId);
                         mCallback.onDragResizeEnd(
                                 e.getRawX(dragPointerIndex), e.getRawY(dragPointerIndex));
                     }
                     mDragging = false;
                     mShouldHandleEvents = false;
-                    mActionDownPoint.set(0, 0);
                     mDragPointerId = -1;
                     result = true;
                     break;
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java
index b603e03..d75c36c 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java
@@ -38,6 +38,7 @@
 import android.content.pm.ApplicationInfo;
 import android.graphics.Point;
 import android.graphics.Rect;
+import android.os.Bundle;
 import android.os.Handler;
 import android.os.IBinder;
 import android.os.RemoteCallback;
@@ -56,6 +57,7 @@
 import android.window.IBackAnimationFinishedCallback;
 import android.window.IOnBackInvokedCallback;
 
+import androidx.annotation.Nullable;
 import androidx.test.filters.SmallTest;
 import androidx.test.platform.app.InstrumentationRegistry;
 
@@ -189,31 +191,23 @@
         }
 
         for (int type: testTypes) {
-            boolean[] backNavigationDone = new boolean[]{false};
-            boolean[] triggerBack = new boolean[]{false};
-
+            final ResultListener result  = new ResultListener();
             createNavigationInfo(new BackNavigationInfo.Builder()
                     .setType(type)
                     .setOnBackInvokedCallback(mAppCallback)
                     .setPrepareRemoteAnimation(true)
-                    .setOnBackNavigationDone(
-                            new RemoteCallback(result -> {
-                                backNavigationDone[0] = true;
-                                triggerBack[0] = result.getBoolean(KEY_TRIGGER_BACK);
-                            })));
+                    .setOnBackNavigationDone(new RemoteCallback(result)));
             triggerBackGesture();
             simulateRemoteAnimationStart(type);
             simulateRemoteAnimationFinished();
             mShellExecutor.flushAll();
 
             assertTrue("Navigation Done callback not called for "
-                    + BackNavigationInfo.typeToString(type), backNavigationDone[0]);
-            assertTrue("TriggerBack should have been true", triggerBack[0]);
+                    + BackNavigationInfo.typeToString(type), result.mBackNavigationDone);
+            assertTrue("TriggerBack should have been true", result.mTriggerBack);
         }
     }
 
-
-
     @Test
     public void backToHome_dispatchesEvents() throws RemoteException {
         registerAnimation(BackNavigationInfo.TYPE_RETURN_TO_HOME);
@@ -351,6 +345,65 @@
         verify(mAnimatorCallback, never()).onBackInvoked();
     }
 
+    @Test
+    public void animationNotDefined() throws RemoteException {
+        final int[] testTypes = new int[] {
+                BackNavigationInfo.TYPE_RETURN_TO_HOME,
+                BackNavigationInfo.TYPE_CROSS_TASK,
+                BackNavigationInfo.TYPE_CROSS_ACTIVITY,
+                BackNavigationInfo.TYPE_DIALOG_CLOSE};
+
+        for (int type: testTypes) {
+            final ResultListener result = new ResultListener();
+            createNavigationInfo(new BackNavigationInfo.Builder()
+                    .setType(type)
+                    .setOnBackInvokedCallback(mAppCallback)
+                    .setPrepareRemoteAnimation(true)
+                    .setOnBackNavigationDone(new RemoteCallback(result)));
+            triggerBackGesture();
+            simulateRemoteAnimationStart(type);
+            mShellExecutor.flushAll();
+
+            assertTrue("Navigation Done callback not called for "
+                    + BackNavigationInfo.typeToString(type), result.mBackNavigationDone);
+            assertTrue("TriggerBack should have been true", result.mTriggerBack);
+        }
+
+        verify(mAppCallback, never()).onBackStarted(any());
+        verify(mAppCallback, never()).onBackProgressed(any());
+        verify(mAppCallback, times(testTypes.length)).onBackInvoked();
+
+        verify(mAnimatorCallback, never()).onBackStarted(any());
+        verify(mAnimatorCallback, never()).onBackProgressed(any());
+        verify(mAnimatorCallback, never()).onBackInvoked();
+    }
+
+    @Test
+    public void callbackShouldDeliverProgress() throws RemoteException {
+        registerAnimation(BackNavigationInfo.TYPE_RETURN_TO_HOME);
+
+        final int type = BackNavigationInfo.TYPE_CALLBACK;
+        final ResultListener result = new ResultListener();
+        createNavigationInfo(new BackNavigationInfo.Builder()
+                .setType(type)
+                .setOnBackInvokedCallback(mAppCallback)
+                .setOnBackNavigationDone(new RemoteCallback(result)));
+        triggerBackGesture();
+        mShellExecutor.flushAll();
+
+        assertTrue("Navigation Done callback not called for "
+                + BackNavigationInfo.typeToString(type), result.mBackNavigationDone);
+        assertTrue("TriggerBack should have been true", result.mTriggerBack);
+
+        verify(mAppCallback, times(1)).onBackStarted(any());
+        verify(mAppCallback, times(1)).onBackProgressed(any());
+        verify(mAppCallback, times(1)).onBackInvoked();
+
+        verify(mAnimatorCallback, never()).onBackStarted(any());
+        verify(mAnimatorCallback, never()).onBackProgressed(any());
+        verify(mAnimatorCallback, never()).onBackInvoked();
+    }
+
     private void doMotionEvent(int actionDown, int coordinate) {
         mController.onMotionEvent(
                 coordinate, coordinate,
@@ -377,4 +430,14 @@
         mController.registerAnimation(type,
                 new BackAnimationRunner(mAnimatorCallback, mBackAnimationRunner));
     }
+
+    private static class ResultListener implements RemoteCallback.OnResultListener {
+        boolean mBackNavigationDone = false;
+        boolean mTriggerBack = false;
+        @Override
+        public void onResult(@Nullable Bundle result) {
+            mBackNavigationDone = true;
+            mTriggerBack = result.getBoolean(KEY_TRIGGER_BACK);
+        }
+    };
 }
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/BackgroundWindowManagerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/BackgroundWindowManagerTest.java
index 11948dbf..f3f7067 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/BackgroundWindowManagerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/BackgroundWindowManagerTest.java
@@ -24,7 +24,6 @@
 import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.SmallTest;
 
-import com.android.wm.shell.RootDisplayAreaOrganizer;
 import com.android.wm.shell.ShellTestCase;
 import com.android.wm.shell.common.DisplayLayout;
 
@@ -42,13 +41,11 @@
     private BackgroundWindowManager mBackgroundWindowManager;
     @Mock
     private DisplayLayout  mMockDisplayLayout;
-    @Mock
-    private RootDisplayAreaOrganizer mRootDisplayAreaOrganizer;
 
     @Before
     public void setup() {
         MockitoAnnotations.initMocks(this);
-        mBackgroundWindowManager = new BackgroundWindowManager(mContext, mRootDisplayAreaOrganizer);
+        mBackgroundWindowManager = new BackgroundWindowManager(mContext);
         mBackgroundWindowManager.onDisplayChanged(mMockDisplayLayout);
     }
 
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageTaskListenerTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageTaskListenerTests.java
index 1a1bebd..5ee8bf3 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageTaskListenerTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageTaskListenerTests.java
@@ -61,7 +61,7 @@
 @RunWith(AndroidJUnit4.class)
 public final class StageTaskListenerTests extends ShellTestCase {
     private static final boolean ENABLE_SHELL_TRANSITIONS =
-            SystemProperties.getBoolean("persist.wm.debug.shell_transit", true);
+            SystemProperties.getBoolean("persist.wm.debug.shell_transit", false);
 
     @Mock
     private ShellTaskOrganizer mTaskOrganizer;
diff --git a/libs/androidfw/LocaleDataTables.cpp b/libs/androidfw/LocaleDataTables.cpp
index b3fb145..b68143d 100644
--- a/libs/androidfw/LocaleDataTables.cpp
+++ b/libs/androidfw/LocaleDataTables.cpp
@@ -143,6 +143,7 @@
     {0xACE00000u, 46u}, // ahl -> Latn
     {0xB8E00000u,  1u}, // aho -> Ahom
     {0x99200000u, 46u}, // ajg -> Latn
+    {0xCD200000u,  2u}, // ajt -> Arab
     {0x616B0000u, 46u}, // ak -> Latn
     {0xA9400000u, 101u}, // akk -> Xsux
     {0x81600000u, 46u}, // ala -> Latn
@@ -1053,6 +1054,7 @@
     {0xB70D0000u, 46u}, // nyn -> Latn
     {0xA32D0000u, 46u}, // nzi -> Latn
     {0x6F630000u, 46u}, // oc -> Latn
+    {0x6F634553u, 46u}, // oc-ES -> Latn
     {0x88CE0000u, 46u}, // ogc -> Latn
     {0x6F6A0000u, 11u}, // oj -> Cans
     {0xC92E0000u, 11u}, // ojs -> Cans
@@ -1093,6 +1095,7 @@
     {0xB4EF0000u, 71u}, // phn -> Phnx
     {0xAD0F0000u, 46u}, // pil -> Latn
     {0xBD0F0000u, 46u}, // pip -> Latn
+    {0xC90F0000u, 46u}, // pis -> Latn
     {0x814F0000u,  9u}, // pka -> Brah
     {0xB94F0000u, 46u}, // pko -> Latn
     {0x706C0000u, 46u}, // pl -> Latn
@@ -1204,12 +1207,14 @@
     {0xE1720000u, 46u}, // sly -> Latn
     {0x736D0000u, 46u}, // sm -> Latn
     {0x81920000u, 46u}, // sma -> Latn
+    {0x8D920000u, 46u}, // smd -> Latn
     {0xA5920000u, 46u}, // smj -> Latn
     {0xB5920000u, 46u}, // smn -> Latn
     {0xBD920000u, 76u}, // smp -> Samr
     {0xC1920000u, 46u}, // smq -> Latn
     {0xC9920000u, 46u}, // sms -> Latn
     {0x736E0000u, 46u}, // sn -> Latn
+    {0x85B20000u, 46u}, // snb -> Latn
     {0x89B20000u, 46u}, // snc -> Latn
     {0xA9B20000u, 46u}, // snk -> Latn
     {0xBDB20000u, 46u}, // snp -> Latn
@@ -1314,6 +1319,7 @@
     {0x746F0000u, 46u}, // to -> Latn
     {0x95D30000u, 46u}, // tof -> Latn
     {0x99D30000u, 46u}, // tog -> Latn
+    {0xA9D30000u, 46u}, // tok -> Latn
     {0xC1D30000u, 46u}, // toq -> Latn
     {0xA1F30000u, 46u}, // tpi -> Latn
     {0xB1F30000u, 46u}, // tpm -> Latn
@@ -1527,6 +1533,7 @@
     0x61665A414C61746ELLU, // af_Latn_ZA
     0xC0C0434D4C61746ELLU, // agq_Latn_CM
     0xB8E0494E41686F6DLLU, // aho_Ahom_IN
+    0xCD20544E41726162LLU, // ajt_Arab_TN
     0x616B47484C61746ELLU, // ak_Latn_GH
     0xA940495158737578LLU, // akk_Xsux_IQ
     0xB560584B4C61746ELLU, // aln_Latn_XK
@@ -1534,6 +1541,7 @@
     0x616D455445746869LLU, // am_Ethi_ET
     0xB9804E474C61746ELLU, // amo_Latn_NG
     0x616E45534C61746ELLU, // an_Latn_ES
+    0xB5A04E474C61746ELLU, // ann_Latn_NG
     0xE5C049444C61746ELLU, // aoz_Latn_ID
     0x8DE0544741726162LLU, // apd_Arab_TG
     0x6172454741726162LLU, // ar_Arab_EG
@@ -2039,6 +2047,7 @@
     0xB88F49525870656FLLU, // peo_Xpeo_IR
     0xACAF44454C61746ELLU, // pfl_Latn_DE
     0xB4EF4C4250686E78LLU, // phn_Phnx_LB
+    0xC90F53424C61746ELLU, // pis_Latn_SB
     0x814F494E42726168LLU, // pka_Brah_IN
     0xB94F4B454C61746ELLU, // pko_Latn_KE
     0x706C504C4C61746ELLU, // pl_Latn_PL
@@ -2119,11 +2128,13 @@
     0xE17249444C61746ELLU, // sly_Latn_ID
     0x736D57534C61746ELLU, // sm_Latn_WS
     0x819253454C61746ELLU, // sma_Latn_SE
+    0x8D92414F4C61746ELLU, // smd_Latn_AO
     0xA59253454C61746ELLU, // smj_Latn_SE
     0xB59246494C61746ELLU, // smn_Latn_FI
     0xBD92494C53616D72LLU, // smp_Samr_IL
     0xC99246494C61746ELLU, // sms_Latn_FI
     0x736E5A574C61746ELLU, // sn_Latn_ZW
+    0x85B24D594C61746ELLU, // snb_Latn_MY
     0xA9B24D4C4C61746ELLU, // snk_Latn_ML
     0x736F534F4C61746ELLU, // so_Latn_SO
     0x99D2555A536F6764LLU, // sog_Sogd_UZ
diff --git a/location/java/android/location/GnssCapabilities.java b/location/java/android/location/GnssCapabilities.java
index 4f45602..a6da0a3 100644
--- a/location/java/android/location/GnssCapabilities.java
+++ b/location/java/android/location/GnssCapabilities.java
@@ -25,6 +25,7 @@
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 import java.util.ArrayList;
+import java.util.Collections;
 import java.util.List;
 import java.util.Objects;
 import java.util.concurrent.Executor;
@@ -125,7 +126,7 @@
      * @hide
      */
     public static GnssCapabilities empty() {
-        return new GnssCapabilities(0, 0, 0, new ArrayList<>());
+        return new GnssCapabilities(0, 0, 0, Collections.emptyList());
     }
 
     private final @TopHalCapabilityFlags int mTopFlags;
@@ -142,7 +143,7 @@
         mTopFlags = topFlags;
         mMeasurementCorrectionsFlags = measurementCorrectionsFlags;
         mPowerFlags = powerFlags;
-        mGnssSignalTypes = gnssSignalTypes;
+        mGnssSignalTypes = Collections.unmodifiableList(gnssSignalTypes);
     }
 
     /**
@@ -155,7 +156,7 @@
             return this;
         } else {
             return new GnssCapabilities(flags, mMeasurementCorrectionsFlags, mPowerFlags,
-                    new ArrayList<>(mGnssSignalTypes));
+                    mGnssSignalTypes);
         }
     }
 
@@ -171,7 +172,7 @@
             return this;
         } else {
             return new GnssCapabilities(mTopFlags, flags, mPowerFlags,
-                    new ArrayList<>(mGnssSignalTypes));
+                    mGnssSignalTypes);
         }
     }
 
@@ -186,7 +187,7 @@
             return this;
         } else {
             return new GnssCapabilities(mTopFlags, mMeasurementCorrectionsFlags, flags,
-                    new ArrayList<>(mGnssSignalTypes));
+                    mGnssSignalTypes);
         }
     }
 
@@ -606,7 +607,7 @@
             mTopFlags = 0;
             mMeasurementCorrectionsFlags = 0;
             mPowerFlags = 0;
-            mGnssSignalTypes = new ArrayList<>();
+            mGnssSignalTypes = Collections.emptyList();
         }
 
         public Builder(@NonNull GnssCapabilities capabilities) {
diff --git a/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java
index d975e96..17d7045 100644
--- a/media/java/android/media/AudioManager.java
+++ b/media/java/android/media/AudioManager.java
@@ -2328,10 +2328,9 @@
         return AudioSystem.SUCCESS;
     }
 
-    private final Map<Integer, Object> mDevRoleForCapturePresetListeners = new HashMap<>(){{
-            put(AudioSystem.DEVICE_ROLE_PREFERRED,
-                    new DevRoleListeners<OnPreferredDevicesForCapturePresetChangedListener>());
-        }};
+    private final Map<Integer, Object> mDevRoleForCapturePresetListeners = Map.of(
+            AudioSystem.DEVICE_ROLE_PREFERRED,
+            new DevRoleListeners<OnPreferredDevicesForCapturePresetChangedListener>());
 
     private class DevRoleListenerInfo<T> {
         final @NonNull Executor mExecutor;
@@ -6515,15 +6514,17 @@
     // AudioPort implementation
     //
 
-    static final int AUDIOPORT_GENERATION_INIT = 0;
-    static Integer sAudioPortGeneration = new Integer(AUDIOPORT_GENERATION_INIT);
-    static ArrayList<AudioPort> sAudioPortsCached = new ArrayList<AudioPort>();
-    static ArrayList<AudioPort> sPreviousAudioPortsCached = new ArrayList<AudioPort>();
-    static ArrayList<AudioPatch> sAudioPatchesCached = new ArrayList<AudioPatch>();
+    private static final int AUDIOPORT_GENERATION_INIT = 0;
+    private static Object sAudioPortGenerationLock = new Object();
+    @GuardedBy("sAudioPortGenerationLock")
+    private static int sAudioPortGeneration = AUDIOPORT_GENERATION_INIT;
+    private static ArrayList<AudioPort> sAudioPortsCached = new ArrayList<AudioPort>();
+    private static ArrayList<AudioPort> sPreviousAudioPortsCached = new ArrayList<AudioPort>();
+    private static ArrayList<AudioPatch> sAudioPatchesCached = new ArrayList<AudioPatch>();
 
     static int resetAudioPortGeneration() {
         int generation;
-        synchronized (sAudioPortGeneration) {
+        synchronized (sAudioPortGenerationLock) {
             generation = sAudioPortGeneration;
             sAudioPortGeneration = AUDIOPORT_GENERATION_INIT;
         }
@@ -6533,7 +6534,7 @@
     static int updateAudioPortCache(ArrayList<AudioPort> ports, ArrayList<AudioPatch> patches,
                                     ArrayList<AudioPort> previousPorts) {
         sAudioPortEventHandler.init();
-        synchronized (sAudioPortGeneration) {
+        synchronized (sAudioPortGenerationLock) {
 
             if (sAudioPortGeneration == AUDIOPORT_GENERATION_INIT) {
                 int[] patchGeneration = new int[1];
diff --git a/media/java/android/media/AudioMetadata.java b/media/java/android/media/AudioMetadata.java
index ca175b4..0f962f9 100644
--- a/media/java/android/media/AudioMetadata.java
+++ b/media/java/android/media/AudioMetadata.java
@@ -30,6 +30,7 @@
 import java.nio.charset.StandardCharsets;
 import java.util.HashMap;
 import java.util.HashSet;
+import java.util.Map;
 import java.util.Objects;
 import java.util.Set;
 
@@ -446,14 +447,13 @@
     // BaseMap is corresponding to audio_utils::metadata::Data
     private static final int AUDIO_METADATA_OBJ_TYPE_BASEMAP = 6;
 
-    private static final HashMap<Class, Integer> AUDIO_METADATA_OBJ_TYPES = new HashMap<>() {{
-            put(Integer.class, AUDIO_METADATA_OBJ_TYPE_INT);
-            put(Long.class, AUDIO_METADATA_OBJ_TYPE_LONG);
-            put(Float.class, AUDIO_METADATA_OBJ_TYPE_FLOAT);
-            put(Double.class, AUDIO_METADATA_OBJ_TYPE_DOUBLE);
-            put(String.class, AUDIO_METADATA_OBJ_TYPE_STRING);
-            put(BaseMap.class, AUDIO_METADATA_OBJ_TYPE_BASEMAP);
-        }};
+    private static final Map<Class, Integer> AUDIO_METADATA_OBJ_TYPES = Map.of(
+            Integer.class, AUDIO_METADATA_OBJ_TYPE_INT,
+            Long.class, AUDIO_METADATA_OBJ_TYPE_LONG,
+            Float.class, AUDIO_METADATA_OBJ_TYPE_FLOAT,
+            Double.class, AUDIO_METADATA_OBJ_TYPE_DOUBLE,
+            String.class, AUDIO_METADATA_OBJ_TYPE_STRING,
+            BaseMap.class, AUDIO_METADATA_OBJ_TYPE_BASEMAP);
 
     private static final Charset AUDIO_METADATA_CHARSET = StandardCharsets.UTF_8;
 
@@ -634,8 +634,8 @@
      *     Datum corresponds to Object
      ****************************************************************************************/
 
-    private static final HashMap<Integer, DataPackage<?>> DATA_PACKAGES = new HashMap<>() {{
-            put(AUDIO_METADATA_OBJ_TYPE_INT, new DataPackage<Integer>() {
+    private static final Map<Integer, DataPackage<?>> DATA_PACKAGES = Map.of(
+            AUDIO_METADATA_OBJ_TYPE_INT, new DataPackage<Integer>() {
                 @Override
                 @Nullable
                 public Integer unpack(ByteBuffer buffer) {
@@ -647,8 +647,8 @@
                     output.putInt(obj);
                     return true;
                 }
-            });
-            put(AUDIO_METADATA_OBJ_TYPE_LONG, new DataPackage<Long>() {
+            },
+            AUDIO_METADATA_OBJ_TYPE_LONG, new DataPackage<Long>() {
                 @Override
                 @Nullable
                 public Long unpack(ByteBuffer buffer) {
@@ -660,8 +660,8 @@
                     output.putLong(obj);
                     return true;
                 }
-            });
-            put(AUDIO_METADATA_OBJ_TYPE_FLOAT, new DataPackage<Float>() {
+            },
+            AUDIO_METADATA_OBJ_TYPE_FLOAT, new DataPackage<Float>() {
                 @Override
                 @Nullable
                 public Float unpack(ByteBuffer buffer) {
@@ -673,8 +673,8 @@
                     output.putFloat(obj);
                     return true;
                 }
-            });
-            put(AUDIO_METADATA_OBJ_TYPE_DOUBLE, new DataPackage<Double>() {
+            },
+            AUDIO_METADATA_OBJ_TYPE_DOUBLE, new DataPackage<Double>() {
                 @Override
                 @Nullable
                 public Double unpack(ByteBuffer buffer) {
@@ -686,8 +686,8 @@
                     output.putDouble(obj);
                     return true;
                 }
-            });
-            put(AUDIO_METADATA_OBJ_TYPE_STRING, new DataPackage<String>() {
+            },
+            AUDIO_METADATA_OBJ_TYPE_STRING, new DataPackage<String>() {
                 @Override
                 @Nullable
                 public String unpack(ByteBuffer buffer) {
@@ -713,9 +713,9 @@
                     output.put(valueArr);
                     return true;
                 }
-            });
-            put(AUDIO_METADATA_OBJ_TYPE_BASEMAP, new BaseMapPackage());
-        }};
+            },
+            AUDIO_METADATA_OBJ_TYPE_BASEMAP, new BaseMapPackage());
+
     // ObjectPackage is a special case that it is expected to unpack audio_utils::metadata::Datum,
     // which contains data type and data size besides the payload for the data.
     private static final ObjectPackage OBJECT_PACKAGE = new ObjectPackage();
diff --git a/media/java/android/media/AudioTrack.java b/media/java/android/media/AudioTrack.java
index 85cd342..d51f1e1 100644
--- a/media/java/android/media/AudioTrack.java
+++ b/media/java/android/media/AudioTrack.java
@@ -48,8 +48,8 @@
 import java.nio.ByteBuffer;
 import java.nio.ByteOrder;
 import java.nio.NioUtils;
-import java.util.HashMap;
 import java.util.LinkedList;
+import java.util.Map;
 import java.util.Objects;
 import java.util.concurrent.Executor;
 
@@ -1867,26 +1867,24 @@
     }
 
     // General pair map
-    private static final HashMap<String, Integer> CHANNEL_PAIR_MAP = new HashMap<>() {{
-        put("front", AudioFormat.CHANNEL_OUT_FRONT_LEFT
-                | AudioFormat.CHANNEL_OUT_FRONT_RIGHT);
-        put("back", AudioFormat.CHANNEL_OUT_BACK_LEFT
-                | AudioFormat.CHANNEL_OUT_BACK_RIGHT);
-        put("front of center", AudioFormat.CHANNEL_OUT_FRONT_LEFT_OF_CENTER
-                | AudioFormat.CHANNEL_OUT_FRONT_RIGHT_OF_CENTER);
-        put("side", AudioFormat.CHANNEL_OUT_SIDE_LEFT
-                | AudioFormat.CHANNEL_OUT_SIDE_RIGHT);
-        put("top front", AudioFormat.CHANNEL_OUT_TOP_FRONT_LEFT
-                | AudioFormat.CHANNEL_OUT_TOP_FRONT_RIGHT);
-        put("top back", AudioFormat.CHANNEL_OUT_TOP_BACK_LEFT
-                | AudioFormat.CHANNEL_OUT_TOP_BACK_RIGHT);
-        put("top side", AudioFormat.CHANNEL_OUT_TOP_SIDE_LEFT
-                | AudioFormat.CHANNEL_OUT_TOP_SIDE_RIGHT);
-        put("bottom front", AudioFormat.CHANNEL_OUT_BOTTOM_FRONT_LEFT
-                | AudioFormat.CHANNEL_OUT_BOTTOM_FRONT_RIGHT);
-        put("front wide", AudioFormat.CHANNEL_OUT_FRONT_WIDE_LEFT
-                | AudioFormat.CHANNEL_OUT_FRONT_WIDE_RIGHT);
-    }};
+    private static final Map<String, Integer> CHANNEL_PAIR_MAP = Map.of(
+            "front", AudioFormat.CHANNEL_OUT_FRONT_LEFT
+                    | AudioFormat.CHANNEL_OUT_FRONT_RIGHT,
+            "back", AudioFormat.CHANNEL_OUT_BACK_LEFT
+                    | AudioFormat.CHANNEL_OUT_BACK_RIGHT,
+            "front of center", AudioFormat.CHANNEL_OUT_FRONT_LEFT_OF_CENTER
+                    | AudioFormat.CHANNEL_OUT_FRONT_RIGHT_OF_CENTER,
+            "side", AudioFormat.CHANNEL_OUT_SIDE_LEFT | AudioFormat.CHANNEL_OUT_SIDE_RIGHT,
+            "top front", AudioFormat.CHANNEL_OUT_TOP_FRONT_LEFT
+                    | AudioFormat.CHANNEL_OUT_TOP_FRONT_RIGHT,
+            "top back", AudioFormat.CHANNEL_OUT_TOP_BACK_LEFT
+                    | AudioFormat.CHANNEL_OUT_TOP_BACK_RIGHT,
+            "top side", AudioFormat.CHANNEL_OUT_TOP_SIDE_LEFT
+                    | AudioFormat.CHANNEL_OUT_TOP_SIDE_RIGHT,
+            "bottom front", AudioFormat.CHANNEL_OUT_BOTTOM_FRONT_LEFT
+                    | AudioFormat.CHANNEL_OUT_BOTTOM_FRONT_RIGHT,
+            "front wide", AudioFormat.CHANNEL_OUT_FRONT_WIDE_LEFT
+                    | AudioFormat.CHANNEL_OUT_FRONT_WIDE_RIGHT);
 
     /**
      * Convenience method to check that the channel configuration (a.k.a channel mask) is supported
@@ -1924,7 +1922,7 @@
                 return false;
         }
         // Check all pairs to see that they are matched (front duplicated here).
-        for (HashMap.Entry<String, Integer> e : CHANNEL_PAIR_MAP.entrySet()) {
+        for (Map.Entry<String, Integer> e : CHANNEL_PAIR_MAP.entrySet()) {
             final int positionPair = e.getValue();
             if ((channelConfig & positionPair) != 0
                     && (channelConfig & positionPair) != positionPair) {
diff --git a/media/java/android/media/ExifInterface.java b/media/java/android/media/ExifInterface.java
index 0c8cacd..524bde4 100644
--- a/media/java/android/media/ExifInterface.java
+++ b/media/java/android/media/ExifInterface.java
@@ -70,6 +70,7 @@
 import java.util.HashSet;
 import java.util.Locale;
 import java.util.Map;
+import java.util.Objects;
 import java.util.Set;
 import java.util.TimeZone;
 import java.util.regex.Matcher;
@@ -4836,12 +4837,13 @@
             for (int i = 1; i < entryValues.length; ++i) {
                 final Pair<Integer, Integer> guessDataFormat = guessDataFormat(entryValues[i]);
                 int first = -1, second = -1;
-                if (guessDataFormat.first == dataFormat.first
-                        || guessDataFormat.second == dataFormat.first) {
+                if (Objects.equals(guessDataFormat.first, dataFormat.first)
+                        || Objects.equals(guessDataFormat.second, dataFormat.first)) {
                     first = dataFormat.first;
                 }
-                if (dataFormat.second != -1 && (guessDataFormat.first == dataFormat.second
-                        || guessDataFormat.second == dataFormat.second)) {
+                if (dataFormat.second != -1
+                        && (Objects.equals(guessDataFormat.first, dataFormat.second)
+                        || Objects.equals(guessDataFormat.second, dataFormat.second))) {
                     second = dataFormat.second;
                 }
                 if (first == -1 && second == -1) {
diff --git a/media/java/android/media/MediaHTTPService.java b/media/java/android/media/MediaHTTPService.java
index 3008067..2342a42 100644
--- a/media/java/android/media/MediaHTTPService.java
+++ b/media/java/android/media/MediaHTTPService.java
@@ -21,6 +21,8 @@
 import android.os.IBinder;
 import android.util.Log;
 
+import com.android.internal.annotations.GuardedBy;
+
 import java.net.CookieHandler;
 import java.net.CookieManager;
 import java.net.CookieStore;
@@ -31,7 +33,9 @@
 public class MediaHTTPService extends IMediaHTTPService.Stub {
     private static final String TAG = "MediaHTTPService";
     @Nullable private List<HttpCookie> mCookies;
-    private Boolean mCookieStoreInitialized = new Boolean(false);
+    private final Object mCookieStoreInitializedLock = new Object();
+    @GuardedBy("mCookieStoreInitializedLock")
+    private boolean mCookieStoreInitialized = false;
 
     public MediaHTTPService(@Nullable List<HttpCookie> cookies) {
         mCookies = cookies;
@@ -40,7 +44,7 @@
 
     public IMediaHTTPConnection makeHTTPConnection() {
 
-        synchronized (mCookieStoreInitialized) {
+        synchronized (mCookieStoreInitializedLock) {
             // Only need to do it once for all connections
             if ( !mCookieStoreInitialized )  {
                 CookieHandler cookieHandler = CookieHandler.getDefault();
@@ -78,8 +82,8 @@
 
                 Log.v(TAG, "makeHTTPConnection(" + this + "): cookieHandler: " + cookieHandler +
                         " Cookies: " + mCookies);
-            }   // mCookieStoreInitialized
-        }   // synchronized
+            }
+        }
 
         return new MediaHTTPConnection();
     }
diff --git a/media/java/android/media/MediaPlayer.java b/media/java/android/media/MediaPlayer.java
index 77b5746..79a5902 100644
--- a/media/java/android/media/MediaPlayer.java
+++ b/media/java/android/media/MediaPlayer.java
@@ -2507,6 +2507,8 @@
      *
      * @see android.media.MediaPlayer#getTrackInfo
      */
+    // The creator needs to be pulic, which requires removing the @UnsupportedAppUsage
+    @SuppressWarnings("ParcelableCreator")
     static public class TrackInfo implements Parcelable {
         /**
          * Gets the track type.
diff --git a/media/java/android/media/tv/interactive/ITvInteractiveAppManager.aidl b/media/java/android/media/tv/interactive/ITvInteractiveAppManager.aidl
index 84b9c9e..38fc717 100644
--- a/media/java/android/media/tv/interactive/ITvInteractiveAppManager.aidl
+++ b/media/java/android/media/tv/interactive/ITvInteractiveAppManager.aidl
@@ -64,6 +64,7 @@
     void notifyContentBlocked(in IBinder sessionToken, in String rating, int userId);
     void notifySignalStrength(in IBinder sessionToken, int stength, int userId);
     void notifyRecordingStarted(in IBinder sessionToken, in String recordingId, int userId);
+    void notifyRecordingStopped(in IBinder sessionToken, in String recordingId, int userId);
     void setSurface(in IBinder sessionToken, in Surface surface, int userId);
     void dispatchSurfaceChanged(in IBinder sessionToken, int format, int width, int height,
             int userId);
diff --git a/media/java/android/media/tv/interactive/ITvInteractiveAppSession.aidl b/media/java/android/media/tv/interactive/ITvInteractiveAppSession.aidl
index 95b4ffa..9e33536 100644
--- a/media/java/android/media/tv/interactive/ITvInteractiveAppSession.aidl
+++ b/media/java/android/media/tv/interactive/ITvInteractiveAppSession.aidl
@@ -54,6 +54,7 @@
     void notifyContentBlocked(in String rating);
     void notifySignalStrength(int strength);
     void notifyRecordingStarted(in String recordingId);
+    void notifyRecordingStopped(in String recordingId);
     void setSurface(in Surface surface);
     void dispatchSurfaceChanged(int format, int width, int height);
     void notifyBroadcastInfoResponse(in BroadcastInfoResponse response);
diff --git a/media/java/android/media/tv/interactive/ITvInteractiveAppSessionWrapper.java b/media/java/android/media/tv/interactive/ITvInteractiveAppSessionWrapper.java
index 042cb15..a2fdfe0 100644
--- a/media/java/android/media/tv/interactive/ITvInteractiveAppSessionWrapper.java
+++ b/media/java/android/media/tv/interactive/ITvInteractiveAppSessionWrapper.java
@@ -82,6 +82,7 @@
     private static final int DO_RELAYOUT_MEDIA_VIEW = 28;
     private static final int DO_REMOVE_MEDIA_VIEW = 29;
     private static final int DO_NOTIFY_RECORDING_STARTED = 30;
+    private static final int DO_NOTIFY_RECORDING_STOPPED = 31;
 
     private final HandlerCaller mCaller;
     private Session mSessionImpl;
@@ -169,6 +170,10 @@
                 mSessionImpl.notifyRecordingStarted((String) msg.obj);
                 break;
             }
+            case DO_NOTIFY_RECORDING_STOPPED: {
+                mSessionImpl.notifyRecordingStopped((String) msg.obj);
+                break;
+            }
             case DO_SEND_SIGNING_RESULT: {
                 SomeArgs args = (SomeArgs) msg.obj;
                 mSessionImpl.sendSigningResult((String) args.arg1, (byte[]) args.arg2);
@@ -392,6 +397,12 @@
     }
 
     @Override
+    public void notifyRecordingStopped(String recordingId) {
+        mCaller.executeOrSendMessage(mCaller.obtainMessageO(
+                DO_NOTIFY_RECORDING_STOPPED, recordingId));
+    }
+
+    @Override
     public void setSurface(Surface surface) {
         mCaller.executeOrSendMessage(mCaller.obtainMessageO(DO_SET_SURFACE, surface));
     }
diff --git a/media/java/android/media/tv/interactive/TvInteractiveAppManager.java b/media/java/android/media/tv/interactive/TvInteractiveAppManager.java
index 0f11407..287df40 100755
--- a/media/java/android/media/tv/interactive/TvInteractiveAppManager.java
+++ b/media/java/android/media/tv/interactive/TvInteractiveAppManager.java
@@ -1071,6 +1071,18 @@
             }
         }
 
+        void notifyRecordingStopped(String recordingId) {
+            if (mToken == null) {
+                Log.w(TAG, "The session has been already released");
+                return;
+            }
+            try {
+                mService.notifyRecordingStopped(mToken, recordingId, mUserId);
+            } catch (RemoteException e) {
+                throw e.rethrowFromSystemServer();
+            }
+        }
+
         void sendSigningResult(@NonNull String signingId, @NonNull byte[] result) {
             if (mToken == null) {
                 Log.w(TAG, "The session has been already released");
diff --git a/media/java/android/media/tv/interactive/TvInteractiveAppService.java b/media/java/android/media/tv/interactive/TvInteractiveAppService.java
index 9ef6503..90eed9e 100755
--- a/media/java/android/media/tv/interactive/TvInteractiveAppService.java
+++ b/media/java/android/media/tv/interactive/TvInteractiveAppService.java
@@ -463,6 +463,16 @@
         }
 
         /**
+         * Receives stopped recording's ID.
+         *
+         * @param recordingId The ID of the recording stopped
+         * @hide
+         */
+        public void onRecordingStopped(@NonNull String recordingId) {
+        }
+
+
+        /**
          * Receives signing result.
          * @param signingId the ID to identify the request. It's the same as the corresponding ID in
          *        {@link Session#requestSigning(String, String, String, byte[])}
@@ -1178,11 +1188,21 @@
             onAdResponse(response);
         }
 
+        /**
+         * Calls {@link #onRecordingStarted(String)}.
+         */
         void notifyRecordingStarted(String recordingId) {
             onRecordingStarted(recordingId);
         }
 
         /**
+         * Calls {@link #onRecordingStopped(String)}.
+         */
+        void notifyRecordingStopped(String recordingId) {
+            onRecordingStopped(recordingId);
+        }
+
+        /**
          * Notifies when the session state is changed.
          *
          * @param state the current session state.
diff --git a/media/java/android/media/tv/interactive/TvInteractiveAppView.java b/media/java/android/media/tv/interactive/TvInteractiveAppView.java
index c21b288..fcd781b 100755
--- a/media/java/android/media/tv/interactive/TvInteractiveAppView.java
+++ b/media/java/android/media/tv/interactive/TvInteractiveAppView.java
@@ -581,9 +581,10 @@
     }
 
     /**
-     * Alerts the TV interactive app that a recording has been started with recordingId
+     * Alerts the TV interactive app that a recording has been started.
      *
-     * @param recordingId The ID of the recording started
+     * @param recordingId The ID of the recording started. This ID is created and maintained by the
+     *                    TV app and is used to identify the recording in the future.
      */
     public void notifyRecordingStarted(@NonNull String recordingId) {
         if (DEBUG) {
@@ -595,6 +596,23 @@
     }
 
     /**
+     * Alerts the TV interactive app that a recording has been stopped.
+     *
+     * @param recordingId The ID of the recording stopped. This ID is created and maintained
+     *                    by the TV app when a recording is started.
+     * @see TvInteractiveAppView#notifyRecordingStarted(String)
+     * @hide
+     */
+    public void notifyRecordingStopped(@NonNull String recordingId) {
+        if (DEBUG) {
+            Log.d(TAG, "notifyRecordingStopped");
+        }
+        if (mSession != null) {
+            mSession.notifyRecordingStopped(recordingId);
+        }
+    }
+
+    /**
      * Sends signing result to related TV interactive app.
      *
      * <p>This is used when the corresponding server of the broadcast-independent interactive
diff --git a/media/java/android/media/tv/tuner/Tuner.java b/media/java/android/media/tv/tuner/Tuner.java
index 51b976b..fab63aa 100644
--- a/media/java/android/media/tv/tuner/Tuner.java
+++ b/media/java/android/media/tv/tuner/Tuner.java
@@ -2376,19 +2376,20 @@
     }
 
     /**
-     * Request a frontend by frontend id.
+     * Request a frontend by frontend info.
      *
      * <p> This API is used if the applications want to select a desired frontend before
      * {@link tune} to use a specific satellite or sending SatCR DiSEqC command for {@link tune}.
      *
-     * @param desiredId the desired fronted Id. It can be retrieved by
+     * @param desiredFrontendInfo the FrontendInfo of the desired fronted. It can be retrieved by
      * {@link getAvailableFrontendInfos}
      *
      * @return result status of open operation.
      * @throws SecurityException if the caller does not have appropriate permissions.
      */
     @Result
-    public int requestFrontendById(int desiredId) {
+    public int applyFrontend(@NonNull FrontendInfo desiredFrontendInfo) {
+        Objects.requireNonNull(desiredFrontendInfo, "desiredFrontendInfo must not be null");
         mFrontendLock.lock();
         try {
             if (mFeOwnerTuner != null) {
@@ -2399,17 +2400,12 @@
                 Log.e(TAG, "A frontend has been opened before");
                 return RESULT_INVALID_STATE;
             }
-            FrontendInfo frontendInfo = getFrontendInfoById(desiredId);
-            if (frontendInfo == null) {
-                Log.e(TAG, "Failed to get a FrontendInfo by frontend id: " + desiredId);
-                return RESULT_UNAVAILABLE;
-            }
-            int frontendType = frontendInfo.getType();
+            mFrontendType = desiredFrontendInfo.getType();
+            mDesiredFrontendId = desiredFrontendInfo.getId();
             if (DEBUG) {
-                Log.d(TAG, "Opening frontend with type " + frontendType + ", id " + desiredId);
+                Log.d(TAG, "Applying frontend with type " + mFrontendType + ", id "
+                        + mDesiredFrontendId);
             }
-            mFrontendType = frontendType;
-            mDesiredFrontendId = desiredId;
             if (!checkResource(TunerResourceManager.TUNER_RESOURCE_TYPE_FRONTEND, mFrontendLock)) {
                 return RESULT_UNAVAILABLE;
             }
diff --git a/media/java/android/media/tv/tuner/dvr/DvrRecorder.java b/media/java/android/media/tv/tuner/dvr/DvrRecorder.java
index 1a65832..4bcc3c6 100644
--- a/media/java/android/media/tv/tuner/dvr/DvrRecorder.java
+++ b/media/java/android/media/tv/tuner/dvr/DvrRecorder.java
@@ -28,6 +28,7 @@
 import android.os.Process;
 import android.util.Log;
 
+import com.android.internal.annotations.GuardedBy;
 import com.android.internal.util.FrameworkStatsLog;
 
 import java.util.concurrent.Executor;
@@ -48,7 +49,9 @@
     private static int sInstantId = 0;
     private int mSegmentId = 0;
     private int mOverflow;
-    private Boolean mIsStopped = true;
+    private final Object mIsStoppedLock = new Object();
+    @GuardedBy("mIsStoppedLock")
+    private boolean mIsStopped = true;
     private final Object mListenerLock = new Object();
 
     private native int nativeAttachFilter(Filter filter);
@@ -178,7 +181,7 @@
                 .write(FrameworkStatsLog.TV_TUNER_DVR_STATUS, mUserId,
                     FrameworkStatsLog.TV_TUNER_DVR_STATUS__TYPE__RECORD,
                     FrameworkStatsLog.TV_TUNER_DVR_STATUS__STATE__STARTED, mSegmentId, 0);
-        synchronized (mIsStopped) {
+        synchronized (mIsStoppedLock) {
             int result = nativeStartDvr();
             if (result == Tuner.RESULT_SUCCESS) {
                 mIsStopped = false;
@@ -201,7 +204,7 @@
                 .write(FrameworkStatsLog.TV_TUNER_DVR_STATUS, mUserId,
                     FrameworkStatsLog.TV_TUNER_DVR_STATUS__TYPE__RECORD,
                     FrameworkStatsLog.TV_TUNER_DVR_STATUS__STATE__STOPPED, mSegmentId, mOverflow);
-        synchronized (mIsStopped) {
+        synchronized (mIsStoppedLock) {
             int result = nativeStopDvr();
             if (result == Tuner.RESULT_SUCCESS) {
                 mIsStopped = true;
@@ -219,7 +222,7 @@
      */
     @Result
     public int flush() {
-        synchronized (mIsStopped) {
+        synchronized (mIsStoppedLock) {
             if (mIsStopped) {
                 return nativeFlushDvr();
             }
diff --git a/media/java/android/mtp/MtpPropertyGroup.java b/media/java/android/mtp/MtpPropertyGroup.java
index aff2e1b4..89e5e0d 100644
--- a/media/java/android/mtp/MtpPropertyGroup.java
+++ b/media/java/android/mtp/MtpPropertyGroup.java
@@ -230,7 +230,7 @@
                 case MtpConstants.PROPERTY_PERSISTENT_UID:
                     // The persistent uid must be unique and never reused among all objects,
                     // and remain the same between sessions.
-                    long puid = (object.getPath().toString().hashCode() << 32)
+                    long puid = (((long) object.getPath().toString().hashCode()) << 32)
                             + object.getModifiedTime();
                     list.append(id, property.code, property.type, puid);
                     break;
diff --git a/media/jni/android_media_tv_Tuner.cpp b/media/jni/android_media_tv_Tuner.cpp
index c18edcd..1504930 100644
--- a/media/jni/android_media_tv_Tuner.cpp
+++ b/media/jni/android_media_tv_Tuner.cpp
@@ -360,6 +360,7 @@
                 lnb,
                 gFields.onLnbEventID,
                 (jint)lnbEventType);
+        env->DeleteLocalRef(lnb);
     } else {
         ALOGE("LnbClientCallbackImpl::onEvent:"
                 "Lnb object has been freed. Ignoring callback.");
@@ -378,6 +379,7 @@
                 lnb,
                 gFields.onLnbDiseqcMessageID,
                 array);
+        env->DeleteLocalRef(lnb);
     } else {
         ALOGE("LnbClientCallbackImpl::onDiseqcMessage:"
                 "Lnb object has been freed. Ignoring callback.");
@@ -404,6 +406,7 @@
     jobject dvr(env->NewLocalRef(mDvrObj));
     if (!env->IsSameObject(dvr, nullptr)) {
         env->CallVoidMethod(dvr, gFields.onDvrRecordStatusID, (jint)status);
+        env->DeleteLocalRef(dvr);
     } else {
         ALOGE("DvrClientCallbackImpl::onRecordStatus:"
                 "Dvr object has been freed. Ignoring callback.");
@@ -416,6 +419,7 @@
     jobject dvr(env->NewLocalRef(mDvrObj));
     if (!env->IsSameObject(dvr, nullptr)) {
         env->CallVoidMethod(dvr, gFields.onDvrPlaybackStatusID, (jint)status);
+        env->DeleteLocalRef(dvr);
     } else {
         ALOGE("DvrClientCallbackImpl::onPlaybackStatus:"
                 "Dvr object has been freed. Ignoring callback.");
@@ -603,6 +607,7 @@
 
     jobject obj = env->NewObject(eventClazz, eventInit, tableId, version, sectionNum, dataLength);
     env->SetObjectArrayElement(arr, size, obj);
+    env->DeleteLocalRef(obj);
 }
 
 void FilterClientCallbackImpl::getMediaEvent(jobjectArray &arr, const int size,
@@ -673,6 +678,10 @@
     }
 
     env->SetObjectArrayElement(arr, size, obj);
+    if(audioDescriptor != nullptr) {
+        env->DeleteLocalRef(audioDescriptor);
+    }
+    env->DeleteLocalRef(obj);
 }
 
 void FilterClientCallbackImpl::getPesEvent(jobjectArray &arr, const int size,
@@ -688,6 +697,7 @@
 
     jobject obj = env->NewObject(eventClazz, eventInit, streamId, dataLength, mpuSequenceNumber);
     env->SetObjectArrayElement(arr, size, obj);
+    env->DeleteLocalRef(obj);
 }
 
 void FilterClientCallbackImpl::getTsRecordEvent(jobjectArray &arr, const int size,
@@ -725,6 +735,7 @@
     jobject obj =
             env->NewObject(eventClazz, eventInit, jpid, ts, sc, byteNumber, pts, firstMbInSlice);
     env->SetObjectArrayElement(arr, size, obj);
+    env->DeleteLocalRef(obj);
 }
 
 void FilterClientCallbackImpl::getMmtpRecordEvent(jobjectArray &arr, const int size,
@@ -745,6 +756,7 @@
     jobject obj = env->NewObject(eventClazz, eventInit, scHevcIndexMask, byteNumber,
                                  mpuSequenceNumber, pts, firstMbInSlice, tsIndexMask);
     env->SetObjectArrayElement(arr, size, obj);
+    env->DeleteLocalRef(obj);
 }
 
 void FilterClientCallbackImpl::getDownloadEvent(jobjectArray &arr, const int size,
@@ -764,6 +776,7 @@
     jobject obj = env->NewObject(eventClazz, eventInit, itemId, downloadId, mpuSequenceNumber,
                                  itemFragmentIndex, lastItemFragmentIndex, dataLength);
     env->SetObjectArrayElement(arr, size, obj);
+    env->DeleteLocalRef(obj);
 }
 
 void FilterClientCallbackImpl::getIpPayloadEvent(jobjectArray &arr, const int size,
@@ -776,6 +789,7 @@
     jint dataLength = ipPayloadEvent.dataLength;
     jobject obj = env->NewObject(eventClazz, eventInit, dataLength);
     env->SetObjectArrayElement(arr, size, obj);
+    env->DeleteLocalRef(obj);
 }
 
 void FilterClientCallbackImpl::getTemiEvent(jobjectArray &arr, const int size,
@@ -794,6 +808,8 @@
 
     jobject obj = env->NewObject(eventClazz, eventInit, pts, descrTag, array);
     env->SetObjectArrayElement(arr, size, obj);
+    env->DeleteLocalRef(array);
+    env->DeleteLocalRef(obj);
 }
 
 void FilterClientCallbackImpl::getScramblingStatusEvent(jobjectArray &arr, const int size,
@@ -807,6 +823,7 @@
                     .get<DemuxFilterMonitorEvent::Tag::scramblingStatus>();
     jobject obj = env->NewObject(eventClazz, eventInit, scramblingStatus);
     env->SetObjectArrayElement(arr, size, obj);
+    env->DeleteLocalRef(obj);
 }
 
 void FilterClientCallbackImpl::getIpCidChangeEvent(jobjectArray &arr, const int size,
@@ -819,6 +836,7 @@
                                                  .get<DemuxFilterMonitorEvent::Tag::cid>();
     jobject obj = env->NewObject(eventClazz, eventInit, cid);
     env->SetObjectArrayElement(arr, size, obj);
+    env->DeleteLocalRef(obj);
 }
 
 void FilterClientCallbackImpl::getRestartEvent(jobjectArray &arr, const int size,
@@ -922,10 +940,12 @@
             methodID = gFields.onSharedFilterEventID;
         }
         env->CallVoidMethod(filter, methodID, array);
+        env->DeleteLocalRef(filter);
     } else {
         ALOGE("FilterClientCallbackImpl::onFilterEvent:"
               "Filter object has been freed. Ignoring callback.");
     }
+    env->DeleteLocalRef(array);
 }
 
 void FilterClientCallbackImpl::onFilterStatus(const DemuxFilterStatus status) {
@@ -938,6 +958,7 @@
             methodID = gFields.onSharedFilterStatusID;
         }
         env->CallVoidMethod(filter, methodID, (jint)static_cast<uint8_t>(status));
+        env->DeleteLocalRef(filter);
     } else {
         ALOGE("FilterClientCallbackImpl::onFilterStatus:"
               "Filter object has been freed. Ignoring callback.");
@@ -1006,6 +1027,7 @@
                     frontend,
                     gFields.onFrontendEventID,
                     (jint)frontendEventType);
+            env->DeleteLocalRef(frontend);
         } else {
             ALOGW("FrontendClientCallbackImpl::onEvent:"
                     "Frontend object has been freed. Ignoring callback.");
@@ -1028,6 +1050,7 @@
             continue;
         }
         executeOnScanMessage(env, clazz, frontend, type, message);
+        env->DeleteLocalRef(frontend);
     }
 }
 
@@ -1069,6 +1092,7 @@
             env->SetLongArrayRegion(freqs, 0, v.size(), reinterpret_cast<jlong *>(&v[0]));
             env->CallVoidMethod(frontend, env->GetMethodID(clazz, "onFrequenciesReport", "([J)V"),
                                 freqs);
+            env->DeleteLocalRef(freqs);
             break;
         }
         case FrontendScanMessageType::SYMBOL_RATE: {
@@ -1077,6 +1101,7 @@
             env->SetIntArrayRegion(symbolRates, 0, v.size(), reinterpret_cast<jint *>(&v[0]));
             env->CallVoidMethod(frontend, env->GetMethodID(clazz, "onSymbolRates", "([I)V"),
                                 symbolRates);
+            env->DeleteLocalRef(symbolRates);
             break;
         }
         case FrontendScanMessageType::HIERARCHY: {
@@ -1094,6 +1119,7 @@
             jintArray plpIds = env->NewIntArray(jintV.size());
             env->SetIntArrayRegion(plpIds, 0, jintV.size(), reinterpret_cast<jint *>(&jintV[0]));
             env->CallVoidMethod(frontend, env->GetMethodID(clazz, "onPlpIds", "([I)V"), plpIds);
+            env->DeleteLocalRef(plpIds);
             break;
         }
         case FrontendScanMessageType::GROUP_IDS: {
@@ -1101,6 +1127,7 @@
             jintArray groupIds = env->NewIntArray(jintV.size());
             env->SetIntArrayRegion(groupIds, 0, jintV.size(), reinterpret_cast<jint *>(&jintV[0]));
             env->CallVoidMethod(frontend, env->GetMethodID(clazz, "onGroupIds", "([I)V"), groupIds);
+            env->DeleteLocalRef(groupIds);
             break;
         }
         case FrontendScanMessageType::INPUT_STREAM_IDS: {
@@ -1109,6 +1136,7 @@
             env->SetIntArrayRegion(streamIds, 0, jintV.size(), reinterpret_cast<jint *>(&jintV[0]));
             env->CallVoidMethod(frontend, env->GetMethodID(clazz, "onInputStreamIds", "([I)V"),
                                 streamIds);
+            env->DeleteLocalRef(streamIds);
             break;
         }
         case FrontendScanMessageType::STANDARD: {
@@ -1142,12 +1170,14 @@
                 jboolean lls = info.bLlsFlag;
                 jobject obj = env->NewObject(plpClazz, init, plpId, lls);
                 env->SetObjectArrayElement(array, i, obj);
+                env->DeleteLocalRef(obj);
             }
             env->CallVoidMethod(frontend,
                                 env->GetMethodID(clazz, "onAtsc3PlpInfos",
                                                  "([Landroid/media/tv/tuner/frontend/"
                                                  "Atsc3PlpInfo;)V"),
                                 array);
+            env->DeleteLocalRef(array);
             break;
         }
         case FrontendScanMessageType::MODULATION: {
@@ -1219,6 +1249,7 @@
             env->SetIntArrayRegion(cellIds, 0, jintV.size(), reinterpret_cast<jint *>(&jintV[0]));
             env->CallVoidMethod(frontend, env->GetMethodID(clazz, "onDvbtCellIdsReported", "([I)V"),
                                 cellIds);
+            env->DeleteLocalRef(cellIds);
             break;
         }
         default:
@@ -1673,6 +1704,7 @@
     for (int i = 0; i < size; i++) {
         jobject readinessObj = env->NewObject(clazz, init, intTypes[i], readiness[i]);
         env->SetObjectArrayElement(valObj, i, readinessObj);
+        env->DeleteLocalRef(readinessObj);
     }
     return valObj;
 }
@@ -2081,6 +2113,7 @@
                 jobject newBooleanObj = env->NewObject(booleanClazz, initBoolean,
                                                        s.get<FrontendStatus::Tag::isDemodLocked>());
                 env->SetObjectField(statusObj, field, newBooleanObj);
+                env->DeleteLocalRef(newBooleanObj);
                 break;
             }
             case FrontendStatus::Tag::snr: {
@@ -2088,6 +2121,7 @@
                 jobject newIntegerObj =
                         env->NewObject(intClazz, initInt, s.get<FrontendStatus::Tag::snr>());
                 env->SetObjectField(statusObj, field, newIntegerObj);
+                env->DeleteLocalRef(newIntegerObj);
                 break;
             }
             case FrontendStatus::Tag::ber: {
@@ -2095,6 +2129,7 @@
                 jobject newIntegerObj =
                         env->NewObject(intClazz, initInt, s.get<FrontendStatus::Tag::ber>());
                 env->SetObjectField(statusObj, field, newIntegerObj);
+                env->DeleteLocalRef(newIntegerObj);
                 break;
             }
             case FrontendStatus::Tag::per: {
@@ -2102,6 +2137,7 @@
                 jobject newIntegerObj =
                         env->NewObject(intClazz, initInt, s.get<FrontendStatus::Tag::per>());
                 env->SetObjectField(statusObj, field, newIntegerObj);
+                env->DeleteLocalRef(newIntegerObj);
                 break;
             }
             case FrontendStatus::Tag::preBer: {
@@ -2109,6 +2145,7 @@
                 jobject newIntegerObj =
                         env->NewObject(intClazz, initInt, s.get<FrontendStatus::Tag::preBer>());
                 env->SetObjectField(statusObj, field, newIntegerObj);
+                env->DeleteLocalRef(newIntegerObj);
                 break;
             }
             case FrontendStatus::Tag::signalQuality: {
@@ -2116,6 +2153,7 @@
                 jobject newIntegerObj = env->NewObject(intClazz, initInt,
                                                        s.get<FrontendStatus::Tag::signalQuality>());
                 env->SetObjectField(statusObj, field, newIntegerObj);
+                env->DeleteLocalRef(newIntegerObj);
                 break;
             }
             case FrontendStatus::Tag::signalStrength: {
@@ -2124,6 +2162,7 @@
                         env->NewObject(intClazz, initInt,
                                        s.get<FrontendStatus::Tag::signalStrength>());
                 env->SetObjectField(statusObj, field, newIntegerObj);
+                env->DeleteLocalRef(newIntegerObj);
                 break;
             }
             case FrontendStatus::Tag::symbolRate: {
@@ -2131,6 +2170,7 @@
                 jobject newIntegerObj =
                         env->NewObject(intClazz, initInt, s.get<FrontendStatus::Tag::symbolRate>());
                 env->SetObjectField(statusObj, field, newIntegerObj);
+                env->DeleteLocalRef(newIntegerObj);
                 break;
             }
             case FrontendStatus::Tag::innerFec: {
@@ -2141,6 +2181,8 @@
                         env->NewObject(longClazz, initLong,
                                        static_cast<long>(s.get<FrontendStatus::Tag::innerFec>()));
                 env->SetObjectField(statusObj, field, newLongObj);
+                env->DeleteLocalRef(longClazz);
+                env->DeleteLocalRef(newLongObj);
                 break;
             }
             case FrontendStatus::Tag::modulationStatus: {
@@ -2183,6 +2225,7 @@
                 if (valid) {
                     jobject newIntegerObj = env->NewObject(intClazz, initInt, intModulation);
                     env->SetObjectField(statusObj, field, newIntegerObj);
+                    env->DeleteLocalRef(newIntegerObj);
                 }
                 break;
             }
@@ -2192,6 +2235,7 @@
                         env->NewObject(intClazz, initInt,
                                        static_cast<jint>(s.get<FrontendStatus::Tag::inversion>()));
                 env->SetObjectField(statusObj, field, newIntegerObj);
+                env->DeleteLocalRef(newIntegerObj);
                 break;
             }
             case FrontendStatus::Tag::lnbVoltage: {
@@ -2200,6 +2244,7 @@
                         env->NewObject(intClazz, initInt,
                                        static_cast<jint>(s.get<FrontendStatus::Tag::lnbVoltage>()));
                 env->SetObjectField(statusObj, field, newIntegerObj);
+                env->DeleteLocalRef(newIntegerObj);
                 break;
             }
             case FrontendStatus::Tag::plpId: {
@@ -2207,6 +2252,7 @@
                 jobject newIntegerObj =
                         env->NewObject(intClazz, initInt, s.get<FrontendStatus::Tag::plpId>());
                 env->SetObjectField(statusObj, field, newIntegerObj);
+                env->DeleteLocalRef(newIntegerObj);
                 break;
             }
             case FrontendStatus::Tag::isEWBS: {
@@ -2214,6 +2260,7 @@
                 jobject newBooleanObj = env->NewObject(booleanClazz, initBoolean,
                                                        s.get<FrontendStatus::Tag::isEWBS>());
                 env->SetObjectField(statusObj, field, newBooleanObj);
+                env->DeleteLocalRef(newBooleanObj);
                 break;
             }
             case FrontendStatus::Tag::agc: {
@@ -2221,6 +2268,7 @@
                 jobject newIntegerObj =
                         env->NewObject(intClazz, initInt, s.get<FrontendStatus::Tag::agc>());
                 env->SetObjectField(statusObj, field, newIntegerObj);
+                env->DeleteLocalRef(newIntegerObj);
                 break;
             }
             case FrontendStatus::Tag::isLnaOn: {
@@ -2228,6 +2276,7 @@
                 jobject newBooleanObj = env->NewObject(booleanClazz, initBoolean,
                                                        s.get<FrontendStatus::Tag::isLnaOn>());
                 env->SetObjectField(statusObj, field, newBooleanObj);
+                env->DeleteLocalRef(newBooleanObj);
                 break;
             }
             case FrontendStatus::Tag::isLayerError: {
@@ -2241,6 +2290,7 @@
                     env->SetBooleanArrayRegion(valObj, i, 1, &x);
                 }
                 env->SetObjectField(statusObj, field, valObj);
+                env->DeleteLocalRef(valObj);
                 break;
             }
             case FrontendStatus::Tag::mer: {
@@ -2248,6 +2298,7 @@
                 jobject newIntegerObj =
                         env->NewObject(intClazz, initInt, s.get<FrontendStatus::Tag::mer>());
                 env->SetObjectField(statusObj, field, newIntegerObj);
+                env->DeleteLocalRef(newIntegerObj);
                 break;
             }
             case FrontendStatus::Tag::freqOffset: {
@@ -2255,6 +2306,7 @@
                 jobject newLongObj = env->NewObject(longClazz, initLong,
                                                     s.get<FrontendStatus::Tag::freqOffset>());
                 env->SetObjectField(statusObj, field, newLongObj);
+                env->DeleteLocalRef(newLongObj);
                 break;
             }
             case FrontendStatus::Tag::hierarchy: {
@@ -2263,6 +2315,7 @@
                         env->NewObject(intClazz, initInt,
                                        static_cast<jint>(s.get<FrontendStatus::Tag::hierarchy>()));
                 env->SetObjectField(statusObj, field, newIntegerObj);
+                env->DeleteLocalRef(newIntegerObj);
                 break;
             }
             case FrontendStatus::Tag::isRfLocked: {
@@ -2270,6 +2323,7 @@
                 jobject newBooleanObj = env->NewObject(booleanClazz, initBoolean,
                                                        s.get<FrontendStatus::Tag::isRfLocked>());
                 env->SetObjectField(statusObj, field, newBooleanObj);
+                env->DeleteLocalRef(newBooleanObj);
                 break;
             }
             case FrontendStatus::Tag::plpInfo: {
@@ -2289,9 +2343,12 @@
 
                     jobject plpObj = env->NewObject(plpClazz, initPlp, plpId, isLocked, uec);
                     env->SetObjectArrayElement(valObj, i, plpObj);
+                    env->DeleteLocalRef(plpObj);
                 }
 
                 env->SetObjectField(statusObj, field, valObj);
+                env->DeleteLocalRef(plpClazz);
+                env->DeleteLocalRef(valObj);
                 break;
             }
             case FrontendStatus::Tag::modulations: {
@@ -2374,6 +2431,7 @@
                 if (valid) {
                     env->SetObjectField(statusObj, field, valObj);
                 }
+                env->DeleteLocalRef(valObj);
                 break;
             }
             case FrontendStatus::Tag::bers: {
@@ -2384,6 +2442,7 @@
                 env->SetIntArrayRegion(valObj, 0, v.size(), reinterpret_cast<jint *>(&v[0]));
 
                 env->SetObjectField(statusObj, field, valObj);
+                env->DeleteLocalRef(valObj);
                 break;
             }
             case FrontendStatus::Tag::codeRates: {
@@ -2394,6 +2453,7 @@
                 env->SetIntArrayRegion(valObj, 0, v.size(), reinterpret_cast<jint *>(&v[0]));
 
                 env->SetObjectField(statusObj, field, valObj);
+                env->DeleteLocalRef(valObj);
                 break;
             }
             case FrontendStatus::Tag::bandwidth: {
@@ -2434,6 +2494,7 @@
                 if (valid) {
                     jobject newIntegerObj = env->NewObject(intClazz, initInt, intBandwidth);
                     env->SetObjectField(statusObj, field, newIntegerObj);
+                    env->DeleteLocalRef(newIntegerObj);
                 }
                 break;
             }
@@ -2465,6 +2526,7 @@
                 if (valid) {
                     jobject newIntegerObj = env->NewObject(intClazz, initInt, intInterval);
                     env->SetObjectField(statusObj, field, newIntegerObj);
+                    env->DeleteLocalRef(newIntegerObj);
                 }
                 break;
             }
@@ -2497,6 +2559,7 @@
                 if (valid) {
                     jobject newIntegerObj = env->NewObject(intClazz, initInt, intTransmissionMode);
                     env->SetObjectField(statusObj, field, newIntegerObj);
+                    env->DeleteLocalRef(newIntegerObj);
                 }
                 break;
             }
@@ -2505,6 +2568,7 @@
                 jobject newIntegerObj =
                         env->NewObject(intClazz, initInt, s.get<FrontendStatus::Tag::uec>());
                 env->SetObjectField(statusObj, field, newIntegerObj);
+                env->DeleteLocalRef(newIntegerObj);
                 break;
             }
             case FrontendStatus::Tag::systemId: {
@@ -2512,6 +2576,7 @@
                 jobject newIntegerObj =
                         env->NewObject(intClazz, initInt, s.get<FrontendStatus::Tag::systemId>());
                 env->SetObjectField(statusObj, field, newIntegerObj);
+                env->DeleteLocalRef(newIntegerObj);
                 break;
             }
             case FrontendStatus::Tag::interleaving: {
@@ -2558,6 +2623,7 @@
                 if (valid) {
                     env->SetObjectField(statusObj, field, valObj);
                 }
+                env->DeleteLocalRef(valObj);
                 break;
             }
             case FrontendStatus::Tag::isdbtSegment: {
@@ -2568,6 +2634,7 @@
                 env->SetIntArrayRegion(valObj, 0, v.size(), reinterpret_cast<jint*>(&v[0]));
 
                 env->SetObjectField(statusObj, field, valObj);
+                env->DeleteLocalRef(valObj);
                 break;
             }
             case FrontendStatus::Tag::tsDataRate: {
@@ -2578,6 +2645,7 @@
                 env->SetIntArrayRegion(valObj, 0, v.size(), reinterpret_cast<jint *>(&v[0]));
 
                 env->SetObjectField(statusObj, field, valObj);
+                env->DeleteLocalRef(valObj);
                 break;
             }
             case FrontendStatus::Tag::rollOff: {
@@ -2605,6 +2673,7 @@
                 if (valid) {
                     jobject newIntegerObj = env->NewObject(intClazz, initInt, intRollOff);
                     env->SetObjectField(statusObj, field, newIntegerObj);
+                    env->DeleteLocalRef(newIntegerObj);
                 }
                 break;
             }
@@ -2613,6 +2682,7 @@
                 jobject newBooleanObj = env->NewObject(booleanClazz, initBoolean,
                                                        s.get<FrontendStatus::Tag::isMiso>());
                 env->SetObjectField(statusObj, field, newBooleanObj);
+                env->DeleteLocalRef(newBooleanObj);
                 break;
             }
             case FrontendStatus::Tag::isLinear: {
@@ -2620,6 +2690,7 @@
                 jobject newBooleanObj = env->NewObject(booleanClazz, initBoolean,
                                                        s.get<FrontendStatus::Tag::isLinear>());
                 env->SetObjectField(statusObj, field, newBooleanObj);
+                env->DeleteLocalRef(newBooleanObj);
                 break;
             }
             case FrontendStatus::Tag::isShortFrames: {
@@ -2627,6 +2698,7 @@
                 jobject newBooleanObj = env->NewObject(booleanClazz, initBoolean,
                                                        s.get<FrontendStatus::Tag::isShortFrames>());
                 env->SetObjectField(statusObj, field, newBooleanObj);
+                env->DeleteLocalRef(newBooleanObj);
                 break;
             }
             case FrontendStatus::Tag::isdbtMode: {
@@ -2634,6 +2706,7 @@
                 jobject newIntegerObj =
                         env->NewObject(intClazz, initInt, s.get<FrontendStatus::Tag::isdbtMode>());
                 env->SetObjectField(statusObj, field, newIntegerObj);
+                env->DeleteLocalRef(newIntegerObj);
                 break;
             }
             case FrontendStatus::Tag::partialReceptionFlag: {
@@ -2643,6 +2716,7 @@
                         env->NewObject(intClazz, initInt,
                                        s.get<FrontendStatus::Tag::partialReceptionFlag>());
                 env->SetObjectField(statusObj, field, newIntegerObj);
+                env->DeleteLocalRef(newIntegerObj);
                 break;
             }
             case FrontendStatus::Tag::streamIdList: {
@@ -2653,6 +2727,7 @@
                 env->SetIntArrayRegion(valObj, 0, v.size(), reinterpret_cast<jint *>(&ids[0]));
 
                 env->SetObjectField(statusObj, field, valObj);
+                env->DeleteLocalRef(valObj);
                 break;
             }
             case FrontendStatus::Tag::dvbtCellIds: {
@@ -2663,6 +2738,7 @@
                 env->SetIntArrayRegion(valObj, 0, v.size(), reinterpret_cast<jint *>(&ids[0]));
 
                 env->SetObjectField(statusObj, field, valObj);
+                env->DeleteLocalRef(valObj);
                 break;
             }
             case FrontendStatus::Tag::allPlpInfo: {
@@ -2678,9 +2754,12 @@
                     jobject plpObj = env->NewObject(plpClazz, initPlp, plpInfos[i].plpId,
                                                     plpInfos[i].bLlsFlag);
                     env->SetObjectArrayElement(valObj, i, plpObj);
+                    env->DeleteLocalRef(plpObj);
                 }
 
                 env->SetObjectField(statusObj, field, valObj);
+                env->DeleteLocalRef(plpClazz);
+                env->DeleteLocalRef(valObj);
                 break;
             }
         }
@@ -2837,6 +2916,7 @@
                 .fec = fec,
         };
         plps[i] = frontendAtsc3PlpSettings;
+        env->DeleteLocalRef(plp);
     }
     return plps;
 }
@@ -3192,6 +3272,7 @@
                 env->GetIntField(layer, env->GetFieldID(layerClazz, "mCodeRate", "I")));
         frontendIsdbtSettings.layerSettings[i].numOfSegment =
                 env->GetIntField(layer, env->GetFieldID(layerClazz, "mNumOfSegments", "I"));
+        env->DeleteLocalRef(layer);
     }
 
     frontendSettings.set<FrontendSettings::Tag::isdbt>(frontendIsdbtSettings);
diff --git a/media/mca/effect/java/android/media/effect/EffectFactory.java b/media/mca/effect/java/android/media/effect/EffectFactory.java
index f6fcba7..cbb2736 100644
--- a/media/mca/effect/java/android/media/effect/EffectFactory.java
+++ b/media/mca/effect/java/android/media/effect/EffectFactory.java
@@ -486,11 +486,9 @@
 
     private Effect instantiateEffect(Class effectClass, String name) {
         // Make sure this is an Effect subclass
-        try {
-            effectClass.asSubclass(Effect.class);
-        } catch (ClassCastException e) {
+        if (!Effect.class.isAssignableFrom(effectClass)) {
             throw new IllegalArgumentException("Attempting to allocate effect '" + effectClass
-                + "' which is not a subclass of Effect!", e);
+                + "' which is not a subclass of Effect!");
         }
 
         // Look for the correct constructor
diff --git a/media/mca/filterfw/java/android/filterfw/core/Filter.java b/media/mca/filterfw/java/android/filterfw/core/Filter.java
index a608ef5..e82c046 100644
--- a/media/mca/filterfw/java/android/filterfw/core/Filter.java
+++ b/media/mca/filterfw/java/android/filterfw/core/Filter.java
@@ -90,9 +90,7 @@
             return false;
         }
         // Then make sure it's a subclass of Filter.
-        try {
-            filterClass.asSubclass(Filter.class);
-        } catch (ClassCastException e) {
+        if (!Filter.class.isAssignableFrom(filterClass)) {
             return false;
         }
         return true;
diff --git a/media/mca/filterfw/java/android/filterfw/core/FilterFactory.java b/media/mca/filterfw/java/android/filterfw/core/FilterFactory.java
index 779df99..736e511 100644
--- a/media/mca/filterfw/java/android/filterfw/core/FilterFactory.java
+++ b/media/mca/filterfw/java/android/filterfw/core/FilterFactory.java
@@ -112,9 +112,7 @@
 
     public Filter createFilterByClass(Class filterClass, String filterName) {
         // Make sure this is a Filter subclass
-        try {
-            filterClass.asSubclass(Filter.class);
-        } catch (ClassCastException e) {
+        if (!Filter.class.isAssignableFrom(filterClass)) {
             throw new IllegalArgumentException("Attempting to allocate class '" + filterClass
                 + "' which is not a subclass of Filter!");
         }
diff --git a/media/mca/filterfw/java/android/filterfw/core/KeyValueMap.java b/media/mca/filterfw/java/android/filterfw/core/KeyValueMap.java
index 8cf9a13..6ff1885 100644
--- a/media/mca/filterfw/java/android/filterfw/core/KeyValueMap.java
+++ b/media/mca/filterfw/java/android/filterfw/core/KeyValueMap.java
@@ -55,12 +55,12 @@
 
     public int getInt(String key) {
         Object result = get(key);
-        return result != null ? (Integer)result : null;
+        return result != null ? (Integer) result : 0;
     }
 
     public float getFloat(String key) {
         Object result = get(key);
-        return result != null ? (Float)result : null;
+        return result != null ? (Float) result : 0;
     }
 
     @Override
diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/performance/MediaPlayerPerformance.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/performance/MediaPlayerPerformance.java
index c5281657..8c05725 100644
--- a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/performance/MediaPlayerPerformance.java
+++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/performance/MediaPlayerPerformance.java
@@ -296,7 +296,7 @@
                 mMemWriter.write("End Memory :" + mEndMemory + "\n");
             }
         } catch (Exception e) {
-            e.toString();
+            // TODO
         }
     }
 
diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/CameraMetadataTest.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/CameraMetadataTest.java
index 39add7e..c814eba 100644
--- a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/CameraMetadataTest.java
+++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/CameraMetadataTest.java
@@ -264,8 +264,14 @@
                 builder.append("**");
             }
 
-            if (elem instanceof Number) {
-                builder.append(String.format("%x", elem));
+            if (elem instanceof Byte) {
+                builder.append(String.format("%x", (Byte) elem));
+            } else if (elem instanceof Short) {
+                builder.append(String.format("%x", (Short) elem));
+            } else if (elem instanceof Integer) {
+                builder.append(String.format("%x", (Integer) elem));
+            } else if (elem instanceof Long) {
+                builder.append(String.format("%x", (Long) elem));
             } else {
                 builder.append(elem);
             }
diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/MediaPlayerGetCurrentPositionStateUnitTest.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/MediaPlayerGetCurrentPositionStateUnitTest.java
index fd1c2d3..37dd4b5 100644
--- a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/MediaPlayerGetCurrentPositionStateUnitTest.java
+++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/MediaPlayerGetCurrentPositionStateUnitTest.java
@@ -18,7 +18,7 @@
 
 import android.media.MediaPlayer;
 import android.test.AndroidTestCase;
-import android.test.suitebuilder.annotation.LargeTest;;
+import android.test.suitebuilder.annotation.LargeTest;
 
 /**
  * Unit test class to test the set of valid and invalid states that
diff --git a/packages/CarrierDefaultApp/res/values/strings.xml b/packages/CarrierDefaultApp/res/values/strings.xml
index 8a19709..3dcdf00 100644
--- a/packages/CarrierDefaultApp/res/values/strings.xml
+++ b/packages/CarrierDefaultApp/res/values/strings.xml
@@ -15,7 +15,7 @@
     <string name="ssl_error_continue">Continue anyway via browser</string>
 
     <!-- Telephony notification channel name for network boost notifications. -->
-    <string name="network_boost_notification_channel">Network Boost</string>
+    <string name="network_boost_notification_channel">Network boost</string>
     <!-- Notification title text for the network boost notification. -->
     <string name="network_boost_notification_title">%s recommends a data boost</string>
     <!-- Notification detail text for the network boost notification. -->
diff --git a/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/SlicePurchaseActivity.java b/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/SlicePurchaseActivity.java
index 220aa33..c524037 100644
--- a/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/SlicePurchaseActivity.java
+++ b/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/SlicePurchaseActivity.java
@@ -28,6 +28,7 @@
 import android.telephony.TelephonyManager;
 import android.util.Log;
 import android.view.KeyEvent;
+import android.webkit.URLUtil;
 import android.webkit.WebView;
 
 import com.android.phone.slice.SlicePurchaseController;
@@ -60,36 +61,38 @@
 
     @NonNull private WebView mWebView;
     @NonNull private Context mApplicationContext;
+    @NonNull private Intent mIntent;
+    @Nullable private URL mUrl;
     private int mSubId;
     @TelephonyManager.PremiumCapability protected int mCapability;
 
     @Override
     protected void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
-        Intent intent = getIntent();
-        mSubId = intent.getIntExtra(SlicePurchaseController.EXTRA_SUB_ID,
+        mIntent = getIntent();
+        mSubId = mIntent.getIntExtra(SlicePurchaseController.EXTRA_SUB_ID,
                 SubscriptionManager.INVALID_SUBSCRIPTION_ID);
-        mCapability = intent.getIntExtra(SlicePurchaseController.EXTRA_PREMIUM_CAPABILITY,
+        mCapability = mIntent.getIntExtra(SlicePurchaseController.EXTRA_PREMIUM_CAPABILITY,
                 SlicePurchaseController.PREMIUM_CAPABILITY_INVALID);
         mApplicationContext = getApplicationContext();
-        URL url = getUrl();
+        mUrl = getUrl();
         logd("onCreate: subId=" + mSubId + ", capability="
                 + TelephonyManager.convertPremiumCapabilityToString(mCapability)
-                + ", url=" + url);
+                + ", url=" + mUrl);
 
         // Cancel network boost notification
         mApplicationContext.getSystemService(NotificationManager.class)
                 .cancel(SlicePurchaseBroadcastReceiver.NETWORK_BOOST_NOTIFICATION_TAG, mCapability);
 
         // Verify intent and values are valid
-        if (!SlicePurchaseBroadcastReceiver.isIntentValid(intent)) {
-            loge("Not starting SlicePurchaseActivity with an invalid Intent: " + intent);
+        if (!SlicePurchaseBroadcastReceiver.isIntentValid(mIntent)) {
+            loge("Not starting SlicePurchaseActivity with an invalid Intent: " + mIntent);
             SlicePurchaseBroadcastReceiver.sendSlicePurchaseAppResponse(
-                    intent, SlicePurchaseController.EXTRA_INTENT_REQUEST_FAILED);
+                    mIntent, SlicePurchaseController.EXTRA_INTENT_REQUEST_FAILED);
             finishAndRemoveTask();
             return;
         }
-        if (url == null) {
+        if (mUrl == null) {
             String error = "Unable to create a URL from carrier configs.";
             loge(error);
             Intent data = new Intent();
@@ -97,7 +100,7 @@
                     SlicePurchaseController.FAILURE_CODE_CARRIER_URL_UNAVAILABLE);
             data.putExtra(SlicePurchaseController.EXTRA_FAILURE_REASON, error);
             SlicePurchaseBroadcastReceiver.sendSlicePurchaseAppResponseWithData(mApplicationContext,
-                    getIntent(), SlicePurchaseController.EXTRA_INTENT_CARRIER_ERROR, data);
+                    mIntent, SlicePurchaseController.EXTRA_INTENT_CARRIER_ERROR, data);
             finishAndRemoveTask();
             return;
         }
@@ -105,7 +108,7 @@
             loge("Unable to start the slice purchase application on the non-default data "
                     + "subscription: " + mSubId);
             SlicePurchaseBroadcastReceiver.sendSlicePurchaseAppResponse(
-                    intent, SlicePurchaseController.EXTRA_INTENT_NOT_DEFAULT_DATA_SUBSCRIPTION);
+                    mIntent, SlicePurchaseController.EXTRA_INTENT_NOT_DEFAULT_DATA_SUBSCRIPTION);
             finishAndRemoveTask();
             return;
         }
@@ -114,16 +117,7 @@
         SlicePurchaseBroadcastReceiver.updateSlicePurchaseActivity(mCapability, this);
 
         // Create and configure WebView
-        mWebView = new WebView(this);
-        // Enable JavaScript for the carrier purchase website to send results back to
-        //  the slice purchase application.
-        mWebView.getSettings().setJavaScriptEnabled(true);
-        mWebView.addJavascriptInterface(
-                new SlicePurchaseWebInterface(this), "SlicePurchaseWebInterface");
-
-        // Display WebView
-        setContentView(mWebView);
-        mWebView.loadUrl(url.toString());
+        setupWebView();
     }
 
     protected void onPurchaseSuccessful(long duration) {
@@ -134,7 +128,7 @@
         Intent intent = new Intent();
         intent.putExtra(SlicePurchaseController.EXTRA_PURCHASE_DURATION, duration);
         SlicePurchaseBroadcastReceiver.sendSlicePurchaseAppResponseWithData(mApplicationContext,
-                getIntent(), SlicePurchaseController.EXTRA_INTENT_SUCCESS, intent);
+                mIntent, SlicePurchaseController.EXTRA_INTENT_SUCCESS, intent);
         finishAndRemoveTask();
     }
 
@@ -147,7 +141,7 @@
         data.putExtra(SlicePurchaseController.EXTRA_FAILURE_CODE, failureCode);
         data.putExtra(SlicePurchaseController.EXTRA_FAILURE_REASON, failureReason);
         SlicePurchaseBroadcastReceiver.sendSlicePurchaseAppResponseWithData(mApplicationContext,
-                getIntent(), SlicePurchaseController.EXTRA_INTENT_CARRIER_ERROR, data);
+                mIntent, SlicePurchaseController.EXTRA_INTENT_CARRIER_ERROR, data);
         finishAndRemoveTask();
     }
 
@@ -166,7 +160,7 @@
     protected void onDestroy() {
         logd("onDestroy: User canceled the purchase by closing the application.");
         SlicePurchaseBroadcastReceiver.sendSlicePurchaseAppResponse(
-                getIntent(), SlicePurchaseController.EXTRA_INTENT_CANCELED);
+                mIntent, SlicePurchaseController.EXTRA_INTENT_CANCELED);
         SlicePurchaseBroadcastReceiver.removeSlicePurchaseActivity(mCapability);
         super.onDestroy();
     }
@@ -175,14 +169,37 @@
         String url = mApplicationContext.getSystemService(CarrierConfigManager.class)
                 .getConfigForSubId(mSubId).getString(
                         CarrierConfigManager.KEY_PREMIUM_CAPABILITY_PURCHASE_URL_STRING);
-        try {
-            return new URL(url);
-        } catch (MalformedURLException e) {
-            loge("Invalid URL: " + url);
+        boolean isUrlValid = URLUtil.isValidUrl(url);
+        if (URLUtil.isAssetUrl(url)) {
+            isUrlValid = url.equals(SlicePurchaseController.SLICE_PURCHASE_TEST_FILE);
         }
+        if (isUrlValid) {
+            try {
+                return new URL(url);
+            } catch (MalformedURLException ignored) {
+            }
+        }
+        loge("Invalid URL: " + url);
         return null;
     }
 
+    private void setupWebView() {
+        // Create WebView
+        mWebView = new WebView(this);
+
+        // Enable JavaScript for the carrier purchase website to send results back to
+        //  the slice purchase application.
+        mWebView.getSettings().setJavaScriptEnabled(true);
+        mWebView.addJavascriptInterface(
+                new SlicePurchaseWebInterface(this), "SlicePurchaseWebInterface");
+
+        // Display WebView
+        setContentView(mWebView);
+
+        // Load the URL
+        mWebView.loadUrl(mUrl.toString());
+    }
+
     private static void logd(@NonNull String s) {
         Log.d(TAG, s);
     }
diff --git a/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/SlicePurchaseBroadcastReceiver.java b/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/SlicePurchaseBroadcastReceiver.java
index dd7b218..b322b8b 100644
--- a/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/SlicePurchaseBroadcastReceiver.java
+++ b/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/SlicePurchaseBroadcastReceiver.java
@@ -240,11 +240,15 @@
             return;
         }
 
-        context.getSystemService(NotificationManager.class).createNotificationChannel(
-                new NotificationChannel(NETWORK_BOOST_NOTIFICATION_CHANNEL_ID,
-                        context.getResources().getString(
-                                R.string.network_boost_notification_channel),
-                        NotificationManager.IMPORTANCE_DEFAULT));
+        NotificationChannel channel = new NotificationChannel(
+                NETWORK_BOOST_NOTIFICATION_CHANNEL_ID,
+                context.getResources().getString(R.string.network_boost_notification_channel),
+                NotificationManager.IMPORTANCE_DEFAULT);
+        // CarrierDefaultApp notifications are unblockable by default. Make this channel blockable
+        //  to allow users to disable notifications posted to this channel without affecting other
+        //  notifications in this application.
+        channel.setBlockable(true);
+        context.getSystemService(NotificationManager.class).createNotificationChannel(channel);
 
         Notification notification =
                 new Notification.Builder(context, NETWORK_BOOST_NOTIFICATION_CHANNEL_ID)
diff --git a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceActivity.java b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceActivity.java
index a7e1a59..ae40460 100644
--- a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceActivity.java
+++ b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceActivity.java
@@ -203,6 +203,7 @@
         initUI();
     }
 
+    @SuppressWarnings("MissingSuperCall") // TODO: Fix me
     @Override
     protected void onNewIntent(Intent intent) {
         // Force cancels the CDM dialog if this activity receives another intent with
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/common/material/ModalBottomSheet.kt b/packages/CredentialManager/src/com/android/credentialmanager/common/material/ModalBottomSheet.kt
index f1f453d..61e11fe 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/common/material/ModalBottomSheet.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/common/material/ModalBottomSheet.kt
@@ -62,6 +62,7 @@
 import com.android.credentialmanager.common.material.ModalBottomSheetValue.Expanded
 import com.android.credentialmanager.common.material.ModalBottomSheetValue.HalfExpanded
 import com.android.credentialmanager.common.material.ModalBottomSheetValue.Hidden
+import com.android.credentialmanager.ui.theme.LocalAndroidColorScheme
 import kotlinx.coroutines.CancellationException
 import kotlinx.coroutines.launch
 import kotlin.math.max
@@ -318,7 +319,7 @@
         rememberModalBottomSheetState(Hidden),
     sheetShape: Shape = MaterialTheme.shapes.large,
     sheetElevation: Dp = ModalBottomSheetDefaults.Elevation,
-    sheetBackgroundColor: Color = MaterialTheme.colorScheme.surface,
+    sheetBackgroundColor: Color = ModalBottomSheetDefaults.scrimColor,
     sheetContentColor: Color = contentColorFor(sheetBackgroundColor),
     scrimColor: Color = ModalBottomSheetDefaults.scrimColor,
     content: @Composable () -> Unit
@@ -476,5 +477,5 @@
      */
     val scrimColor: Color
         @Composable
-        get() = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.32f)
+        get() = LocalAndroidColorScheme.current.colorSurfaceHighlight
 }
\ No newline at end of file
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreatePasskeyComponents.kt b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreatePasskeyComponents.kt
index 67b704f..8a1f83d 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreatePasskeyComponents.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreatePasskeyComponents.kt
@@ -1,3 +1,5 @@
+@file:OptIn(ExperimentalMaterial3Api::class)
+
 package com.android.credentialmanager.createflow
 
 import android.credentials.Credential.TYPE_PASSWORD_CREDENTIAL
@@ -19,6 +21,7 @@
 import androidx.compose.material3.Text
 import androidx.compose.material3.TextButton
 import androidx.compose.material3.TopAppBar
+import androidx.compose.material3.TopAppBarDefaults
 import androidx.compose.material.icons.Icons
 import androidx.compose.material.icons.filled.ArrowBack
 import androidx.compose.material.icons.outlined.NewReleases
@@ -40,6 +43,7 @@
 import com.android.credentialmanager.common.material.rememberModalBottomSheetState
 import com.android.credentialmanager.common.ui.CancelButton
 import com.android.credentialmanager.common.ui.ConfirmButton
+import com.android.credentialmanager.ui.theme.EntryShape
 import com.android.credentialmanager.jetpack.developer.PublicKeyCredential.Companion.TYPE_PUBLIC_KEY_CREDENTIAL
 
 @OptIn(ExperimentalMaterial3Api::class)
@@ -91,7 +95,7 @@
       }
     },
     scrimColor = MaterialTheme.colorScheme.scrim,
-    sheetShape = MaterialTheme.shapes.medium,
+    sheetShape = EntryShape.TopRoundedCorner,
   ) {}
   LaunchedEffect(state.currentValue) {
     if (state.currentValue == ModalBottomSheetValue.Hidden) {
@@ -100,6 +104,7 @@
   }
 }
 
+@OptIn(ExperimentalMaterial3Api::class)
 @Composable
 fun ConfirmationCard(
   onConfirm: () -> Unit,
@@ -179,7 +184,7 @@
         color = Color.Transparent
       )
       Card(
-        shape = MaterialTheme.shapes.large,
+        shape = EntryShape.FullRoundedCorner,
         modifier = Modifier
           .padding(horizontal = 24.dp)
           .align(alignment = Alignment.CenterHorizontally),
@@ -243,14 +248,16 @@
               Icons.Filled.ArrowBack,
               stringResource(R.string.accessibility_back_arrow_button))
           }
-        }
+        },
+        colors = TopAppBarDefaults.smallTopAppBarColors
+          (containerColor = Color.Transparent),
       )
       Divider(
          thickness = 8.dp,
          color = Color.Transparent
       )
       Card(
-        shape = MaterialTheme.shapes.large,
+        shape = EntryShape.FullRoundedCorner,
         modifier = Modifier
           .padding(horizontal = 24.dp)
           .align(alignment = Alignment.CenterHorizontally)
@@ -362,7 +369,7 @@
             // TODO: add description.
             contentDescription = "")
     },
-    shape = MaterialTheme.shapes.large,
+    shape = EntryShape.FullRoundedCorner,
     label = {
       Text(
         text = providerInfo.displayName,
@@ -391,7 +398,8 @@
         bitmap = providerInfo.icon.toBitmap().asImageBitmap(),
         contentDescription = null,
         tint = Color.Unspecified,
-        modifier = Modifier.align(alignment = Alignment.CenterHorizontally).padding(all = 24.dp)
+        modifier = Modifier.align(alignment = Alignment.CenterHorizontally)
+          .padding(all = 24.dp).size(32.dp)
       )
       Text(
         text = when (requestDisplayInfo.type) {
@@ -425,7 +433,7 @@
         )
       }
       Card(
-        shape = MaterialTheme.shapes.large,
+        shape = EntryShape.FullRoundedCorner,
         modifier = Modifier
           .padding(horizontal = 24.dp)
           .align(alignment = Alignment.CenterHorizontally),
@@ -499,7 +507,7 @@
         bitmap = createOptionInfo.credentialTypeIcon.toBitmap().asImageBitmap(),
         contentDescription = null)
     },
-    shape = MaterialTheme.shapes.large,
+    shape = EntryShape.FullRoundedCorner,
     label = {
       Column() {
         Text(
@@ -532,7 +540,7 @@
                 bitmap = providerInfo.icon.toBitmap().asImageBitmap(),
                 contentDescription = null)
         },
-        shape = MaterialTheme.shapes.large,
+        shape = EntryShape.FullRoundedCorner,
         label = {
           Column() {
               Text(
@@ -603,7 +611,7 @@
         modifier = Modifier.padding(start = 16.dp)
       )
     },
-    shape = MaterialTheme.shapes.large,
+    shape = EntryShape.FullRoundedCorner,
     label = {
       Column() {
         Text(
@@ -637,7 +645,7 @@
         modifier = Modifier.padding(start = 18.dp)
       )
     },
-    shape = MaterialTheme.shapes.large,
+    shape = EntryShape.FullRoundedCorner,
     label = {
       Column() {
         Text(
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/ui/theme/AndroidColorScheme.kt b/packages/CredentialManager/src/com/android/credentialmanager/ui/theme/AndroidColorScheme.kt
new file mode 100644
index 0000000..e0954ad
--- /dev/null
+++ b/packages/CredentialManager/src/com/android/credentialmanager/ui/theme/AndroidColorScheme.kt
@@ -0,0 +1,50 @@
+/*
+ * 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.credentialmanager.ui.theme
+
+import android.annotation.ColorInt
+import android.content.Context
+import androidx.compose.runtime.staticCompositionLocalOf
+import androidx.compose.ui.graphics.Color
+import com.android.internal.R
+
+/** CompositionLocal used to pass [AndroidColorScheme] down the tree. */
+val LocalAndroidColorScheme =
+    staticCompositionLocalOf<AndroidColorScheme> {
+        throw IllegalStateException(
+            "No AndroidColorScheme configured. Make sure to use LocalAndroidColorScheme in a " +
+                    "Composable surrounded by a CredentialSelectorTheme {}."
+        )
+    }
+
+/**
+ * The Android color scheme.
+ *
+ * Important: Use M3 colors from MaterialTheme.colorScheme whenever possible instead. In the future,
+ * most of the colors in this class will be removed in favor of their M3 counterpart.
+ */
+class AndroidColorScheme internal constructor(context: Context) {
+
+    val colorSurfaceHighlight = getColor(context, R.attr.colorSurfaceHighlight)
+
+    private fun getColor(context: Context, attr: Int): Color {
+        val ta = context.obtainStyledAttributes(intArrayOf(attr))
+        @ColorInt val color = ta.getColor(0, 0)
+        ta.recycle()
+        return Color(color)
+    }
+}
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/ui/theme/Shape.kt b/packages/CredentialManager/src/com/android/credentialmanager/ui/theme/Shape.kt
index 5ea6993..32def89 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/ui/theme/Shape.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/ui/theme/Shape.kt
@@ -9,3 +9,8 @@
   medium = RoundedCornerShape(20.dp),
   large = RoundedCornerShape(0.dp)
 )
+
+object EntryShape {
+  val TopRoundedCorner = RoundedCornerShape(28.dp, 28.dp, 0.dp, 0.dp)
+  val FullRoundedCorner = RoundedCornerShape(28.dp, 28.dp, 28.dp, 28.dp)
+}
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/ui/theme/Theme.kt b/packages/CredentialManager/src/com/android/credentialmanager/ui/theme/Theme.kt
index 248df92..3ca0e44 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/ui/theme/Theme.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/ui/theme/Theme.kt
@@ -2,37 +2,37 @@
 
 import androidx.compose.foundation.isSystemInDarkTheme
 import androidx.compose.material3.MaterialTheme
-import androidx.compose.material3.lightColorScheme
-import androidx.compose.material3.darkColorScheme
+import androidx.compose.material3.dynamicDarkColorScheme
+import androidx.compose.material3.dynamicLightColorScheme
 import androidx.compose.runtime.Composable
-
-private val AppDarkColorScheme = darkColorScheme(
-  primary = Purple200,
-  secondary = Purple700,
-  tertiary = Teal200
-)
-
-private val AppLightColorScheme = lightColorScheme(
-  primary = Purple500,
-  secondary = Purple700,
-  tertiary = Teal200
-)
+import androidx.compose.runtime.CompositionLocalProvider
+import androidx.compose.ui.platform.LocalContext
 
 @Composable
 fun CredentialSelectorTheme(
   darkTheme: Boolean = isSystemInDarkTheme(),
   content: @Composable () -> Unit
 ) {
-  val AppColorScheme = if (darkTheme) {
-    AppDarkColorScheme
-  } else {
-    AppLightColorScheme
-  }
+  val context = LocalContext.current
+
+  val colorScheme =
+    if (darkTheme) {
+      dynamicDarkColorScheme(context)
+    } else {
+      dynamicLightColorScheme(context)
+    }
+  val androidColorScheme = AndroidColorScheme(context)
+  val typography = Typography
 
   MaterialTheme(
-    colorScheme = AppColorScheme,
-    typography = Typography,
-    shapes = Shapes,
-    content = content
-  )
+    colorScheme,
+    typography = typography,
+    shapes = Shapes
+  ) {
+    CompositionLocalProvider(
+      LocalAndroidColorScheme provides androidColorScheme,
+    ) {
+      content()
+    }
+  }
 }
diff --git a/packages/PrintRecommendationService/src/com/android/printservice/recommendation/plugin/google/CloudPrintPlugin.java b/packages/PrintRecommendationService/src/com/android/printservice/recommendation/plugin/google/CloudPrintPlugin.java
index 93e6271..3029d10 100644
--- a/packages/PrintRecommendationService/src/com/android/printservice/recommendation/plugin/google/CloudPrintPlugin.java
+++ b/packages/PrintRecommendationService/src/com/android/printservice/recommendation/plugin/google/CloudPrintPlugin.java
@@ -55,18 +55,15 @@
     private static final String PRIVET_SERVICE = "_privet._tcp";
 
     /** The required mDNS service types */
-    private static final Set<String> PRINTER_SERVICE_TYPE = new HashSet<String>() {{
-        // Not checking _printer_._sub
-        add(PRIVET_SERVICE);
-    }};
+    private static final Set<String> PRINTER_SERVICE_TYPE = Set.of(
+            PRIVET_SERVICE); // Not checking _printer_._sub
 
     /** All possible connection states */
-    private static final Set<String> POSSIBLE_CONNECTION_STATES = new HashSet<String>() {{
-        add("online");
-        add("offline");
-        add("connecting");
-        add("not-configured");
-    }};
+    private static final Set<String> POSSIBLE_CONNECTION_STATES = Set.of(
+            "online",
+            "offline",
+            "connecting",
+            "not-configured");
 
     private static final byte SUPPORTED_TXTVERS = '1';
 
diff --git a/packages/PrintRecommendationService/src/com/android/printservice/recommendation/plugin/mdnsFilter/MDNSFilterPlugin.java b/packages/PrintRecommendationService/src/com/android/printservice/recommendation/plugin/mdnsFilter/MDNSFilterPlugin.java
index 34e7e3d..0c5de27 100644
--- a/packages/PrintRecommendationService/src/com/android/printservice/recommendation/plugin/mdnsFilter/MDNSFilterPlugin.java
+++ b/packages/PrintRecommendationService/src/com/android/printservice/recommendation/plugin/mdnsFilter/MDNSFilterPlugin.java
@@ -37,9 +37,7 @@
 public class MDNSFilterPlugin implements PrintServicePlugin {
 
     /** The mDNS service types supported */
-    private static final Set<String> PRINTER_SERVICE_TYPES = new HashSet<String>() {{
-        add("_ipp._tcp");
-    }};
+    private static final Set<String> PRINTER_SERVICE_TYPES = Set.of("_ipp._tcp");
 
     /**
      * The printer filter for {@link MDNSFilteredDiscovery} passing only mDNS results
diff --git a/packages/PrintRecommendationService/src/com/android/printservice/recommendation/plugin/samsung/PrinterFilterMopria.java b/packages/PrintRecommendationService/src/com/android/printservice/recommendation/plugin/samsung/PrinterFilterMopria.java
index d03bb1d..b9983c3 100644
--- a/packages/PrintRecommendationService/src/com/android/printservice/recommendation/plugin/samsung/PrinterFilterMopria.java
+++ b/packages/PrintRecommendationService/src/com/android/printservice/recommendation/plugin/samsung/PrinterFilterMopria.java
@@ -23,7 +23,6 @@
 import com.android.printservice.recommendation.util.MDNSFilteredDiscovery;
 import com.android.printservice.recommendation.util.MDNSUtils;
 
-import java.util.HashSet;
 import java.util.Set;
 
 /**
@@ -32,10 +31,7 @@
 class PrinterFilterMopria implements MDNSFilteredDiscovery.PrinterFilter {
     private static final String TAG = "PrinterFilterMopria";
 
-    static final Set<String> MOPRIA_MDNS_SERVICES = new HashSet<String>() {{
-        add("_ipp._tcp");
-        add("_ipps._tcp");
-    }};
+    static final Set<String> MOPRIA_MDNS_SERVICES = Set.of("_ipp._tcp", "_ipps._tcp");
 
     private static final String PDL__PDF = "application/pdf";
     private static final String PDL__PCLM = "application/PCLm";
diff --git a/packages/PrintRecommendationService/src/com/android/printservice/recommendation/plugin/samsung/PrinterFilterSamsung.java b/packages/PrintRecommendationService/src/com/android/printservice/recommendation/plugin/samsung/PrinterFilterSamsung.java
index b9b9098..680dd84 100644
--- a/packages/PrintRecommendationService/src/com/android/printservice/recommendation/plugin/samsung/PrinterFilterSamsung.java
+++ b/packages/PrintRecommendationService/src/com/android/printservice/recommendation/plugin/samsung/PrinterFilterSamsung.java
@@ -25,7 +25,6 @@
 import com.android.printservice.recommendation.util.MDNSFilteredDiscovery;
 import com.android.printservice.recommendation.util.MDNSUtils;
 
-import java.util.HashSet;
 import java.util.Locale;
 import java.util.Map;
 import java.util.Set;
@@ -36,9 +35,7 @@
 class PrinterFilterSamsung implements MDNSFilteredDiscovery.PrinterFilter {
     private static final String TAG = "PrinterFilterSamsung";
 
-    static final Set<String> SAMSUNG_MDNS_SERVICES = new HashSet<String>() {{
-        add("_pdl-datastream._tcp");
-    }};
+    static final Set<String> SAMSUNG_MDNS_SERVICES = Set.of("_pdl-datastream._tcp");
 
     private static final String[] NOT_SUPPORTED_MODELS = new String[]{
             "SCX-5x15",
@@ -57,9 +54,7 @@
     private static final String ATTR_PRODUCT = "product";
     private static final String ATTR_TY = "ty";
 
-    private static Set<String> SAMUNG_VENDOR_SET = new HashSet<String>() {{
-        add("samsung");
-    }};
+    private static final Set<String> SAMUNG_VENDOR_SET = Set.of("samsung");
 
     @Override
     public boolean matchesCriteria(NsdServiceInfo nsdServiceInfo) {
diff --git a/packages/PrintRecommendationService/src/com/android/printservice/recommendation/plugin/samsung/SamsungRecommendationPlugin.java b/packages/PrintRecommendationService/src/com/android/printservice/recommendation/plugin/samsung/SamsungRecommendationPlugin.java
index ae1bdce..cbd5833 100644
--- a/packages/PrintRecommendationService/src/com/android/printservice/recommendation/plugin/samsung/SamsungRecommendationPlugin.java
+++ b/packages/PrintRecommendationService/src/com/android/printservice/recommendation/plugin/samsung/SamsungRecommendationPlugin.java
@@ -29,10 +29,11 @@
 import java.util.Set;
 
 public class SamsungRecommendationPlugin implements PrintServicePlugin {
-    private static final Set<String> ALL_MDNS_SERVICES = new HashSet<String>() {{
-        addAll(PrinterFilterMopria.MOPRIA_MDNS_SERVICES);
-        addAll(PrinterFilterSamsung.SAMSUNG_MDNS_SERVICES);
-    }};
+    private static final Set<String> ALL_MDNS_SERVICES = new HashSet<String>();
+    static {
+        ALL_MDNS_SERVICES.addAll(PrinterFilterMopria.MOPRIA_MDNS_SERVICES);
+        ALL_MDNS_SERVICES.addAll(PrinterFilterSamsung.SAMSUNG_MDNS_SERVICES);
+    }
 
     private final @NonNull Context mContext;
     private final @NonNull MDNSFilteredDiscovery mMDNSFilteredDiscovery;
diff --git a/packages/PrintSpooler/src/com/android/printspooler/widget/PrintContentView.java b/packages/PrintSpooler/src/com/android/printspooler/widget/PrintContentView.java
index 00b3736..b0aa8f1 100644
--- a/packages/PrintSpooler/src/com/android/printspooler/widget/PrintContentView.java
+++ b/packages/PrintSpooler/src/com/android/printspooler/widget/PrintContentView.java
@@ -402,7 +402,7 @@
 
         @Override
         public void onViewPositionChanged(View changedView, int left, int top, int dx, int dy) {
-            if ((isOptionsClosed() || isOptionsClosed()) && dy <= 0) {
+            if (isOptionsClosed() && dy <= 0) {
                 return;
             }
 
diff --git a/packages/SettingsLib/OWNERS b/packages/SettingsLib/OWNERS
index cb0ba4e..a53782a 100644
--- a/packages/SettingsLib/OWNERS
+++ b/packages/SettingsLib/OWNERS
@@ -3,9 +3,9 @@
 edgarwang@google.com
 emilychuang@google.com
 evanlaird@google.com
+hanxu@google.com
 juliacr@google.com
 leifhendrik@google.com
-tmfang@google.com
 virgild@google.com
 ykhung@google.com
 
diff --git a/packages/SettingsLib/Spa/gallery/AndroidManifest.xml b/packages/SettingsLib/Spa/gallery/AndroidManifest.xml
index 50cab84..1e52aaf 100644
--- a/packages/SettingsLib/Spa/gallery/AndroidManifest.xml
+++ b/packages/SettingsLib/Spa/gallery/AndroidManifest.xml
@@ -41,6 +41,20 @@
             android:exported="false">
         </provider>
 
+        <provider android:name="com.android.settingslib.spa.framework.SpaSliceProvider"
+            android:authorities="com.android.spa.gallery.slice.provider"
+            android:exported="true" >
+            <intent-filter>
+                <action android:name="android.intent.action.VIEW" />
+                <category android:name="android.app.slice.category.SLICE" />
+            </intent-filter>
+        </provider>
+
+        <receiver
+            android:name="com.android.settingslib.spa.framework.SpaSliceBroadcastReceiver"
+            android:exported="false">
+        </receiver>
+
         <activity
             android:name="com.android.settingslib.spa.framework.debug.BlankActivity"
             android:exported="true">
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/GallerySpaEnvironment.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/GallerySpaEnvironment.kt
index 016b27f..941e770 100644
--- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/GallerySpaEnvironment.kt
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/GallerySpaEnvironment.kt
@@ -17,6 +17,7 @@
 package com.android.settingslib.spa.gallery
 
 import android.content.Context
+import com.android.settingslib.spa.framework.SpaSliceBroadcastReceiver
 import com.android.settingslib.spa.framework.common.LocalLogger
 import com.android.settingslib.spa.framework.common.SettingsPageProviderRepository
 import com.android.settingslib.spa.framework.common.SpaEnvironment
@@ -79,8 +80,8 @@
     }
 
     override val browseActivityClass = GalleryMainActivity::class.java
-
+    override val sliceBroadcastReceiverClass = SpaSliceBroadcastReceiver::class.java
     override val searchProviderAuthorities = "com.android.spa.gallery.search.provider"
-
+    override val sliceProviderAuthorities = "com.android.spa.gallery.slice.provider"
     override val logger = LocalLogger()
 }
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/PreferencePage.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/PreferencePage.kt
index 26e59ff..ff89f2b 100644
--- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/PreferencePage.kt
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/PreferencePage.kt
@@ -27,6 +27,7 @@
 import androidx.compose.ui.res.stringResource
 import androidx.compose.ui.tooling.preview.Preview
 import com.android.settingslib.spa.framework.common.EntrySearchData
+import com.android.settingslib.spa.framework.common.EntrySliceData
 import com.android.settingslib.spa.framework.common.EntryStatusData
 import com.android.settingslib.spa.framework.common.SettingsEntry
 import com.android.settingslib.spa.framework.common.SettingsEntryBuilder
@@ -37,6 +38,7 @@
 import com.android.settingslib.spa.framework.theme.SettingsTheme
 import com.android.settingslib.spa.gallery.R
 import com.android.settingslib.spa.gallery.SettingsPageProviderEnum
+import com.android.settingslib.spa.gallery.preference.PreferencePageModel.Companion.ASYNC_PREFERENCE_SUMMARY
 import com.android.settingslib.spa.gallery.preference.PreferencePageModel.Companion.ASYNC_PREFERENCE_TITLE
 import com.android.settingslib.spa.gallery.preference.PreferencePageModel.Companion.AUTO_UPDATE_PREFERENCE_TITLE
 import com.android.settingslib.spa.gallery.preference.PreferencePageModel.Companion.DISABLE_PREFERENCE_SUMMARY
@@ -46,10 +48,15 @@
 import com.android.settingslib.spa.gallery.preference.PreferencePageModel.Companion.SIMPLE_PREFERENCE_KEYWORDS
 import com.android.settingslib.spa.gallery.preference.PreferencePageModel.Companion.SIMPLE_PREFERENCE_SUMMARY
 import com.android.settingslib.spa.gallery.preference.PreferencePageModel.Companion.SIMPLE_PREFERENCE_TITLE
+import com.android.settingslib.spa.slice.createBrowsePendingIntent
+import com.android.settingslib.spa.slice.provider.createDemoActionSlice
+import com.android.settingslib.spa.slice.provider.createDemoBrowseSlice
+import com.android.settingslib.spa.slice.provider.createDemoSlice
 import com.android.settingslib.spa.widget.preference.Preference
 import com.android.settingslib.spa.widget.preference.PreferenceModel
 import com.android.settingslib.spa.widget.preference.SimplePreferenceMacro
 import com.android.settingslib.spa.widget.ui.SettingsIcon
+import kotlinx.coroutines.delay
 
 private const val TAG = "PreferencePage"
 
@@ -134,6 +141,26 @@
                             override val enabled = model.asyncEnable
                         }
                     )
+                }
+                .setSliceDataFn { sliceUri, _ ->
+                    val createSliceImpl = { s: String ->
+                        createDemoBrowseSlice(
+                            sliceUri = sliceUri,
+                            title = ASYNC_PREFERENCE_TITLE,
+                            summary = s,
+                        )
+                    }
+                    return@setSliceDataFn object : EntrySliceData() {
+                        init {
+                            postValue(createSliceImpl("(loading)"))
+                        }
+
+                        override suspend fun asyncRunner() {
+                            spaLogger.message(TAG, "Async entry loading")
+                            delay(2000L)
+                            postValue(createSliceImpl(ASYNC_PREFERENCE_SUMMARY))
+                        }
+                    }
                 }.build()
         )
         entryList.add(
@@ -152,6 +179,27 @@
                             }
                         }
                     )
+                }.setSliceDataFn { sliceUri, args ->
+                    val createSliceImpl = { v: Int ->
+                        createDemoActionSlice(
+                            sliceUri = sliceUri,
+                            title = MANUAL_UPDATE_PREFERENCE_TITLE,
+                            summary = "manual update value $v",
+                        )
+                    }
+
+                    return@setSliceDataFn object : EntrySliceData() {
+                        private var tick = args?.getString("init")?.toInt() ?: 0
+
+                        init {
+                            postValue(createSliceImpl(tick))
+                        }
+
+                        override suspend fun asyncAction() {
+                            tick++
+                            postValue(createSliceImpl(tick))
+                        }
+                    }
                 }.build()
         )
         entryList.add(
@@ -170,7 +218,33 @@
                         }
                     )
                 }
-                .build()
+                .setSliceDataFn { sliceUri, args ->
+                    val createSliceImpl = { v: Int ->
+                        createDemoBrowseSlice(
+                            sliceUri = sliceUri,
+                            title = AUTO_UPDATE_PREFERENCE_TITLE,
+                            summary = "auto update value $v",
+                        )
+                    }
+
+                    return@setSliceDataFn object : EntrySliceData() {
+                        private var tick = args?.getString("init")?.toInt() ?: 0
+
+                        init {
+                            postValue(createSliceImpl(tick))
+                        }
+
+                        override suspend fun asyncRunner() {
+                            spaLogger.message(TAG, "autoUpdater.active")
+                            while (true) {
+                                delay(1000L)
+                                tick++
+                                spaLogger.message(TAG, "autoUpdater.value $tick")
+                                postValue(createSliceImpl(tick))
+                            }
+                        }
+                    }
+                }.build()
         )
 
         return entryList
@@ -201,6 +275,22 @@
                     clickRoute = SettingsPageProviderEnum.PREFERENCE.name
                 )
             }
+            .setSliceDataFn { sliceUri, _ ->
+                val intent = owner.createBrowseIntent()?.createBrowsePendingIntent()
+                    ?: return@setSliceDataFn null
+                return@setSliceDataFn object : EntrySliceData() {
+                    init {
+                        postValue(
+                            createDemoSlice(
+                                sliceUri = sliceUri,
+                                title = PAGE_TITLE,
+                                summary = "Injected Entry",
+                                intent = intent,
+                            )
+                        )
+                    }
+                }
+            }
     }
 
     override fun getTitle(arguments: Bundle?): String {
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/PreferencePageModel.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/PreferencePageModel.kt
index d874417..fc6f10f 100644
--- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/PreferencePageModel.kt
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/PreferencePageModel.kt
@@ -44,7 +44,7 @@
         const val DISABLE_PREFERENCE_TITLE = "Disabled"
         const val DISABLE_PREFERENCE_SUMMARY = "Disabled summary"
         const val ASYNC_PREFERENCE_TITLE = "Async Preference"
-        private const val ASYNC_PREFERENCE_SUMMARY = "Async summary"
+        const val ASYNC_PREFERENCE_SUMMARY = "Async summary"
         const val MANUAL_UPDATE_PREFERENCE_TITLE = "Manual Updater"
         const val AUTO_UPDATE_PREFERENCE_TITLE = "Auto Updater"
         val SIMPLE_PREFERENCE_KEYWORDS = listOf("simple keyword1", "simple keyword2")
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/SpaSliceBroadcastReceiver.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/SpaSliceBroadcastReceiver.kt
index 8c038c8..58131e6 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/SpaSliceBroadcastReceiver.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/SpaSliceBroadcastReceiver.kt
@@ -23,7 +23,7 @@
 
 class SpaSliceBroadcastReceiver : BroadcastReceiver() {
     override fun onReceive(context: Context?, intent: Intent?) {
-        val sliceRepository = SpaEnvironmentFactory.instance.sliceDataRepository
+        val sliceRepository by SpaEnvironmentFactory.instance.sliceDataRepository
         val sliceUri = intent?.data ?: return
         val sliceData = sliceRepository.getActiveSliceData(sliceUri) ?: return
         sliceData.doAction()
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/SpaSliceProvider.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/SpaSliceProvider.kt
index d800e50..faa04fd 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/SpaSliceProvider.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/SpaSliceProvider.kt
@@ -32,7 +32,8 @@
 class SpaSliceProvider : SliceProvider(), Observer<Slice?> {
     private fun getOrPutSliceData(sliceUri: Uri): EntrySliceData? {
         if (!SpaEnvironmentFactory.isReady()) return null
-        return SpaEnvironmentFactory.instance.sliceDataRepository.getOrBuildSliceData(sliceUri)
+        val sliceRepository by SpaEnvironmentFactory.instance.sliceDataRepository
+        return sliceRepository.getOrBuildSliceData(sliceUri)
     }
 
     override fun onBindSlice(sliceUri: Uri): Slice? {
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SpaEnvironment.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SpaEnvironment.kt
index 9bb06f0..945add4 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SpaEnvironment.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SpaEnvironment.kt
@@ -65,7 +65,7 @@
 
     val entryRepository = lazy { SettingsEntryRepository(pageProviderRepository.value) }
 
-    val sliceDataRepository = SettingsSliceDataRepository()
+    val sliceDataRepository = lazy { SettingsSliceDataRepository(entryRepository.value) }
 
     // In Robolectric test, applicationContext is not available. Use context as fallback.
     val appContext: Context = context.applicationContext ?: context
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/slice/SettingsSliceDataRepository.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/slice/SettingsSliceDataRepository.kt
index d8d2378..14855a8 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/slice/SettingsSliceDataRepository.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/slice/SettingsSliceDataRepository.kt
@@ -19,11 +19,11 @@
 import android.net.Uri
 import android.util.Log
 import com.android.settingslib.spa.framework.common.EntrySliceData
-import com.android.settingslib.spa.framework.common.SpaEnvironmentFactory
+import com.android.settingslib.spa.framework.common.SettingsEntryRepository
 
 private const val TAG = "SliceDataRepository"
 
-class SettingsSliceDataRepository {
+class SettingsSliceDataRepository(private val entryRepository: SettingsEntryRepository) {
     // The map of slice uri to its EntrySliceData, a.k.a. LiveData<Slice?>
     private val sliceDataMap: MutableMap<String, EntrySliceData> = mutableMapOf()
 
@@ -46,13 +46,10 @@
 
     private fun buildLiveDataImpl(sliceUri: Uri): EntrySliceData? {
         Log.d(TAG, "buildLiveData: $sliceUri")
-        if (!SpaEnvironmentFactory.isReady()) return null
 
-        val entryRepository by SpaEnvironmentFactory.instance.entryRepository
         val entryId = sliceUri.getEntryId() ?: return null
         val entry = entryRepository.getEntry(entryId) ?: return null
         if (!entry.hasSliceSupport) return null
-
         val arguments = sliceUri.getRuntimeArguments()
         return entry.getSliceData(runtimeArguments = arguments, sliceUri = sliceUri)
     }
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppListModel.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppListModel.kt
index 373b57f..a7122d0 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppListModel.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppListModel.kt
@@ -54,6 +54,14 @@
     )
 
     /**
+     * Gets the group title of this item.
+     *
+     * Note: Items should be sorted by group in [getComparator] first, this [getGroupTitle] will not
+     * change the list order.
+     */
+    fun getGroupTitle(option: Int, record: T): String? = null
+
+    /**
      * Gets the summary for the given app record.
      *
      * @return null if no summary should be displayed.
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/PackageManagers.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/PackageManagers.kt
index 215d22c..cb0bfc6 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/PackageManagers.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/PackageManagers.kt
@@ -26,43 +26,76 @@
 
 private const val TAG = "PackageManagers"
 
-object PackageManagers {
-    private val iPackageManager by lazy { AppGlobals.getPackageManager() }
-
-    fun getPackageInfoAsUser(packageName: String, userId: Int): PackageInfo? =
-        getPackageInfoAsUser(packageName, 0, userId)
-
-    fun getApplicationInfoAsUser(packageName: String, userId: Int): ApplicationInfo? =
-        PackageManager.getApplicationInfoAsUserCached(packageName, 0, userId)
+interface IPackageManagers {
+    fun getPackageInfoAsUser(packageName: String, userId: Int): PackageInfo?
+    fun getApplicationInfoAsUser(packageName: String, userId: Int): ApplicationInfo?
 
     /** Checks whether a package is installed for a given user. */
-    fun isPackageInstalledAsUser(packageName: String, userId: Int): Boolean =
+    fun isPackageInstalledAsUser(packageName: String, userId: Int): Boolean
+    fun ApplicationInfo.hasRequestPermission(permission: String): Boolean
+
+    /** Checks whether a permission is currently granted to the application. */
+    fun ApplicationInfo.hasGrantPermission(permission: String): Boolean
+
+    suspend fun getAppOpPermissionPackages(userId: Int, permission: String): Set<String>
+    fun getPackageInfoAsUser(packageName: String, flags: Int, userId: Int): PackageInfo?
+}
+
+object PackageManagers : IPackageManagers by PackageManagersImpl(PackageManagerWrapperImpl)
+
+internal interface PackageManagerWrapper {
+    fun getPackageInfoAsUserCached(
+        packageName: String,
+        flags: Long,
+        userId: Int,
+    ): PackageInfo?
+}
+
+internal object PackageManagerWrapperImpl : PackageManagerWrapper {
+    override fun getPackageInfoAsUserCached(
+        packageName: String,
+        flags: Long,
+        userId: Int,
+    ): PackageInfo? = PackageManager.getPackageInfoAsUserCached(packageName, flags, userId)
+}
+
+internal class PackageManagersImpl(
+    private val packageManagerWrapper: PackageManagerWrapper,
+) : IPackageManagers {
+    private val iPackageManager by lazy { AppGlobals.getPackageManager() }
+
+    override fun getPackageInfoAsUser(packageName: String, userId: Int): PackageInfo? =
+        getPackageInfoAsUser(packageName, 0, userId)
+
+    override fun getApplicationInfoAsUser(packageName: String, userId: Int): ApplicationInfo? =
+        PackageManager.getApplicationInfoAsUserCached(packageName, 0, userId)
+
+    override fun isPackageInstalledAsUser(packageName: String, userId: Int): Boolean =
         getApplicationInfoAsUser(packageName, userId)?.hasFlag(ApplicationInfo.FLAG_INSTALLED)
             ?: false
 
-    fun ApplicationInfo.hasRequestPermission(permission: String): Boolean {
+    override fun ApplicationInfo.hasRequestPermission(permission: String): Boolean {
         val packageInfo = getPackageInfoAsUser(packageName, PackageManager.GET_PERMISSIONS, userId)
         return packageInfo?.requestedPermissions?.let {
             permission in it
         } ?: false
     }
 
-    fun ApplicationInfo.hasGrantPermission(permission: String): Boolean {
+    override fun ApplicationInfo.hasGrantPermission(permission: String): Boolean {
         val packageInfo = getPackageInfoAsUser(packageName, PackageManager.GET_PERMISSIONS, userId)
-            ?: return false
-        val index = packageInfo.requestedPermissions.indexOf(permission)
+        val index = packageInfo?.requestedPermissions?.indexOf(permission) ?: return false
         return index >= 0 &&
             packageInfo.requestedPermissionsFlags[index].hasFlag(REQUESTED_PERMISSION_GRANTED)
     }
 
-    suspend fun getAppOpPermissionPackages(userId: Int, permission: String): Set<String> =
+    override suspend fun getAppOpPermissionPackages(userId: Int, permission: String): Set<String> =
         iPackageManager.getAppOpPermissionPackages(permission, userId).asIterable().asyncFilter {
             iPackageManager.isPackageAvailable(it, userId)
         }.toSet()
 
-    fun getPackageInfoAsUser(packageName: String, flags: Int, userId: Int): PackageInfo? =
+    override fun getPackageInfoAsUser(packageName: String, flags: Int, userId: Int): PackageInfo? =
         try {
-            PackageManager.getPackageInfoAsUserCached(packageName, flags.toLong(), userId)
+            packageManagerWrapper.getPackageInfoAsUserCached(packageName, flags.toLong(), userId)
         } catch (e: PackageManager.NameNotFoundException) {
             Log.w(TAG, "getPackageInfoAsUserCached() failed", e)
             null
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppList.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppList.kt
index 7f5fe9f..681eb1c 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppList.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppList.kt
@@ -34,9 +34,11 @@
 import com.android.settingslib.spa.framework.compose.TimeMeasurer.Companion.rememberTimeMeasurer
 import com.android.settingslib.spa.framework.compose.rememberLazyListStateAndHideKeyboardWhenStartScroll
 import com.android.settingslib.spa.framework.compose.toState
+import com.android.settingslib.spa.widget.ui.CategoryTitle
 import com.android.settingslib.spa.widget.ui.PlaceholderTitle
 import com.android.settingslib.spaprivileged.R
 import com.android.settingslib.spaprivileged.framework.compose.DisposableBroadcastReceiverAsUser
+import com.android.settingslib.spaprivileged.model.app.AppEntry
 import com.android.settingslib.spaprivileged.model.app.AppListConfig
 import com.android.settingslib.spaprivileged.model.app.AppListData
 import com.android.settingslib.spaprivileged.model.app.AppListModel
@@ -100,17 +102,28 @@
             }
 
             items(count = list.size, key = { option to list[it].record.app.packageName }) {
+                remember(list) { listModel.getGroupTitleIfFirst(option, list, it) }
+                    ?.let { group -> CategoryTitle(title = group) }
+
                 val appEntry = list[it]
                 val summary = listModel.getSummary(option, appEntry.record) ?: "".toState()
-                val itemModel = remember(appEntry) {
+                appItem(remember(appEntry) {
                     AppListItemModel(appEntry.record, appEntry.label, summary)
-                }
-                appItem(itemModel)
+                })
             }
         }
     }
 }
 
+/** Returns group title if this is the first item of the group. */
+private fun <T : AppRecord> AppListModel<T>.getGroupTitleIfFirst(
+    option: Int,
+    list: List<AppEntry<T>>,
+    index: Int,
+): String? = getGroupTitle(option, list[index].record)?.takeIf {
+    index == 0 || it != getGroupTitle(option, list[index - 1].record)
+}
+
 @Composable
 private fun <T : AppRecord> loadAppListData(
     config: AppListConfig,
diff --git a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/PackageManagersTest.kt b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/PackageManagersTest.kt
new file mode 100644
index 0000000..6c31f4b
--- /dev/null
+++ b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/PackageManagersTest.kt
@@ -0,0 +1,117 @@
+/*
+ * 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.settingslib.spaprivileged.model.app
+
+import android.content.pm.ApplicationInfo
+import android.content.pm.PackageInfo
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+class PackageManagersTest {
+
+    private val fakePackageManagerWrapper = FakePackageManagerWrapper()
+
+    private val packageManagersImpl = PackageManagersImpl(fakePackageManagerWrapper)
+
+    @Test
+    fun hasGrantPermission_packageInfoIsNull_returnFalse() {
+        fakePackageManagerWrapper.fakePackageInfo = null
+
+        val hasGrantPermission = with(packageManagersImpl) {
+            APP.hasGrantPermission(PERMISSION_A)
+        }
+
+        assertThat(hasGrantPermission).isFalse()
+    }
+
+    @Test
+    fun hasGrantPermission_requestedPermissionsIsNull_returnFalse() {
+        fakePackageManagerWrapper.fakePackageInfo = PackageInfo()
+
+        val hasGrantPermission = with(packageManagersImpl) {
+            APP.hasGrantPermission(PERMISSION_A)
+        }
+
+        assertThat(hasGrantPermission).isFalse()
+    }
+
+    @Test
+    fun hasGrantPermission_notRequested_returnFalse() {
+        fakePackageManagerWrapper.fakePackageInfo = PackageInfo().apply {
+            requestedPermissions = arrayOf(PERMISSION_B)
+            requestedPermissionsFlags = intArrayOf(PackageInfo.REQUESTED_PERMISSION_GRANTED)
+        }
+
+        val hasGrantPermission = with(packageManagersImpl) {
+            APP.hasGrantPermission(PERMISSION_A)
+        }
+
+        assertThat(hasGrantPermission).isFalse()
+    }
+
+    @Test
+    fun hasGrantPermission_notGranted_returnFalse() {
+        fakePackageManagerWrapper.fakePackageInfo = PackageInfo().apply {
+            requestedPermissions = arrayOf(PERMISSION_A, PERMISSION_B)
+            requestedPermissionsFlags = intArrayOf(0, PackageInfo.REQUESTED_PERMISSION_GRANTED)
+        }
+
+        val hasGrantPermission = with(packageManagersImpl) {
+            APP.hasGrantPermission(PERMISSION_A)
+        }
+
+        assertThat(hasGrantPermission).isFalse()
+    }
+
+    @Test
+    fun hasGrantPermission_granted_returnTrue() {
+        fakePackageManagerWrapper.fakePackageInfo = PackageInfo().apply {
+            requestedPermissions = arrayOf(PERMISSION_A, PERMISSION_B)
+            requestedPermissionsFlags = intArrayOf(PackageInfo.REQUESTED_PERMISSION_GRANTED, 0)
+        }
+
+        val hasGrantPermission = with(packageManagersImpl) {
+            APP.hasGrantPermission(PERMISSION_A)
+        }
+
+        assertThat(hasGrantPermission).isTrue()
+    }
+
+    private inner class FakePackageManagerWrapper : PackageManagerWrapper {
+        var fakePackageInfo: PackageInfo? = null
+
+        override fun getPackageInfoAsUserCached(
+            packageName: String,
+            flags: Long,
+            userId: Int,
+        ): PackageInfo? = fakePackageInfo
+    }
+
+    private companion object {
+        const val PACKAGE_NAME = "packageName"
+        const val PERMISSION_A = "permission.A"
+        const val PERMISSION_B = "permission.B"
+        const val UID = 123
+        val APP = ApplicationInfo().apply {
+            packageName = PACKAGE_NAME
+            uid = UID
+        }
+    }
+}
diff --git a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppListTest.kt b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppListTest.kt
index 80c4eac..9f20c78 100644
--- a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppListTest.kt
+++ b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppListTest.kt
@@ -58,26 +58,43 @@
 
     @Test
     fun couldShowAppItem() {
-        setContent(appEntries = listOf(APP_ENTRY))
+        setContent(appEntries = listOf(APP_ENTRY_A))
 
-        composeTestRule.onNodeWithText(APP_ENTRY.label).assertIsDisplayed()
+        composeTestRule.onNodeWithText(APP_ENTRY_A.label).assertIsDisplayed()
     }
 
     @Test
     fun couldShowHeader() {
-        setContent(header = { Text(HEADER) }, appEntries = listOf(APP_ENTRY))
+        setContent(appEntries = listOf(APP_ENTRY_A), header = { Text(HEADER) })
 
         composeTestRule.onNodeWithText(HEADER).assertIsDisplayed()
     }
 
+    @Test
+    fun whenNotGrouped_groupTitleDoesNotExist() {
+        setContent(appEntries = listOf(APP_ENTRY_A, APP_ENTRY_B), enableGrouping = false)
+
+        composeTestRule.onNodeWithText(GROUP_A).assertDoesNotExist()
+        composeTestRule.onNodeWithText(GROUP_B).assertDoesNotExist()
+    }
+
+    @Test
+    fun whenGrouped_groupTitleDisplayed() {
+        setContent(appEntries = listOf(APP_ENTRY_A, APP_ENTRY_B), enableGrouping = true)
+
+        composeTestRule.onNodeWithText(GROUP_A).assertIsDisplayed()
+        composeTestRule.onNodeWithText(GROUP_B).assertIsDisplayed()
+    }
+
     private fun setContent(
-        header: @Composable () -> Unit = {},
         appEntries: List<AppEntry<TestAppRecord>>,
+        header: @Composable () -> Unit = {},
+        enableGrouping: Boolean = false,
     ) {
         composeTestRule.setContent {
             AppList(
                 config = AppListConfig(userId = USER_ID, showInstantApps = false),
-                listModel = TestAppListModel(),
+                listModel = TestAppListModel(enableGrouping),
                 state = AppListState(
                     showSystem = false.toState(),
                     option = 0.toState(),
@@ -96,17 +113,37 @@
     private companion object {
         const val USER_ID = 0
         const val HEADER = "Header"
-        val APP_ENTRY = AppEntry(
-            record = TestAppRecord(ApplicationInfo()),
-            label = "AAA",
+        const val GROUP_A = "Group A"
+        const val GROUP_B = "Group B"
+        val APP_ENTRY_A = AppEntry(
+            record = TestAppRecord(
+                app = ApplicationInfo().apply {
+                    packageName = "package.name.a"
+                },
+                group = GROUP_A,
+            ),
+            label = "Label A",
+            labelCollationKey = CollationKey("", byteArrayOf()),
+        )
+        val APP_ENTRY_B = AppEntry(
+            record = TestAppRecord(
+                app = ApplicationInfo().apply {
+                    packageName = "package.name.b"
+                },
+                group = GROUP_B,
+            ),
+            label = "Label B",
             labelCollationKey = CollationKey("", byteArrayOf()),
         )
     }
 }
 
-private data class TestAppRecord(override val app: ApplicationInfo) : AppRecord
+private data class TestAppRecord(
+    override val app: ApplicationInfo,
+    val group: String? = null,
+) : AppRecord
 
-private class TestAppListModel : AppListModel<TestAppRecord> {
+private class TestAppListModel(val enableGrouping: Boolean) : AppListModel<TestAppRecord> {
     override fun transform(userIdFlow: Flow<Int>, appListFlow: Flow<List<ApplicationInfo>>) =
         appListFlow.asyncMapItem { TestAppRecord(it) }
 
@@ -118,4 +155,7 @@
         option: Int,
         recordListFlow: Flow<List<TestAppRecord>>,
     ) = recordListFlow
+
+    override fun getGroupTitle(option: Int, record: TestAppRecord) =
+        if (enableGrouping) record.group else null
 }
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothDiscoverableTimeoutReceiver.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothDiscoverableTimeoutReceiver.java
index 6ce72bb..3af64e2 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothDiscoverableTimeoutReceiver.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothDiscoverableTimeoutReceiver.java
@@ -82,4 +82,4 @@
             Log.e(TAG, "localBluetoothAdapter is NULL!!");
         }
     }
-};
+}
diff --git a/packages/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml
index 90fab08..2e4a245 100644
--- a/packages/Shell/AndroidManifest.xml
+++ b/packages/Shell/AndroidManifest.xml
@@ -720,6 +720,57 @@
     <!-- Permission required for CTS test - CtsDeviceLockTestCases -->
     <uses-permission android:name="android.permission.MANAGE_DEVICE_LOCK_STATE" />
 
+    <!-- Permission required for CTS test - CtsAppFgsTestCases -->
+    <uses-permission android:name="android.permission.FOREGROUND_SERVICE_CAMERA" />
+
+    <!-- Permission required for CTS test - CtsAppFgsTestCases -->
+    <uses-permission android:name="android.permission.FOREGROUND_SERVICE_CONNECTED_DEVICE" />
+
+    <!-- Permission required for CTS test - CtsAppFgsTestCases -->
+    <uses-permission android:name="android.permission.FOREGROUND_SERVICE_DATA_SYNC" />
+
+    <!-- Permission required for CTS test - CtsAppFgsTestCases -->
+    <uses-permission android:name="android.permission.FOREGROUND_SERVICE_LOCATION" />
+
+    <!-- Permission required for CTS test - CtsAppFgsTestCases -->
+    <uses-permission android:name="android.permission.FOREGROUND_SERVICE_MEDIA_PLAYBACK" />
+
+    <!-- Permission required for CTS test - CtsAppFgsTestCases -->
+    <uses-permission android:name="android.permission.FOREGROUND_SERVICE_MEDIA_PROJECTION" />
+
+    <!-- Permission required for CTS test - CtsAppFgsTestCases -->
+    <uses-permission android:name="android.permission.FOREGROUND_SERVICE_MICROPHONE" />
+
+    <!-- Permission required for CTS test - CtsAppFgsTestCases -->
+    <uses-permission android:name="android.permission.FOREGROUND_SERVICE_PHONE_CALL" />
+
+    <!-- Permission required for CTS test - CtsAppFgsTestCases -->
+    <uses-permission android:name="android.permission.FOREGROUND_SERVICE_HEALTH" />
+
+    <!-- Permission required for CTS test - CtsAppFgsTestCases -->
+    <uses-permission android:name="android.permission.FOREGROUND_SERVICE_REMOTE_MESSAGING" />
+
+    <!-- Permission required for CTS test - CtsAppFgsTestCases -->
+    <uses-permission android:name="android.permission.FOREGROUND_SERVICE_SYSTEM_EXEMPTED" />
+
+    <!-- Permission required for CTS test - CtsAppFgsTestCases -->
+    <uses-permission android:name="android.permission.FOREGROUND_SERVICE_SPECIAL_USE" />
+
+    <!-- Permissions required for CTS test - CtsAppFgsTestCases -->
+    <uses-permission android:name="android.permission.CAPTURE_MEDIA_OUTPUT" />
+
+    <!-- Permissions required for CTS test - CtsAppFgsTestCases -->
+    <uses-permission android:name="android.permission.CAPTURE_TUNER_AUDIO_INPUT" />
+
+    <!-- Permissions required for CTS test - CtsAppFgsTestCases -->
+    <uses-permission android:name="android.permission.CAPTURE_VOICE_COMMUNICATION_OUTPUT" />
+
+    <!-- Permissions required for CTS test - CtsAppFgsTestCases -->
+    <uses-permission android:name="android.permission.SCHEDULE_EXACT_ALARM" />
+
+    <!-- Permissions required for CTS test - CtsAppFgsTestCases -->
+    <uses-permission android:name="android.permission.USE_EXACT_ALARM" />
+
     <application android:label="@string/app_label"
                 android:theme="@android:style/Theme.DeviceDefault.DayNight"
                 android:defaultToDeviceProtectedStorage="true"
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/user/ui/compose/UserSwitcherScreen.kt b/packages/SystemUI/compose/features/src/com/android/systemui/user/ui/compose/UserSwitcherScreen.kt
deleted file mode 100644
index 4d94bab..0000000
--- a/packages/SystemUI/compose/features/src/com/android/systemui/user/ui/compose/UserSwitcherScreen.kt
+++ /dev/null
@@ -1,392 +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.user.ui.compose
-
-import android.graphics.drawable.Drawable
-import androidx.appcompat.content.res.AppCompatResources
-import androidx.compose.foundation.Image
-import androidx.compose.foundation.background
-import androidx.compose.foundation.border
-import androidx.compose.foundation.clickable
-import androidx.compose.foundation.layout.Arrangement
-import androidx.compose.foundation.layout.Box
-import androidx.compose.foundation.layout.Column
-import androidx.compose.foundation.layout.Row
-import androidx.compose.foundation.layout.Spacer
-import androidx.compose.foundation.layout.fillMaxSize
-import androidx.compose.foundation.layout.heightIn
-import androidx.compose.foundation.layout.padding
-import androidx.compose.foundation.layout.size
-import androidx.compose.foundation.layout.sizeIn
-import androidx.compose.foundation.layout.width
-import androidx.compose.foundation.shape.CircleShape
-import androidx.compose.material3.DropdownMenu
-import androidx.compose.material3.DropdownMenuItem
-import androidx.compose.material3.MaterialTheme
-import androidx.compose.material3.Text
-import androidx.compose.runtime.Composable
-import androidx.compose.runtime.LaunchedEffect
-import androidx.compose.runtime.collectAsState
-import androidx.compose.runtime.getValue
-import androidx.compose.runtime.remember
-import androidx.compose.ui.Alignment
-import androidx.compose.ui.Modifier
-import androidx.compose.ui.draw.alpha
-import androidx.compose.ui.draw.clip
-import androidx.compose.ui.graphics.asImageBitmap
-import androidx.compose.ui.graphics.painter.ColorPainter
-import androidx.compose.ui.platform.LocalConfiguration
-import androidx.compose.ui.platform.LocalContext
-import androidx.compose.ui.platform.LocalDensity
-import androidx.compose.ui.res.colorResource
-import androidx.compose.ui.res.stringResource
-import androidx.compose.ui.text.style.TextOverflow
-import androidx.compose.ui.unit.Dp
-import androidx.compose.ui.unit.dp
-import androidx.core.graphics.drawable.toBitmap
-import com.android.systemui.common.ui.compose.load
-import com.android.systemui.compose.SysUiOutlinedButton
-import com.android.systemui.compose.SysUiTextButton
-import com.android.systemui.compose.features.R
-import com.android.systemui.compose.theme.LocalAndroidColorScheme
-import com.android.systemui.user.ui.viewmodel.UserActionViewModel
-import com.android.systemui.user.ui.viewmodel.UserSwitcherViewModel
-import com.android.systemui.user.ui.viewmodel.UserViewModel
-import java.lang.Integer.min
-import kotlin.math.ceil
-
-@Composable
-fun UserSwitcherScreen(
-    viewModel: UserSwitcherViewModel,
-    onFinished: () -> Unit,
-    modifier: Modifier = Modifier,
-) {
-    val isFinishRequested: Boolean by viewModel.isFinishRequested.collectAsState(false)
-    val users: List<UserViewModel> by viewModel.users.collectAsState(emptyList())
-    val maxUserColumns: Int by viewModel.maximumUserColumns.collectAsState(1)
-    val menuActions: List<UserActionViewModel> by viewModel.menu.collectAsState(emptyList())
-    val isOpenMenuButtonVisible: Boolean by viewModel.isOpenMenuButtonVisible.collectAsState(false)
-    val isMenuVisible: Boolean by viewModel.isMenuVisible.collectAsState(false)
-
-    UserSwitcherScreenStateless(
-        isFinishRequested = isFinishRequested,
-        users = users,
-        maxUserColumns = maxUserColumns,
-        menuActions = menuActions,
-        isOpenMenuButtonVisible = isOpenMenuButtonVisible,
-        isMenuVisible = isMenuVisible,
-        onMenuClosed = viewModel::onMenuClosed,
-        onOpenMenuButtonClicked = viewModel::onOpenMenuButtonClicked,
-        onCancelButtonClicked = viewModel::onCancelButtonClicked,
-        onFinished = {
-            onFinished()
-            viewModel.onFinished()
-        },
-        modifier = modifier,
-    )
-}
-
-@Composable
-private fun UserSwitcherScreenStateless(
-    isFinishRequested: Boolean,
-    users: List<UserViewModel>,
-    maxUserColumns: Int,
-    menuActions: List<UserActionViewModel>,
-    isOpenMenuButtonVisible: Boolean,
-    isMenuVisible: Boolean,
-    onMenuClosed: () -> Unit,
-    onOpenMenuButtonClicked: () -> Unit,
-    onCancelButtonClicked: () -> Unit,
-    onFinished: () -> Unit,
-    modifier: Modifier = Modifier,
-) {
-    LaunchedEffect(isFinishRequested) {
-        if (isFinishRequested) {
-            onFinished()
-        }
-    }
-
-    Box(
-        modifier =
-            modifier
-                .fillMaxSize()
-                .padding(
-                    horizontal = 60.dp,
-                    vertical = 40.dp,
-                ),
-    ) {
-        UserGrid(
-            users = users,
-            maxUserColumns = maxUserColumns,
-            modifier = Modifier.align(Alignment.Center),
-        )
-
-        Buttons(
-            menuActions = menuActions,
-            isOpenMenuButtonVisible = isOpenMenuButtonVisible,
-            isMenuVisible = isMenuVisible,
-            onMenuClosed = onMenuClosed,
-            onOpenMenuButtonClicked = onOpenMenuButtonClicked,
-            onCancelButtonClicked = onCancelButtonClicked,
-            modifier = Modifier.align(Alignment.BottomEnd),
-        )
-    }
-}
-
-@Composable
-private fun UserGrid(
-    users: List<UserViewModel>,
-    maxUserColumns: Int,
-    modifier: Modifier = Modifier,
-) {
-    Column(
-        horizontalAlignment = Alignment.CenterHorizontally,
-        verticalArrangement = Arrangement.spacedBy(44.dp),
-        modifier = modifier,
-    ) {
-        val rowCount = ceil(users.size / maxUserColumns.toFloat()).toInt()
-        (0 until rowCount).forEach { rowIndex ->
-            Row(
-                horizontalArrangement = Arrangement.spacedBy(64.dp),
-                modifier = modifier,
-            ) {
-                val fromIndex = rowIndex * maxUserColumns
-                val toIndex = min(users.size, (rowIndex + 1) * maxUserColumns)
-                users.subList(fromIndex, toIndex).forEach { user ->
-                    UserItem(
-                        viewModel = user,
-                    )
-                }
-            }
-        }
-    }
-}
-
-@Composable
-private fun UserItem(
-    viewModel: UserViewModel,
-) {
-    val onClicked = viewModel.onClicked
-    Column(
-        horizontalAlignment = Alignment.CenterHorizontally,
-        modifier =
-            if (onClicked != null) {
-                    Modifier.clickable { onClicked() }
-                } else {
-                    Modifier
-                }
-                .alpha(viewModel.alpha),
-    ) {
-        Box {
-            UserItemBackground(modifier = Modifier.align(Alignment.Center).size(222.dp))
-
-            UserItemIcon(
-                image = viewModel.image,
-                isSelectionMarkerVisible = viewModel.isSelectionMarkerVisible,
-                modifier = Modifier.align(Alignment.Center).size(222.dp)
-            )
-        }
-
-        // User name
-        val text = viewModel.name.load()
-        if (text != null) {
-            // We use the box to center-align the text vertically as that is not possible with Text
-            // alone.
-            Box(
-                modifier = Modifier.size(width = 222.dp, height = 48.dp),
-            ) {
-                Text(
-                    text = text,
-                    style = MaterialTheme.typography.titleLarge,
-                    color = colorResource(com.android.internal.R.color.system_neutral1_50),
-                    maxLines = 1,
-                    overflow = TextOverflow.Ellipsis,
-                    modifier = Modifier.align(Alignment.Center),
-                )
-            }
-        }
-    }
-}
-
-@Composable
-private fun UserItemBackground(
-    modifier: Modifier = Modifier,
-) {
-    Image(
-        painter = ColorPainter(LocalAndroidColorScheme.current.colorBackground),
-        contentDescription = null,
-        modifier = modifier.clip(CircleShape),
-    )
-}
-
-@Composable
-private fun UserItemIcon(
-    image: Drawable,
-    isSelectionMarkerVisible: Boolean,
-    modifier: Modifier = Modifier,
-) {
-    Image(
-        bitmap = image.toBitmap().asImageBitmap(),
-        contentDescription = null,
-        modifier =
-            if (isSelectionMarkerVisible) {
-                    // Draws a ring
-                    modifier.border(
-                        width = 8.dp,
-                        color = LocalAndroidColorScheme.current.colorAccentPrimary,
-                        shape = CircleShape,
-                    )
-                } else {
-                    modifier
-                }
-                .padding(16.dp)
-                .clip(CircleShape)
-    )
-}
-
-@Composable
-private fun Buttons(
-    menuActions: List<UserActionViewModel>,
-    isOpenMenuButtonVisible: Boolean,
-    isMenuVisible: Boolean,
-    onMenuClosed: () -> Unit,
-    onOpenMenuButtonClicked: () -> Unit,
-    onCancelButtonClicked: () -> Unit,
-    modifier: Modifier = Modifier,
-) {
-    Row(
-        modifier = modifier,
-    ) {
-        // Cancel button.
-        SysUiTextButton(
-            onClick = onCancelButtonClicked,
-        ) {
-            Text(stringResource(R.string.cancel))
-        }
-
-        // "Open menu" button.
-        if (isOpenMenuButtonVisible) {
-            Spacer(modifier = Modifier.width(8.dp))
-            // To properly use a DropdownMenu in Compose, we need to wrap the button that opens it
-            // and the menu itself in a Box.
-            Box {
-                SysUiOutlinedButton(
-                    onClick = onOpenMenuButtonClicked,
-                ) {
-                    Text(stringResource(R.string.add))
-                }
-                Menu(
-                    viewModel = menuActions,
-                    isMenuVisible = isMenuVisible,
-                    onMenuClosed = onMenuClosed,
-                )
-            }
-        }
-    }
-}
-
-@Composable
-private fun Menu(
-    viewModel: List<UserActionViewModel>,
-    isMenuVisible: Boolean,
-    onMenuClosed: () -> Unit,
-    modifier: Modifier = Modifier,
-) {
-    val maxItemWidth = LocalConfiguration.current.screenWidthDp.dp / 4
-    DropdownMenu(
-        expanded = isMenuVisible,
-        onDismissRequest = onMenuClosed,
-        modifier =
-            modifier.background(
-                color = MaterialTheme.colorScheme.inverseOnSurface,
-            ),
-    ) {
-        viewModel.forEachIndexed { index, action ->
-            MenuItem(
-                viewModel = action,
-                onClicked = { action.onClicked() },
-                topPadding =
-                    if (index == 0) {
-                        16.dp
-                    } else {
-                        0.dp
-                    },
-                bottomPadding =
-                    if (index == viewModel.size - 1) {
-                        16.dp
-                    } else {
-                        0.dp
-                    },
-                modifier = Modifier.sizeIn(maxWidth = maxItemWidth),
-            )
-        }
-    }
-}
-
-@Composable
-private fun MenuItem(
-    viewModel: UserActionViewModel,
-    onClicked: () -> Unit,
-    topPadding: Dp,
-    bottomPadding: Dp,
-    modifier: Modifier = Modifier,
-) {
-    val context = LocalContext.current
-    val density = LocalDensity.current
-
-    val icon =
-        remember(viewModel.iconResourceId) {
-            val drawable =
-                checkNotNull(AppCompatResources.getDrawable(context, viewModel.iconResourceId))
-            val size = with(density) { 20.dp.toPx() }.toInt()
-            drawable
-                .toBitmap(
-                    width = size,
-                    height = size,
-                )
-                .asImageBitmap()
-        }
-
-    DropdownMenuItem(
-        text = {
-            Text(
-                text = stringResource(viewModel.textResourceId),
-                style = MaterialTheme.typography.bodyMedium,
-            )
-        },
-        onClick = onClicked,
-        leadingIcon = {
-            Spacer(modifier = Modifier.width(10.dp))
-            Image(
-                bitmap = icon,
-                contentDescription = null,
-            )
-        },
-        modifier =
-            modifier
-                .heightIn(
-                    min = 56.dp,
-                )
-                .padding(
-                    start = 18.dp,
-                    end = 65.dp,
-                    top = topPadding,
-                    bottom = bottomPadding,
-                ),
-    )
-}
diff --git a/packages/SystemUI/ktfmt_includes.txt b/packages/SystemUI/ktfmt_includes.txt
index 06ea381..7fc9a83 100644
--- a/packages/SystemUI/ktfmt_includes.txt
+++ b/packages/SystemUI/ktfmt_includes.txt
@@ -16,7 +16,6 @@
 -packages/SystemUI/checks/tests/com/android/systemui/lint/RegisterReceiverViaContextDetectorTest.kt
 -packages/SystemUI/checks/tests/com/android/systemui/lint/SoftwareBitmapDetectorTest.kt
 -packages/SystemUI/monet/src/com/android/systemui/monet/ColorScheme.kt
--packages/SystemUI/plugin/src/com/android/systemui/plugins/ClockProviderPlugin.kt
 -packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QSContainerController.kt
 -packages/SystemUI/shared/src/com/android/systemui/flags/Flag.kt
 -packages/SystemUI/shared/src/com/android/systemui/flags/FlagListenable.kt
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/ClockProviderPlugin.kt b/packages/SystemUI/plugin/src/com/android/systemui/plugins/ClockProviderPlugin.kt
index 89f5c2c..66e44b9 100644
--- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/ClockProviderPlugin.kt
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/ClockProviderPlugin.kt
@@ -70,10 +70,10 @@
     }
 
     /** Optional method for dumping debug information */
-    fun dump(pw: PrintWriter) { }
+    fun dump(pw: PrintWriter) {}
 
     /** Optional method for debug logging */
-    fun setLogBuffer(logBuffer: LogBuffer) { }
+    fun setLogBuffer(logBuffer: LogBuffer) {}
 }
 
 /** Interface for a specific clock face version rendered by the clock */
@@ -88,40 +88,37 @@
 /** Events that should call when various rendering parameters change */
 interface ClockEvents {
     /** Call every time tick */
-    fun onTimeTick() { }
+    fun onTimeTick() {}
 
     /** Call whenever timezone changes */
-    fun onTimeZoneChanged(timeZone: TimeZone) { }
+    fun onTimeZoneChanged(timeZone: TimeZone) {}
 
     /** Call whenever the text time format changes (12hr vs 24hr) */
-    fun onTimeFormatChanged(is24Hr: Boolean) { }
+    fun onTimeFormatChanged(is24Hr: Boolean) {}
 
     /** Call whenever the locale changes */
-    fun onLocaleChanged(locale: Locale) { }
-
-    /** Call whenever font settings change */
-    fun onFontSettingChanged() { }
+    fun onLocaleChanged(locale: Locale) {}
 
     /** Call whenever the color palette should update */
-    fun onColorPaletteChanged(resources: Resources) { }
+    fun onColorPaletteChanged(resources: Resources) {}
 }
 
 /** Methods which trigger various clock animations */
 interface ClockAnimations {
     /** Runs an enter animation (if any) */
-    fun enter() { }
+    fun enter() {}
 
     /** Sets how far into AOD the device currently is. */
-    fun doze(fraction: Float) { }
+    fun doze(fraction: Float) {}
 
     /** Sets how far into the folding animation the device is. */
-    fun fold(fraction: Float) { }
+    fun fold(fraction: Float) {}
 
     /** Runs the battery animation (if any). */
-    fun charge() { }
+    fun charge() {}
 
     /** Move the clock, for example, if the notification tray appears in split-shade mode. */
-    fun onPositionUpdated(fromRect: Rect, toRect: Rect, fraction: Float) { }
+    fun onPositionUpdated(fromRect: Rect, toRect: Rect, fraction: Float) {}
 
     /**
      * Whether this clock has a custom position update animation. If true, the keyguard will call
@@ -135,11 +132,26 @@
 /** Events that have specific data about the related face */
 interface ClockFaceEvents {
     /** Region Darkness specific to the clock face */
-    fun onRegionDarknessChanged(isDark: Boolean) { }
+    fun onRegionDarknessChanged(isDark: Boolean) {}
+
+    /**
+     * Call whenever font settings change. Pass in a target font size in pixels. The specific clock
+     * design is allowed to ignore this target size on a case-by-case basis.
+     */
+    fun onFontSettingChanged(fontSizePx: Float) {}
+
+    /**
+     * Target region information for the clock face. For small clock, this will match the bounds of
+     * the parent view mostly, but have a target height based on the height of the default clock.
+     * For large clocks, the parent view is the entire device size, but most clocks will want to
+     * render within the centered targetRect to avoid obstructing other elements. The specified
+     * targetRegion is relative to the parent view.
+     */
+    fun onTargetRegionChanged(targetRegion: Rect?) {}
 }
 
 /** Some data about a clock design */
 data class ClockMetadata(
     val clockId: ClockId,
-    val name: String
+    val name: String,
 )
diff --git a/packages/SystemUI/res-keyguard/layout/keyguard_clock_switch.xml b/packages/SystemUI/res-keyguard/layout/keyguard_clock_switch.xml
index c297149..b49afee 100644
--- a/packages/SystemUI/res-keyguard/layout/keyguard_clock_switch.xml
+++ b/packages/SystemUI/res-keyguard/layout/keyguard_clock_switch.xml
@@ -37,7 +37,6 @@
         android:id="@+id/lockscreen_clock_view_large"
         android:layout_width="match_parent"
         android:layout_height="match_parent"
-        android:layout_marginTop="@dimen/keyguard_large_clock_top_margin"
         android:clipChildren="false"
         android:visibility="gone" />
 
diff --git a/packages/SystemUI/res/drawable/ic_watch.xml b/packages/SystemUI/res/drawable/ic_watch.xml
new file mode 100644
index 0000000..8ff880c
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_watch.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ 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
+  -->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+        android:width="24dp"
+        android:height="24dp"
+        android:viewportWidth="24"
+        android:viewportHeight="24"
+        android:tint="?attr/colorControlNormal">
+    <path
+        android:fillColor="#FFFFFFFF"
+        android:pathData="M16,0L8,0l-0.95,5.73C5.19,7.19 4,9.45 4,12s1.19,4.81 3.05,6.27L8,24
+        h8l0.96,-5.73C18.81,16.81 20,14.54 20,12s-1.19,-4.81 -3.04,-6.27L16,0z
+        M12,18c-3.31,0 -6,-2.69 -6,-6s2.69,-6 6,-6 6,2.69 6,6 -2.69,6 -6,6z"/>
+</vector>
diff --git a/packages/SystemUI/res/layout/super_notification_shade.xml b/packages/SystemUI/res/layout/super_notification_shade.xml
index 8388b67..bafdb11 100644
--- a/packages/SystemUI/res/layout/super_notification_shade.xml
+++ b/packages/SystemUI/res/layout/super_notification_shade.xml
@@ -26,12 +26,12 @@
     android:fitsSystemWindows="true">
 
     <com.android.systemui.statusbar.BackDropView
-            android:id="@+id/backdrop"
-            android:layout_width="match_parent"
-            android:layout_height="match_parent"
-            android:visibility="gone"
-            sysui:ignoreRightInset="true"
-            >
+        android:id="@+id/backdrop"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:visibility="gone"
+        sysui:ignoreRightInset="true"
+    >
         <ImageView android:id="@+id/backdrop_back"
                    android:layout_width="match_parent"
                    android:scaleType="centerCrop"
@@ -49,7 +49,7 @@
         android:layout_height="match_parent"
         android:importantForAccessibility="no"
         sysui:ignoreRightInset="true"
-        />
+    />
 
     <com.android.systemui.scrim.ScrimView
         android:id="@+id/scrim_notifications"
@@ -57,17 +57,17 @@
         android:layout_height="match_parent"
         android:importantForAccessibility="no"
         sysui:ignoreRightInset="true"
-        />
+    />
 
     <com.android.systemui.statusbar.LightRevealScrim
-            android:id="@+id/light_reveal_scrim"
-            android:layout_width="match_parent"
-            android:layout_height="match_parent" />
+        android:id="@+id/light_reveal_scrim"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent" />
 
     <include layout="@layout/status_bar_expanded"
-        android:layout_width="match_parent"
-        android:layout_height="match_parent"
-        android:visibility="invisible" />
+             android:layout_width="match_parent"
+             android:layout_height="match_parent"
+             android:visibility="invisible" />
 
     <include layout="@layout/brightness_mirror_container" />
 
diff --git a/packages/SystemUI/res/values/bools.xml b/packages/SystemUI/res/values/bools.xml
index 8221d78..04fc4b8 100644
--- a/packages/SystemUI/res/values/bools.xml
+++ b/packages/SystemUI/res/values/bools.xml
@@ -25,6 +25,9 @@
     <!-- Whether to enable clipping on Quick Settings -->
     <bool name="qs_enable_clipping">true</bool>
 
+    <!-- Whether to enable clipping on Notification Views -->
+    <bool name="notification_enable_clipping">true</bool>
+
     <!-- Whether to enable transparent background for notification scrims -->
     <bool name="notification_scrim_transparent">false</bool>
 </resources>
diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml
index 55d6379..88af179 100644
--- a/packages/SystemUI/res/values/config.xml
+++ b/packages/SystemUI/res/values/config.xml
@@ -743,12 +743,35 @@
     <!-- How long in milliseconds before full burn-in protection is achieved. -->
     <integer name="config_dreamOverlayMillisUntilFullJitter">240000</integer>
 
+    <!-- The duration in milliseconds of the y-translation animation when waking up from
+         the dream -->
+    <integer name="config_dreamOverlayOutTranslationYDurationMs">333</integer>
+    <!-- The delay in milliseconds of the y-translation animation when waking up from
+         the dream for the complications at the bottom of the screen -->
+    <integer name="config_dreamOverlayOutTranslationYDelayBottomMs">33</integer>
+    <!-- The delay in milliseconds of the y-translation animation when waking up from
+         the dream for the complications at the top of the screen -->
+    <integer name="config_dreamOverlayOutTranslationYDelayTopMs">117</integer>
+    <!-- The duration in milliseconds of the alpha animation when waking up from the dream -->
+    <integer name="config_dreamOverlayOutAlphaDurationMs">200</integer>
+    <!-- The delay in milliseconds of the alpha animation when waking up from the dream for the
+         complications at the top of the screen -->
+    <integer name="config_dreamOverlayOutAlphaDelayTopMs">217</integer>
+    <!-- The delay in milliseconds of the alpha animation when waking up from the dream for the
+         complications at the bottom of the screen -->
+    <integer name="config_dreamOverlayOutAlphaDelayBottomMs">133</integer>
+    <!-- The duration in milliseconds of the blur animation when waking up from
+         the dream -->
+    <integer name="config_dreamOverlayOutBlurDurationMs">250</integer>
+
     <integer name="complicationFadeOutMs">500</integer>
 
     <integer name="complicationFadeInMs">500</integer>
 
     <integer name="complicationRestoreMs">1000</integer>
 
+    <integer name="complicationFadeOutDelayMs">200</integer>
+
     <!-- Duration in milliseconds of the dream in un-blur animation. -->
     <integer name="config_dreamOverlayInBlurDurationMs">249</integer>
     <!-- Delay in milliseconds of the dream in un-blur animation. -->
@@ -780,28 +803,18 @@
         <item>com.android.systemui</item>
     </string-array>
 
-    <!-- The thresholds which determine the color used by the AQI dream overlay.
-         NOTE: This must always be kept sorted from low to high -->
-    <integer-array name="config_dreamAqiThresholds">
-        <item>-1</item>
-        <item>50</item>
-        <item>100</item>
-        <item>150</item>
-        <item>200</item>
-        <item>300</item>
-    </integer-array>
-
-    <!-- The color values which correspond to the thresholds above -->
-    <integer-array name="config_dreamAqiColorValues">
-        <item>@color/dream_overlay_aqi_good</item>
-        <item>@color/dream_overlay_aqi_moderate</item>
-        <item>@color/dream_overlay_aqi_unhealthy_sensitive</item>
-        <item>@color/dream_overlay_aqi_unhealthy</item>
-        <item>@color/dream_overlay_aqi_very_unhealthy</item>
-        <item>@color/dream_overlay_aqi_hazardous</item>
-    </integer-array>
-
     <!-- Whether the device should display hotspot UI. If true, UI will display only when tethering
          is available. If false, UI will never show regardless of tethering availability" -->
     <bool name="config_show_wifi_tethering">true</bool>
+
+    <!-- A collection of "slots" for placing quick affordance actions on the lock screen when the
+    device is locked. Each item is a string consisting of two parts, separated by the ':' character.
+    The first part is the unique ID for the slot, it is not a human-visible name, but should still
+    be unique across all slots specified. The second part is the capacity and must be a positive
+    integer; this is how many quick affordance actions that user is allowed to add to the slot. -->
+    <string-array name="config_keyguardQuickAffordanceSlots" translatable="false">
+        <item>bottom_start:1</item>
+        <item>bottom_end:1</item>
+    </string-array>
+
 </resources>
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index 6577b07..fbdccff 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -1552,6 +1552,7 @@
     <dimen name="dream_overlay_complication_margin">0dp</dimen>
 
     <dimen name="dream_overlay_y_offset">80dp</dimen>
+    <dimen name="dream_overlay_exit_y_offset">40dp</dimen>
 
     <dimen name="dream_aqi_badge_corner_radius">28dp</dimen>
     <dimen name="dream_aqi_badge_padding_vertical">6dp</dimen>
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 80dff856..72c8163e 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -405,8 +405,8 @@
     <string name="keyguard_face_failed">Can\u2019t recognize face</string>
     <!-- Message shown to suggest using fingerprint sensor to authenticate after another biometric failed. [CHAR LIMIT=25] -->
     <string name="keyguard_suggest_fingerprint">Use fingerprint instead</string>
-    <!-- Message shown to inform the user that face unlock is not available. [CHAR LIMIT=65] -->
-    <string name="keyguard_face_unlock_unavailable">Face unlock unavailable.</string>
+    <!-- Message shown to inform the user that face unlock is not available. [CHAR LIMIT=59] -->
+    <string name="keyguard_face_unlock_unavailable">Face Unlock unavailable</string>
 
     <!-- Content description of the bluetooth icon when connected for accessibility (not shown on the screen). [CHAR LIMIT=NONE] -->
     <string name="accessibility_bluetooth_connected">Bluetooth connected.</string>
@@ -439,16 +439,16 @@
     <string name="accessibility_battery_level">Battery <xliff:g id="number">%d</xliff:g> percent.</string>
 
     <!-- Content description of the battery level icon for accessibility, including the estimated time remaining before the phone runs out of battery (not shown on the screen). [CHAR LIMIT=NONE] -->
-    <string name="accessibility_battery_level_with_estimate">Battery <xliff:g id="percentage" example="95%">%1$d</xliff:g> percent, about <xliff:g id="time" example="Until 3:15pm">%2$s</xliff:g> left based on your usage</string>
+    <string name="accessibility_battery_level_with_estimate">Battery <xliff:g id="percentage" example="95%">%1$d</xliff:g> percent, <xliff:g id="time" example="Until 3:15pm">%2$s</xliff:g></string>
 
     <!-- Content description of the battery level icon for accessibility while the device is charging (not shown on the screen). [CHAR LIMIT=NONE] -->
     <string name="accessibility_battery_level_charging">Battery charging, <xliff:g id="battery_percentage">%d</xliff:g> percent.</string>
 
     <!-- Content description of the battery level icon for accessibility, with information that the device charging is paused in order to protect the lifetime of the battery (not shown on screen). [CHAR LIMIT=NONE] -->
-    <string name="accessibility_battery_level_charging_paused">Battery <xliff:g id="percentage" example="90%">%d</xliff:g> percent. Charging paused for battery protection.</string>
+    <string name="accessibility_battery_level_charging_paused">Battery <xliff:g id="percentage" example="90%">%d</xliff:g> percent, charging paused for battery protection.</string>
 
     <!-- Content description of the battery level icon for accessibility, including the estimated time remaining before the phone runs out of battery *and* information that the device charging is paused in order to protect the lifetime of the battery (not shown on screen). [CHAR LIMIT=NONE] -->
-    <string name="accessibility_battery_level_charging_paused_with_estimate">Battery <xliff:g id="percentage" example="90%">%1$d</xliff:g> percent, about <xliff:g id="time" example="Until 3:15pm">%2$s</xliff:g> left based on your usage. Charging paused for battery protection.</string>
+    <string name="accessibility_battery_level_charging_paused_with_estimate">Battery <xliff:g id="percentage" example="90%">%1$d</xliff:g> percent, <xliff:g id="time" example="Until 3:15pm">%2$s</xliff:g>, charging paused for battery protection.</string>
 
     <!-- Content description of overflow icon container of the notifications for accessibility (not shown on the screen)[CHAR LIMIT=NONE] -->
     <string name="accessibility_overflow_action">See all notifications</string>
diff --git a/packages/SystemUI/res/xml/qqs_header.xml b/packages/SystemUI/res/xml/qqs_header.xml
index e07a6c1..5d3650c 100644
--- a/packages/SystemUI/res/xml/qqs_header.xml
+++ b/packages/SystemUI/res/xml/qqs_header.xml
@@ -59,6 +59,7 @@
         <Layout
             android:layout_width="wrap_content"
             android:layout_height="@dimen/new_qs_header_non_clickable_element_height"
+            app:layout_constrainedWidth="true"
             app:layout_constraintHeight_min="@dimen/new_qs_header_non_clickable_element_height"
             app:layout_constraintStart_toEndOf="@id/date"
             app:layout_constraintEnd_toStartOf="@id/batteryRemainingIcon"
diff --git a/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/ViewScreenshotTestRule.kt b/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/ViewScreenshotTestRule.kt
index 0b0595f..36ac1ff 100644
--- a/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/ViewScreenshotTestRule.kt
+++ b/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/ViewScreenshotTestRule.kt
@@ -34,6 +34,7 @@
 import platform.test.screenshot.DeviceEmulationRule
 import platform.test.screenshot.DeviceEmulationSpec
 import platform.test.screenshot.MaterialYouColorsRule
+import platform.test.screenshot.PathConfig
 import platform.test.screenshot.ScreenshotTestRule
 import platform.test.screenshot.getEmulatedDevicePathConfig
 import platform.test.screenshot.matchers.BitmapMatcher
@@ -41,13 +42,19 @@
 /** A rule for View screenshot diff unit tests. */
 class ViewScreenshotTestRule(
     emulationSpec: DeviceEmulationSpec,
-    private val matcher: BitmapMatcher = UnitTestBitmapMatcher
+    private val matcher: BitmapMatcher = UnitTestBitmapMatcher,
+    pathConfig: PathConfig = getEmulatedDevicePathConfig(emulationSpec),
+    assetsPathRelativeToRepo: String = ""
 ) : TestRule {
     private val colorsRule = MaterialYouColorsRule()
     private val deviceEmulationRule = DeviceEmulationRule(emulationSpec)
     private val screenshotRule =
         ScreenshotTestRule(
-            SystemUIGoldenImagePathManager(getEmulatedDevicePathConfig(emulationSpec))
+            if (assetsPathRelativeToRepo.isBlank()) {
+                SystemUIGoldenImagePathManager(pathConfig)
+            } else {
+                SystemUIGoldenImagePathManager(pathConfig, assetsPathRelativeToRepo)
+            }
         )
     private val activityRule = ActivityScenarioRule(ScreenshotActivity::class.java)
     private val delegateRule =
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/clocks/DefaultClockController.kt b/packages/SystemUI/shared/src/com/android/systemui/shared/clocks/DefaultClockController.kt
index ca780c8..599cd23 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/clocks/DefaultClockController.kt
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/clocks/DefaultClockController.kt
@@ -20,6 +20,7 @@
 import android.icu.text.NumberFormat
 import android.util.TypedValue
 import android.view.LayoutInflater
+import android.view.View
 import android.widget.FrameLayout
 import androidx.annotation.VisibleForTesting
 import com.android.systemui.plugins.ClockAnimations
@@ -80,7 +81,7 @@
     }
 
     override fun initialize(resources: Resources, dozeFraction: Float, foldFraction: Float) {
-        largeClock.recomputePadding()
+        largeClock.recomputePadding(null)
         animations = DefaultClockAnimations(dozeFraction, foldFraction)
         events.onColorPaletteChanged(resources)
         events.onTimeZoneChanged(TimeZone.getDefault())
@@ -101,6 +102,7 @@
         // MAGENTA is a placeholder, and will be assigned correctly in initialize
         private var currentColor = Color.MAGENTA
         private var isRegionDark = false
+        protected var targetRegion: Rect? = null
 
         init {
             view.setColors(currentColor, currentColor)
@@ -112,8 +114,20 @@
                     this@DefaultClockFaceController.isRegionDark = isRegionDark
                     updateColor()
                 }
+
+                override fun onTargetRegionChanged(targetRegion: Rect?) {
+                    this@DefaultClockFaceController.targetRegion = targetRegion
+                    recomputePadding(targetRegion)
+                }
+
+                override fun onFontSettingChanged(fontSizePx: Float) {
+                    view.setTextSize(TypedValue.COMPLEX_UNIT_PX, fontSizePx)
+                    recomputePadding(targetRegion)
+                }
             }
 
+        open fun recomputePadding(targetRegion: Rect?) {}
+
         fun updateColor() {
             val color =
                 if (isRegionDark) {
@@ -135,9 +149,16 @@
     inner class LargeClockFaceController(
         view: AnimatableClockView,
     ) : DefaultClockFaceController(view) {
-        fun recomputePadding() {
+        override fun recomputePadding(targetRegion: Rect?) {
+            // We center the view within the targetRegion instead of within the parent
+            // view by computing the difference and adding that to the padding.
+            val parent = view.parent
+            val yDiff =
+                if (targetRegion != null && parent is View && parent.isLaidOut())
+                    targetRegion.centerY() - parent.height / 2f
+                else 0f
             val lp = view.getLayoutParams() as FrameLayout.LayoutParams
-            lp.topMargin = (-0.5f * view.bottom).toInt()
+            lp.topMargin = (-0.5f * view.bottom + yDiff).toInt()
             view.setLayoutParams(lp)
         }
 
@@ -155,18 +176,6 @@
         override fun onTimeZoneChanged(timeZone: TimeZone) =
             clocks.forEach { it.onTimeZoneChanged(timeZone) }
 
-        override fun onFontSettingChanged() {
-            smallClock.view.setTextSize(
-                TypedValue.COMPLEX_UNIT_PX,
-                resources.getDimensionPixelSize(R.dimen.small_clock_text_size).toFloat()
-            )
-            largeClock.view.setTextSize(
-                TypedValue.COMPLEX_UNIT_PX,
-                resources.getDimensionPixelSize(R.dimen.large_clock_text_size).toFloat()
-            )
-            largeClock.recomputePadding()
-        }
-
         override fun onColorPaletteChanged(resources: Resources) {
             largeClock.updateColor()
             smallClock.updateColor()
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/keyguard/data/content/KeyguardQuickAffordanceProviderClient.kt b/packages/SystemUI/shared/src/com/android/systemui/shared/keyguard/data/content/KeyguardQuickAffordanceProviderClient.kt
deleted file mode 100644
index 8612b3a..0000000
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/keyguard/data/content/KeyguardQuickAffordanceProviderClient.kt
+++ /dev/null
@@ -1,326 +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.shared.keyguard.data.content
-
-import android.annotation.SuppressLint
-import android.content.ContentValues
-import android.content.Context
-import android.database.ContentObserver
-import android.graphics.drawable.Drawable
-import android.net.Uri
-import android.os.UserHandle
-import androidx.annotation.DrawableRes
-import com.android.systemui.shared.keyguard.data.content.KeyguardQuickAffordanceProviderContract as Contract
-import kotlinx.coroutines.CoroutineDispatcher
-import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.channels.awaitClose
-import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.callbackFlow
-import kotlinx.coroutines.flow.map
-import kotlinx.coroutines.flow.onStart
-import kotlinx.coroutines.withContext
-
-/** Collection of utility functions for using a content provider implementing the [Contract]. */
-object KeyguardQuickAffordanceProviderClient {
-
-    /**
-     * Selects an affordance with the given ID for a slot on the lock screen with the given ID.
-     *
-     * Note that the maximum number of selected affordances on this slot is automatically enforced.
-     * Selecting a slot that is already full (e.g. already has a number of selected affordances at
-     * its maximum capacity) will automatically remove the oldest selected affordance before adding
-     * the one passed in this call. Additionally, selecting an affordance that's already one of the
-     * selected affordances on the slot will move the selected affordance to the newest location in
-     * the slot.
-     */
-    suspend fun insertSelection(
-        context: Context,
-        slotId: String,
-        affordanceId: String,
-        dispatcher: CoroutineDispatcher = Dispatchers.IO,
-    ) {
-        withContext(dispatcher) {
-            context.contentResolver.insert(
-                Contract.SelectionTable.URI,
-                ContentValues().apply {
-                    put(Contract.SelectionTable.Columns.SLOT_ID, slotId)
-                    put(Contract.SelectionTable.Columns.AFFORDANCE_ID, affordanceId)
-                }
-            )
-        }
-    }
-
-    /** Returns all available slots supported by the device. */
-    suspend fun querySlots(
-        context: Context,
-        dispatcher: CoroutineDispatcher = Dispatchers.IO,
-    ): List<Slot> {
-        return withContext(dispatcher) {
-            context.contentResolver
-                .query(
-                    Contract.SlotTable.URI,
-                    null,
-                    null,
-                    null,
-                    null,
-                )
-                ?.use { cursor ->
-                    buildList {
-                        val idColumnIndex = cursor.getColumnIndex(Contract.SlotTable.Columns.ID)
-                        val capacityColumnIndex =
-                            cursor.getColumnIndex(Contract.SlotTable.Columns.CAPACITY)
-                        if (idColumnIndex == -1 || capacityColumnIndex == -1) {
-                            return@buildList
-                        }
-
-                        while (cursor.moveToNext()) {
-                            add(
-                                Slot(
-                                    id = cursor.getString(idColumnIndex),
-                                    capacity = cursor.getInt(capacityColumnIndex),
-                                )
-                            )
-                        }
-                    }
-                }
-        }
-            ?: emptyList()
-    }
-
-    /**
-     * Returns [Flow] for observing the collection of slots.
-     *
-     * @see [querySlots]
-     */
-    fun observeSlots(
-        context: Context,
-        dispatcher: CoroutineDispatcher = Dispatchers.IO,
-    ): Flow<List<Slot>> {
-        return observeUri(
-                context,
-                Contract.SlotTable.URI,
-            )
-            .map { querySlots(context, dispatcher) }
-    }
-
-    /**
-     * Returns all available affordances supported by the device, regardless of current slot
-     * placement.
-     */
-    suspend fun queryAffordances(
-        context: Context,
-        dispatcher: CoroutineDispatcher = Dispatchers.IO,
-    ): List<Affordance> {
-        return withContext(dispatcher) {
-            context.contentResolver
-                .query(
-                    Contract.AffordanceTable.URI,
-                    null,
-                    null,
-                    null,
-                    null,
-                )
-                ?.use { cursor ->
-                    buildList {
-                        val idColumnIndex =
-                            cursor.getColumnIndex(Contract.AffordanceTable.Columns.ID)
-                        val nameColumnIndex =
-                            cursor.getColumnIndex(Contract.AffordanceTable.Columns.NAME)
-                        val iconColumnIndex =
-                            cursor.getColumnIndex(Contract.AffordanceTable.Columns.ICON)
-                        if (idColumnIndex == -1 || nameColumnIndex == -1 || iconColumnIndex == -1) {
-                            return@buildList
-                        }
-
-                        while (cursor.moveToNext()) {
-                            add(
-                                Affordance(
-                                    id = cursor.getString(idColumnIndex),
-                                    name = cursor.getString(nameColumnIndex),
-                                    iconResourceId = cursor.getInt(iconColumnIndex),
-                                )
-                            )
-                        }
-                    }
-                }
-        }
-            ?: emptyList()
-    }
-
-    /**
-     * Returns [Flow] for observing the collection of affordances.
-     *
-     * @see [queryAffordances]
-     */
-    fun observeAffordances(
-        context: Context,
-        dispatcher: CoroutineDispatcher = Dispatchers.IO,
-    ): Flow<List<Affordance>> {
-        return observeUri(
-                context,
-                Contract.AffordanceTable.URI,
-            )
-            .map { queryAffordances(context, dispatcher) }
-    }
-
-    /** Returns the current slot-affordance selections. */
-    suspend fun querySelections(
-        context: Context,
-        dispatcher: CoroutineDispatcher = Dispatchers.IO,
-    ): List<Selection> {
-        return withContext(dispatcher) {
-            context.contentResolver
-                .query(
-                    Contract.SelectionTable.URI,
-                    null,
-                    null,
-                    null,
-                    null,
-                )
-                ?.use { cursor ->
-                    buildList {
-                        val slotIdColumnIndex =
-                            cursor.getColumnIndex(Contract.SelectionTable.Columns.SLOT_ID)
-                        val affordanceIdColumnIndex =
-                            cursor.getColumnIndex(Contract.SelectionTable.Columns.AFFORDANCE_ID)
-                        if (slotIdColumnIndex == -1 || affordanceIdColumnIndex == -1) {
-                            return@buildList
-                        }
-
-                        while (cursor.moveToNext()) {
-                            add(
-                                Selection(
-                                    slotId = cursor.getString(slotIdColumnIndex),
-                                    affordanceId = cursor.getString(affordanceIdColumnIndex),
-                                )
-                            )
-                        }
-                    }
-                }
-        }
-            ?: emptyList()
-    }
-
-    /**
-     * Returns [Flow] for observing the collection of selections.
-     *
-     * @see [querySelections]
-     */
-    fun observeSelections(
-        context: Context,
-        dispatcher: CoroutineDispatcher = Dispatchers.IO,
-    ): Flow<List<Selection>> {
-        return observeUri(
-                context,
-                Contract.SelectionTable.URI,
-            )
-            .map { querySelections(context, dispatcher) }
-    }
-
-    /** Unselects an affordance with the given ID from the slot with the given ID. */
-    suspend fun deleteSelection(
-        context: Context,
-        slotId: String,
-        affordanceId: String,
-        dispatcher: CoroutineDispatcher = Dispatchers.IO,
-    ) {
-        withContext(dispatcher) {
-            context.contentResolver.delete(
-                Contract.SelectionTable.URI,
-                "${Contract.SelectionTable.Columns.SLOT_ID} = ? AND" +
-                    " ${Contract.SelectionTable.Columns.AFFORDANCE_ID} = ?",
-                arrayOf(
-                    slotId,
-                    affordanceId,
-                ),
-            )
-        }
-    }
-
-    /** Unselects all affordances from the slot with the given ID. */
-    suspend fun deleteAllSelections(
-        context: Context,
-        slotId: String,
-        dispatcher: CoroutineDispatcher = Dispatchers.IO,
-    ) {
-        withContext(dispatcher) {
-            context.contentResolver.delete(
-                Contract.SelectionTable.URI,
-                "${Contract.SelectionTable.Columns.SLOT_ID}",
-                arrayOf(
-                    slotId,
-                ),
-            )
-        }
-    }
-
-    private fun observeUri(
-        context: Context,
-        uri: Uri,
-    ): Flow<Unit> {
-        return callbackFlow {
-                val observer =
-                    object : ContentObserver(null) {
-                        override fun onChange(selfChange: Boolean) {
-                            trySend(Unit)
-                        }
-                    }
-
-                context.contentResolver.registerContentObserver(
-                    uri,
-                    /* notifyForDescendants= */ true,
-                    observer,
-                    UserHandle.USER_CURRENT,
-                )
-
-                awaitClose { context.contentResolver.unregisterContentObserver(observer) }
-            }
-            .onStart { emit(Unit) }
-    }
-
-    @SuppressLint("UseCompatLoadingForDrawables")
-    suspend fun getAffordanceIcon(
-        context: Context,
-        @DrawableRes iconResourceId: Int,
-        dispatcher: CoroutineDispatcher = Dispatchers.IO,
-    ): Drawable {
-        return withContext(dispatcher) {
-            context.packageManager
-                .getResourcesForApplication(SYSTEM_UI_PACKAGE_NAME)
-                .getDrawable(iconResourceId)
-        }
-    }
-
-    data class Slot(
-        val id: String,
-        val capacity: Int,
-    )
-
-    data class Affordance(
-        val id: String,
-        val name: String,
-        val iconResourceId: Int,
-    )
-
-    data class Selection(
-        val slotId: String,
-        val affordanceId: String,
-    )
-
-    private const val SYSTEM_UI_PACKAGE_NAME = "com.android.systemui"
-}
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/utilities/PreviewPositionHelper.java b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/utilities/PreviewPositionHelper.java
index a790d89..f45887c 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/utilities/PreviewPositionHelper.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/utilities/PreviewPositionHelper.java
@@ -71,14 +71,20 @@
         int deltaRotate = getRotationDelta(currentRotation, thumbnailRotation);
         RectF thumbnailClipHint = new RectF();
 
-        float scaledTaskbarSize = 0;
+        float scaledTaskbarSize;
+        float canvasScreenRatio;
         if (mSplitBounds != null) {
             float fullscreenTaskWidth;
             float fullscreenTaskHeight;
-            float canvasScreenRatio;
 
             float taskPercent;
-            if (!mSplitBounds.appsStackedVertically) {
+            if (mSplitBounds.appsStackedVertically) {
+                taskPercent = mDesiredStagePosition != STAGE_POSITION_TOP_OR_LEFT
+                        ? mSplitBounds.topTaskPercent
+                        : (1 - (mSplitBounds.topTaskPercent + mSplitBounds.dividerHeightPercent));
+                fullscreenTaskHeight = screenHeightPx * taskPercent;
+                canvasScreenRatio = canvasHeight / fullscreenTaskHeight;
+            } else {
                 // For landscape, scale the width
                 taskPercent = mDesiredStagePosition == STAGE_POSITION_TOP_OR_LEFT
                         ? mSplitBounds.leftTaskPercent
@@ -86,17 +92,12 @@
                 // Scale landscape width to that of actual screen
                 fullscreenTaskWidth = screenWidthPx * taskPercent;
                 canvasScreenRatio = canvasWidth / fullscreenTaskWidth;
-            } else {
-                taskPercent = mDesiredStagePosition != STAGE_POSITION_TOP_OR_LEFT
-                        ? mSplitBounds.leftTaskPercent
-                        : (1 - (mSplitBounds.leftTaskPercent + mSplitBounds.dividerWidthPercent));
-                // Scale landscape width to that of actual screen
-                fullscreenTaskHeight = screenHeightPx * taskPercent;
-                canvasScreenRatio = canvasHeight / fullscreenTaskHeight;
             }
-            scaledTaskbarSize = taskbarSize * canvasScreenRatio;
-            thumbnailClipHint.bottom = isTablet ? scaledTaskbarSize : 0;
+        } else {
+            canvasScreenRatio = (float) canvasWidth / screenWidthPx;
         }
+        scaledTaskbarSize = taskbarSize * canvasScreenRatio;
+        thumbnailClipHint.bottom = isTablet ? scaledTaskbarSize : 0;
 
         float scale = thumbnailData.scale;
         final float thumbnailScale;
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 74519c2..05372fe 100644
--- a/packages/SystemUI/src-debug/com/android/systemui/flags/FlagsFactory.kt
+++ b/packages/SystemUI/src-debug/com/android/systemui/flags/FlagsFactory.kt
@@ -22,7 +22,11 @@
     private val flagMap = mutableMapOf<String, Flag<*>>()
 
     val knownFlags: Map<String, Flag<*>>
-        get() = flagMap
+        get() {
+            // We need to access Flags in order to initialize our map.
+            assert(flagMap.contains(Flags.TEAMFOOD.name)) { "Where is teamfood?" }
+            return flagMap
+        }
 
     fun unreleasedFlag(
         id: Int,
diff --git a/packages/SystemUI/src-debug/com/android/systemui/flags/FlagsModule.kt b/packages/SystemUI/src-debug/com/android/systemui/flags/FlagsModule.kt
index 7b216017..8323d09 100644
--- a/packages/SystemUI/src-debug/com/android/systemui/flags/FlagsModule.kt
+++ b/packages/SystemUI/src-debug/com/android/systemui/flags/FlagsModule.kt
@@ -34,6 +34,9 @@
     @Binds
     abstract fun bindsFeatureFlagDebug(impl: FeatureFlagsDebug): FeatureFlags
 
+    @Binds
+    abstract fun bindsRestarter(debugRestarter: FeatureFlagsDebugRestarter): Restarter
+
     @Module
     companion object {
         @JvmStatic
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 89c0786..27c5699 100644
--- a/packages/SystemUI/src-release/com/android/systemui/flags/FlagsFactory.kt
+++ b/packages/SystemUI/src-release/com/android/systemui/flags/FlagsFactory.kt
@@ -22,7 +22,11 @@
     private val flagMap = mutableMapOf<String, Flag<*>>()
 
     val knownFlags: Map<String, Flag<*>>
-        get() = flagMap
+        get() {
+            // We need to access Flags in order to initialize our map.
+            assert(flagMap.contains(Flags.TEAMFOOD.name)) { "Where is teamfood?" }
+            return flagMap
+        }
 
     fun unreleasedFlag(
         id: Int,
diff --git a/packages/SystemUI/src-release/com/android/systemui/flags/FlagsModule.kt b/packages/SystemUI/src-release/com/android/systemui/flags/FlagsModule.kt
index aef8876..87beff7 100644
--- a/packages/SystemUI/src-release/com/android/systemui/flags/FlagsModule.kt
+++ b/packages/SystemUI/src-release/com/android/systemui/flags/FlagsModule.kt
@@ -27,4 +27,7 @@
 abstract class FlagsModule {
     @Binds
     abstract fun bindsFeatureFlagRelease(impl: FeatureFlagsRelease): FeatureFlags
+
+    @Binds
+    abstract fun bindsRestarter(debugRestarter: FeatureFlagsReleaseRestarter): Restarter
 }
diff --git a/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt b/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt
index c9b8712..87e9d56 100644
--- a/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt
+++ b/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt
@@ -26,6 +26,7 @@
 import androidx.annotation.VisibleForTesting
 import androidx.lifecycle.Lifecycle
 import androidx.lifecycle.repeatOnLifecycle
+import com.android.systemui.R
 import com.android.systemui.broadcast.BroadcastDispatcher
 import com.android.systemui.dagger.qualifiers.Background
 import com.android.systemui.dagger.qualifiers.Main
@@ -43,6 +44,11 @@
 import com.android.systemui.statusbar.policy.BatteryController
 import com.android.systemui.statusbar.policy.BatteryController.BatteryStateChangeCallback
 import com.android.systemui.statusbar.policy.ConfigurationController
+import java.io.PrintWriter
+import java.util.Locale
+import java.util.TimeZone
+import java.util.concurrent.Executor
+import javax.inject.Inject
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.DisposableHandle
 import kotlinx.coroutines.Job
@@ -50,11 +56,6 @@
 import kotlinx.coroutines.flow.combine
 import kotlinx.coroutines.flow.filter
 import kotlinx.coroutines.launch
-import java.io.PrintWriter
-import java.util.Locale
-import java.util.TimeZone
-import java.util.concurrent.Executor
-import javax.inject.Inject
 
 /**
  * Controller for a Clock provided by the registry and used on the keyguard. Instantiated by
@@ -84,6 +85,7 @@
 
                 value.initialize(resources, dozeAmount, 0f)
                 updateRegionSamplers(value)
+                updateFontSizes()
             }
         }
 
@@ -150,7 +152,7 @@
             mainExecutor,
             bgExecutor,
             regionSamplingEnabled,
-            updateFun = { updateColors() } )
+            updateColors)
     }
 
     var smallRegionSampler: RegionSampler? = null
@@ -166,7 +168,7 @@
         }
 
         override fun onDensityOrFontScaleChanged() {
-            clock?.events?.onFontSettingChanged()
+            updateFontSizes()
         }
     }
 
@@ -251,6 +253,13 @@
         largeRegionSampler?.stopRegionSampler()
     }
 
+    private fun updateFontSizes() {
+        clock?.smallClock?.events?.onFontSettingChanged(
+            resources.getDimensionPixelSize(R.dimen.small_clock_text_size).toFloat())
+        clock?.largeClock?.events?.onFontSettingChanged(
+            resources.getDimensionPixelSize(R.dimen.large_clock_text_size).toFloat())
+    }
+
     /**
      * Dump information for debugging
      */
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java
index 8ebad6c..40423cd 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java
@@ -5,6 +5,7 @@
 import android.animation.AnimatorSet;
 import android.animation.ObjectAnimator;
 import android.content.Context;
+import android.graphics.Rect;
 import android.util.AttributeSet;
 import android.util.Log;
 import android.view.View;
@@ -22,6 +23,7 @@
 import java.io.PrintWriter;
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
+
 /**
  * Switch to show plugin clock when plugin is connected, otherwise it will show default clock.
  */
@@ -46,6 +48,7 @@
      */
     private FrameLayout mSmallClockFrame;
     private FrameLayout mLargeClockFrame;
+    private ClockController mClock;
 
     private View mStatusArea;
     private int mSmartspaceTopOffset;
@@ -95,6 +98,8 @@
     }
 
     void setClock(ClockController clock, int statusBarState) {
+        mClock = clock;
+
         // Disconnect from existing plugin.
         mSmallClockFrame.removeAllViews();
         mLargeClockFrame.removeAllViews();
@@ -108,6 +113,35 @@
         Log.i(TAG, "Attached new clock views to switch");
         mSmallClockFrame.addView(clock.getSmallClock().getView());
         mLargeClockFrame.addView(clock.getLargeClock().getView());
+        updateClockTargetRegions();
+    }
+
+    void updateClockTargetRegions() {
+        if (mClock != null) {
+            if (mSmallClockFrame.isLaidOut()) {
+                int targetHeight =  getResources()
+                        .getDimensionPixelSize(R.dimen.small_clock_text_size);
+                mClock.getSmallClock().getEvents().onTargetRegionChanged(new Rect(
+                        mSmallClockFrame.getLeft(),
+                        mSmallClockFrame.getTop(),
+                        mSmallClockFrame.getRight(),
+                        mSmallClockFrame.getTop() + targetHeight));
+            }
+
+            if (mLargeClockFrame.isLaidOut()) {
+                int largeClockTopMargin = getResources()
+                        .getDimensionPixelSize(R.dimen.keyguard_large_clock_top_margin);
+                int targetHeight = getResources()
+                        .getDimensionPixelSize(R.dimen.large_clock_text_size) * 2;
+                int top = mLargeClockFrame.getHeight() / 2 - targetHeight / 2
+                        + largeClockTopMargin / 2;
+                mClock.getLargeClock().getEvents().onTargetRegionChanged(new Rect(
+                        mLargeClockFrame.getLeft(),
+                        top,
+                        mLargeClockFrame.getRight(),
+                        top + targetHeight));
+            }
+        }
     }
 
     private void updateClockViews(boolean useLargeClock, boolean animate) {
@@ -214,6 +248,10 @@
     protected void onLayout(boolean changed, int l, int t, int r, int b) {
         super.onLayout(changed, l, t, r, b);
 
+        if (changed) {
+            post(() -> updateClockTargetRegions());
+        }
+
         if (mDisplayedClockSize != null && !mChildrenAreLaidOut) {
             post(() -> updateClockViews(mDisplayedClockSize == LARGE, mAnimateOnLayout));
         }
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java
index ace942d..e6aae9b 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java
@@ -77,7 +77,7 @@
     @KeyguardClockSwitch.ClockSize
     private int mCurrentClockSize = SMALL;
 
-    private int mKeyguardClockTopMargin = 0;
+    private int mKeyguardSmallClockTopMargin = 0;
     private final ClockRegistry.ClockChangeListener mClockChangedListener;
 
     private ViewGroup mStatusArea;
@@ -162,7 +162,7 @@
         mClockRegistry.registerClockChangeListener(mClockChangedListener);
         setClock(mClockRegistry.createCurrentClock());
         mClockEventController.registerListeners(mView);
-        mKeyguardClockTopMargin =
+        mKeyguardSmallClockTopMargin =
                 mView.getResources().getDimensionPixelSize(R.dimen.keyguard_clock_top_margin);
 
         if (mOnlyClock) {
@@ -244,10 +244,12 @@
      */
     public void onDensityOrFontScaleChanged() {
         mView.onDensityOrFontScaleChanged();
-        mKeyguardClockTopMargin =
+        mKeyguardSmallClockTopMargin =
                 mView.getResources().getDimensionPixelSize(R.dimen.keyguard_clock_top_margin);
+        mView.updateClockTargetRegions();
     }
 
+
     /**
      * Set which clock should be displayed on the keyguard. The other one will be automatically
      * hidden.
@@ -327,7 +329,7 @@
             return frameHeight / 2 + clockHeight / 2;
         } else {
             int clockHeight = clock.getSmallClock().getView().getHeight();
-            return clockHeight + statusBarHeaderHeight + mKeyguardClockTopMargin;
+            return clockHeight + statusBarHeaderHeight + mKeyguardSmallClockTopMargin;
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardHostViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardHostViewController.java
index 8fa7b11..2b660de 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardHostViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardHostViewController.java
@@ -21,7 +21,6 @@
 import android.content.res.Resources;
 import android.media.AudioManager;
 import android.os.SystemClock;
-import android.service.trust.TrustAgentService;
 import android.telephony.TelephonyManager;
 import android.util.Log;
 import android.util.MathUtils;
@@ -68,30 +67,24 @@
     private final KeyguardUpdateMonitorCallback mUpdateCallback =
             new KeyguardUpdateMonitorCallback() {
                 @Override
-                public void onTrustGrantedWithFlags(int flags, int userId, String message) {
-                    if (userId != KeyguardUpdateMonitor.getCurrentUser()) return;
-                    boolean bouncerVisible = mView.isVisibleToUser();
-                    boolean temporaryAndRenewable =
-                            (flags & TrustAgentService.FLAG_GRANT_TRUST_TEMPORARY_AND_RENEWABLE)
-                            != 0;
-                    boolean initiatedByUser =
-                            (flags & TrustAgentService.FLAG_GRANT_TRUST_INITIATED_BY_USER) != 0;
-                    boolean dismissKeyguard =
-                            (flags & TrustAgentService.FLAG_GRANT_TRUST_DISMISS_KEYGUARD) != 0;
-
-                    if (initiatedByUser || dismissKeyguard) {
-                        if ((mViewMediatorCallback.isScreenOn() || temporaryAndRenewable)
-                                && (bouncerVisible || dismissKeyguard)) {
-                            if (!bouncerVisible) {
-                                // The trust agent dismissed the keyguard without the user proving
-                                // that they are present (by swiping up to show the bouncer). That's
-                                // fine if the user proved presence via some other way to the trust
-                                //agent.
-                                Log.i(TAG, "TrustAgent dismissed Keyguard.");
-                            }
-                            mSecurityCallback.dismiss(false /* authenticated */, userId,
-                                    /* bypassSecondaryLockScreen */ false, SecurityMode.Invalid);
-                        } else {
+                public void onTrustGrantedForCurrentUser(boolean dismissKeyguard,
+                        TrustGrantFlags flags, String message) {
+                    if (dismissKeyguard) {
+                        if (!mView.isVisibleToUser()) {
+                            // The trust agent dismissed the keyguard without the user proving
+                            // that they are present (by swiping up to show the bouncer). That's
+                            // fine if the user proved presence via some other way to the trust
+                            // agent.
+                            Log.i(TAG, "TrustAgent dismissed Keyguard.");
+                        }
+                        mSecurityCallback.dismiss(
+                                false /* authenticated */,
+                                KeyguardUpdateMonitor.getCurrentUser(),
+                                /* bypassSecondaryLockScreen */ false,
+                                SecurityMode.Invalid
+                        );
+                    } else {
+                        if (flags.isInitiatedByUser() || flags.dismissKeyguardRequested()) {
                             mViewMediatorCallback.playTrustedSound();
                         }
                     }
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
index 2ac93b5..2d3dda9 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
@@ -144,6 +144,7 @@
 import com.android.systemui.dump.DumpManager;
 import com.android.systemui.log.SessionTracker;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
+import com.android.systemui.settings.UserTracker;
 import com.android.systemui.shared.system.TaskStackChangeListener;
 import com.android.systemui.shared.system.TaskStackChangeListeners;
 import com.android.systemui.statusbar.StatusBarState;
@@ -263,6 +264,7 @@
             "com.android.settings", "com.android.settings.FallbackHome");
 
     private final Context mContext;
+    private final UserTracker mUserTracker;
     private final KeyguardUpdateMonitorLogger mLogger;
     private final boolean mIsPrimaryUser;
     private final AuthController mAuthController;
@@ -470,10 +472,12 @@
                     FACE_AUTH_TRIGGERED_TRUST_DISABLED);
         }
 
-        String message = null;
-        if (KeyguardUpdateMonitor.getCurrentUser() == userId) {
-            final boolean userHasTrust = getUserHasTrust(userId);
-            if (userHasTrust && trustGrantedMessages != null) {
+        if (enabled) {
+            String message = null;
+            if (KeyguardUpdateMonitor.getCurrentUser() == userId
+                    && trustGrantedMessages != null) {
+                // Show the first non-empty string provided by a trust agent OR intentionally pass
+                // an empty string through (to prevent the default trust agent string from showing)
                 for (String msg : trustGrantedMessages) {
                     message = msg;
                     if (!TextUtils.isEmpty(message)) {
@@ -481,21 +485,39 @@
                     }
                 }
             }
-        }
-        mLogger.logTrustChanged(wasTrusted, enabled, userId);
-        if (message != null) {
-            mLogger.logShowTrustGrantedMessage(message.toString());
-        }
-        for (int i = 0; i < mCallbacks.size(); i++) {
-            KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get();
-            if (cb != null) {
-                cb.onTrustChanged(userId);
-                if (enabled) {
-                    cb.onTrustGrantedWithFlags(flags, userId, message);
+
+            mLogger.logTrustGrantedWithFlags(flags, userId, message);
+            if (userId == getCurrentUser()) {
+                final TrustGrantFlags trustGrantFlags = new TrustGrantFlags(flags);
+                for (int i = 0; i < mCallbacks.size(); i++) {
+                    KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get();
+                    if (cb != null) {
+                        cb.onTrustGrantedForCurrentUser(
+                                shouldDismissKeyguardOnTrustGrantedWithCurrentUser(trustGrantFlags),
+                                trustGrantFlags, message);
+                    }
                 }
             }
         }
 
+        mLogger.logTrustChanged(wasTrusted, enabled, userId);
+        for (int i = 0; i < mCallbacks.size(); i++) {
+            KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get();
+            if (cb != null) {
+                cb.onTrustChanged(userId);
+            }
+        }
+    }
+
+    /**
+     * Whether the trust granted call with its passed flags should dismiss keyguard.
+     * It's assumed that the trust was granted for the current user.
+     */
+    private boolean shouldDismissKeyguardOnTrustGrantedWithCurrentUser(TrustGrantFlags flags) {
+        final boolean isBouncerShowing = mPrimaryBouncerIsOrWillBeShowing || mUdfpsBouncerShowing;
+        return (flags.isInitiatedByUser() || flags.dismissKeyguardRequested())
+                && (mDeviceInteractive || flags.temporaryAndRenewable())
+                && (isBouncerShowing || flags.dismissKeyguardRequested());
     }
 
     @Override
@@ -848,13 +870,7 @@
             mHandler.removeCallbacks(mFpCancelNotReceived);
         }
         try {
-            final int userId;
-            try {
-                userId = ActivityManager.getService().getCurrentUser().id;
-            } catch (RemoteException e) {
-                mLogger.logException(e, "Failed to get current user id");
-                return;
-            }
+            final int userId = mUserTracker.getUserId();
             if (userId != authUserId) {
                 mLogger.logFingerprintAuthForWrongUser(authUserId);
                 return;
@@ -1072,13 +1088,7 @@
                 mLogger.d("Aborted successful auth because device is going to sleep.");
                 return;
             }
-            final int userId;
-            try {
-                userId = ActivityManager.getService().getCurrentUser().id;
-            } catch (RemoteException e) {
-                mLogger.logException(e, "Failed to get current user id");
-                return;
-            }
+            final int userId = mUserTracker.getUserId();
             if (userId != authUserId) {
                 mLogger.logFaceAuthForWrongUser(authUserId);
                 return;
@@ -1928,6 +1938,7 @@
     @Inject
     protected KeyguardUpdateMonitor(
             Context context,
+            UserTracker userTracker,
             @Main Looper mainLooper,
             BroadcastDispatcher broadcastDispatcher,
             SecureSettings secureSettings,
@@ -1960,6 +1971,7 @@
             FaceWakeUpTriggersConfig faceWakeUpTriggersConfig) {
         mContext = context;
         mSubscriptionManager = subscriptionManager;
+        mUserTracker = userTracker;
         mTelephonyListenerManager = telephonyListenerManager;
         mDeviceProvisioned = isDeviceProvisionedInSettingsDb();
         mStrongAuthTracker = new StrongAuthTracker(context, this::notifyStrongAuthStateChanged,
@@ -2192,7 +2204,7 @@
 
         TaskStackChangeListeners.getInstance().registerTaskStackListener(mTaskStackListener);
         mIsPrimaryUser = mUserManager.isPrimaryUser();
-        int user = ActivityManager.getCurrentUser();
+        int user = mUserTracker.getUserId();
         mUserIsUnlocked.put(user, mUserManager.isUserUnlocked(user));
         mLogoutEnabled = mDevicePolicyManager.isLogoutEnabled();
         updateSecondaryLockscreenRequirement(user);
@@ -3818,7 +3830,7 @@
             pw.println("    " + subId + "=" + mServiceStates.get(subId));
         }
         if (mFpm != null && mFpm.isHardwareDetected()) {
-            final int userId = ActivityManager.getCurrentUser();
+            final int userId = mUserTracker.getUserId();
             final int strongAuthFlags = mStrongAuthTracker.getStrongAuthForUser(userId);
             BiometricAuthenticated fingerprint = mUserFingerprintAuthenticated.get(userId);
             pw.println("  Fingerprint state (user=" + userId + ")");
@@ -3858,7 +3870,7 @@
             }
         }
         if (mFaceManager != null && mFaceManager.isHardwareDetected()) {
-            final int userId = ActivityManager.getCurrentUser();
+            final int userId = mUserTracker.getUserId();
             final int strongAuthFlags = mStrongAuthTracker.getStrongAuthForUser(userId);
             BiometricAuthenticated face = mUserFaceAuthenticated.get(userId);
             pw.println("  Face authentication state (user=" + userId + ")");
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java
index c5142f3..1d58fc9 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java
@@ -19,6 +19,7 @@
 import android.telephony.TelephonyManager;
 import android.view.WindowManagerPolicyConstants;
 
+import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 
 import com.android.settingslib.fuelgauge.BatteryStatus;
@@ -175,11 +176,13 @@
 
     /**
      * Called after trust was granted.
-     * @param userId of the user that has been granted trust
+     * @param dismissKeyguard whether the keyguard should be dismissed as a result of the
+     *                        trustGranted
      * @param message optional message the trust agent has provided to show that should indicate
      *                why trust was granted.
      */
-    public void onTrustGrantedWithFlags(int flags, int userId, @Nullable String message) { }
+    public void onTrustGrantedForCurrentUser(boolean dismissKeyguard,
+            @NonNull TrustGrantFlags flags, @Nullable String message) { }
 
     /**
      * Called when a biometric has been acquired.
diff --git a/packages/SystemUI/src/com/android/keyguard/TrustGrantFlags.java b/packages/SystemUI/src/com/android/keyguard/TrustGrantFlags.java
new file mode 100644
index 0000000..d33732c
--- /dev/null
+++ b/packages/SystemUI/src/com/android/keyguard/TrustGrantFlags.java
@@ -0,0 +1,98 @@
+/*
+ * 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.keyguard;
+
+import android.service.trust.TrustAgentService;
+
+import java.util.Objects;
+
+/**
+ * Translating {@link android.service.trust.TrustAgentService.GrantTrustFlags} to a more
+ * parsable object. These flags are requested by a TrustAgent.
+ */
+public class TrustGrantFlags {
+    final int mFlags;
+
+    public TrustGrantFlags(int flags) {
+        this.mFlags = flags;
+    }
+
+    /** {@link TrustAgentService.FLAG_GRANT_TRUST_INITIATED_BY_USER} */
+    public boolean isInitiatedByUser() {
+        return (mFlags & TrustAgentService.FLAG_GRANT_TRUST_INITIATED_BY_USER) != 0;
+    }
+
+    /**
+     * Trust agent is requesting to dismiss the keyguard.
+     * See {@link TrustAgentService.FLAG_GRANT_TRUST_DISMISS_KEYGUARD}.
+     *
+     * This does not guarantee that the keyguard is dismissed.
+     * KeyguardUpdateMonitor makes the final determination whether the keyguard should be dismissed.
+     * {@link KeyguardUpdateMonitorCallback#onTrustGrantedForCurrentUser(
+     *      boolean, TrustGrantFlags, String).
+     */
+    public boolean dismissKeyguardRequested() {
+        return (mFlags & TrustAgentService.FLAG_GRANT_TRUST_DISMISS_KEYGUARD) != 0;
+    }
+
+    /** {@link TrustAgentService.FLAG_GRANT_TRUST_TEMPORARY_AND_RENEWABLE} */
+    public boolean temporaryAndRenewable() {
+        return (mFlags & TrustAgentService.FLAG_GRANT_TRUST_TEMPORARY_AND_RENEWABLE) != 0;
+    }
+
+    /** {@link TrustAgentService.FLAG_GRANT_TRUST_DISPLAY_MESSAGE} */
+    public boolean displayMessage() {
+        return (mFlags & TrustAgentService.FLAG_GRANT_TRUST_DISPLAY_MESSAGE) != 0;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (!(o instanceof TrustGrantFlags)) {
+            return false;
+        }
+
+        return ((TrustGrantFlags) o).mFlags == this.mFlags;
+    }
+
+    @Override
+    public int hashCode() {
+      return Objects.hash(mFlags);
+    }
+
+    @Override
+    public String toString() {
+        final StringBuilder sb = new StringBuilder();
+        sb.append("[");
+        sb.append(mFlags);
+        sb.append("]=");
+
+        if (isInitiatedByUser()) {
+            sb.append("initiatedByUser|");
+        }
+        if (dismissKeyguardRequested()) {
+            sb.append("dismissKeyguard|");
+        }
+        if (temporaryAndRenewable()) {
+            sb.append("temporaryAndRenewable|");
+        }
+        if (displayMessage()) {
+            sb.append("displayMessage|");
+        }
+
+        return sb.toString();
+    }
+}
diff --git a/packages/SystemUI/src/com/android/keyguard/clock/ClockManager.java b/packages/SystemUI/src/com/android/keyguard/clock/ClockManager.java
index 9a0bfc1..ad9609f 100644
--- a/packages/SystemUI/src/com/android/keyguard/clock/ClockManager.java
+++ b/packages/SystemUI/src/com/android/keyguard/clock/ClockManager.java
@@ -29,17 +29,17 @@
 import android.util.DisplayMetrics;
 import android.view.LayoutInflater;
 
+import androidx.annotation.NonNull;
 import androidx.annotation.VisibleForTesting;
-import androidx.lifecycle.Observer;
 
-import com.android.systemui.broadcast.BroadcastDispatcher;
 import com.android.systemui.colorextraction.SysuiColorExtractor;
 import com.android.systemui.dagger.SysUISingleton;
+import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.dock.DockManager;
 import com.android.systemui.dock.DockManager.DockEventListener;
 import com.android.systemui.plugins.ClockPlugin;
 import com.android.systemui.plugins.PluginListener;
-import com.android.systemui.settings.CurrentUserObservable;
+import com.android.systemui.settings.UserTracker;
 import com.android.systemui.shared.plugins.PluginManager;
 
 import java.util.ArrayList;
@@ -47,6 +47,7 @@
 import java.util.List;
 import java.util.Map;
 import java.util.Objects;
+import java.util.concurrent.Executor;
 import java.util.function.Supplier;
 
 import javax.inject.Inject;
@@ -69,7 +70,8 @@
     private final ContentResolver mContentResolver;
     private final SettingsWrapper mSettingsWrapper;
     private final Handler mMainHandler = new Handler(Looper.getMainLooper());
-    private final CurrentUserObservable mCurrentUserObservable;
+    private final UserTracker mUserTracker;
+    private final Executor mMainExecutor;
 
     /**
      * Observe settings changes to know when to switch the clock face.
@@ -80,7 +82,7 @@
                 public void onChange(boolean selfChange, Collection<Uri> uris,
                         int flags, int userId) {
                     if (Objects.equals(userId,
-                            mCurrentUserObservable.getCurrentUser().getValue())) {
+                            mUserTracker.getUserId())) {
                         reload();
                     }
                 }
@@ -89,7 +91,13 @@
     /**
      * Observe user changes and react by potentially loading the custom clock for the new user.
      */
-    private final Observer<Integer> mCurrentUserObserver = (newUserId) -> reload();
+    private final UserTracker.Callback mUserChangedCallback =
+            new UserTracker.Callback() {
+                @Override
+                public void onUserChanged(int newUser, @NonNull Context userContext) {
+                    reload();
+                }
+            };
 
     private final PluginManager mPluginManager;
     @Nullable private final DockManager mDockManager;
@@ -129,22 +137,24 @@
     @Inject
     public ClockManager(Context context, LayoutInflater layoutInflater,
             PluginManager pluginManager, SysuiColorExtractor colorExtractor,
-            @Nullable DockManager dockManager, BroadcastDispatcher broadcastDispatcher) {
+            @Nullable DockManager dockManager, UserTracker userTracker,
+            @Main Executor mainExecutor) {
         this(context, layoutInflater, pluginManager, colorExtractor,
-                context.getContentResolver(), new CurrentUserObservable(broadcastDispatcher),
+                context.getContentResolver(), userTracker, mainExecutor,
                 new SettingsWrapper(context.getContentResolver()), dockManager);
     }
 
     @VisibleForTesting
     ClockManager(Context context, LayoutInflater layoutInflater,
             PluginManager pluginManager, SysuiColorExtractor colorExtractor,
-            ContentResolver contentResolver, CurrentUserObservable currentUserObservable,
+            ContentResolver contentResolver, UserTracker userTracker, Executor mainExecutor,
             SettingsWrapper settingsWrapper, DockManager dockManager) {
         mContext = context;
         mPluginManager = pluginManager;
         mContentResolver = contentResolver;
         mSettingsWrapper = settingsWrapper;
-        mCurrentUserObservable = currentUserObservable;
+        mUserTracker = userTracker;
+        mMainExecutor = mainExecutor;
         mDockManager = dockManager;
         mPreviewClocks = new AvailableClocks();
 
@@ -226,7 +236,7 @@
         mContentResolver.registerContentObserver(
                 Settings.Secure.getUriFor(Settings.Secure.DOCKED_CLOCK_FACE),
                 false, mContentObserver, UserHandle.USER_ALL);
-        mCurrentUserObservable.getCurrentUser().observeForever(mCurrentUserObserver);
+        mUserTracker.addCallback(mUserChangedCallback, mMainExecutor);
         if (mDockManager != null) {
             mDockManager.addListener(mDockEventListener);
         }
@@ -235,7 +245,7 @@
     private void unregister() {
         mPluginManager.removePluginListener(mPreviewClocks);
         mContentResolver.unregisterContentObserver(mContentObserver);
-        mCurrentUserObservable.getCurrentUser().removeObserver(mCurrentUserObserver);
+        mUserTracker.removeCallback(mUserChangedCallback);
         if (mDockManager != null) {
             mDockManager.removeListener(mDockEventListener);
         }
@@ -363,7 +373,7 @@
             ClockPlugin plugin = null;
             if (ClockManager.this.isDocked()) {
                 final String name = mSettingsWrapper.getDockedClockFace(
-                        mCurrentUserObservable.getCurrentUser().getValue());
+                        mUserTracker.getUserId());
                 if (name != null) {
                     plugin = mClocks.get(name);
                     if (plugin != null) {
@@ -372,7 +382,7 @@
                 }
             }
             final String name = mSettingsWrapper.getLockScreenCustomClockFace(
-                    mCurrentUserObservable.getCurrentUser().getValue());
+                    mUserTracker.getUserId());
             if (name != null) {
                 plugin = mClocks.get(name);
             }
diff --git a/packages/SystemUI/src/com/android/keyguard/logging/KeyguardUpdateMonitorLogger.kt b/packages/SystemUI/src/com/android/keyguard/logging/KeyguardUpdateMonitorLogger.kt
index 81b8dfe..6763700 100644
--- a/packages/SystemUI/src/com/android/keyguard/logging/KeyguardUpdateMonitorLogger.kt
+++ b/packages/SystemUI/src/com/android/keyguard/logging/KeyguardUpdateMonitorLogger.kt
@@ -25,6 +25,7 @@
 import com.android.keyguard.FaceAuthUiEvent
 import com.android.keyguard.KeyguardListenModel
 import com.android.keyguard.KeyguardUpdateMonitorCallback
+import com.android.keyguard.TrustGrantFlags
 import com.android.systemui.plugins.log.LogBuffer
 import com.android.systemui.plugins.log.LogLevel
 import com.android.systemui.plugins.log.LogLevel.DEBUG
@@ -368,12 +369,16 @@
                 }, { "reportUserRequestedUnlock origin=$str1 reason=$str2 dismissKeyguard=$bool1" })
     }
 
-    fun logShowTrustGrantedMessage(
+    fun logTrustGrantedWithFlags(
+            flags: Int,
+            userId: Int,
             message: String?
     ) {
         logBuffer.log(TAG, DEBUG, {
+            int1 = flags
+            int2 = userId
             str1 = message
-        }, { "showTrustGrantedMessage message$str1" })
+        }, { "trustGrantedWithFlags[user=$int2] flags=${TrustGrantFlags(int1)} message=$str1" })
     }
 
     fun logTrustChanged(
diff --git a/packages/SystemUI/src/com/android/systemui/Dependency.java b/packages/SystemUI/src/com/android/systemui/Dependency.java
index a5fdc68..51bcd6b 100644
--- a/packages/SystemUI/src/com/android/systemui/Dependency.java
+++ b/packages/SystemUI/src/com/android/systemui/Dependency.java
@@ -70,6 +70,7 @@
 import com.android.systemui.qs.tiles.dialog.InternetDialogFactory;
 import com.android.systemui.recents.OverviewProxyService;
 import com.android.systemui.screenrecord.RecordingController;
+import com.android.systemui.settings.UserTracker;
 import com.android.systemui.shade.ShadeController;
 import com.android.systemui.shared.plugins.PluginManager;
 import com.android.systemui.shared.system.ActivityManagerWrapper;
@@ -357,6 +358,7 @@
     @Inject Lazy<GroupExpansionManager> mGroupExpansionManagerLazy;
     @Inject Lazy<SystemUIDialogManager> mSystemUIDialogManagerLazy;
     @Inject Lazy<DialogLaunchAnimator> mDialogLaunchAnimatorLazy;
+    @Inject Lazy<UserTracker> mUserTrackerLazy;
 
     @Inject
     public Dependency() {
@@ -564,6 +566,7 @@
         mProviders.put(GroupExpansionManager.class, mGroupExpansionManagerLazy::get);
         mProviders.put(SystemUIDialogManager.class, mSystemUIDialogManagerLazy::get);
         mProviders.put(DialogLaunchAnimator.class, mDialogLaunchAnimatorLazy::get);
+        mProviders.put(UserTracker.class, mUserTrackerLazy::get);
 
         Dependency.setInstance(this);
     }
diff --git a/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java b/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java
index 45f9385..7e3b1389 100644
--- a/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java
+++ b/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java
@@ -26,7 +26,6 @@
 import android.annotation.IdRes;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
-import android.app.ActivityManager;
 import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.Intent;
@@ -901,7 +900,7 @@
     private final BroadcastReceiver mUserSwitchIntentReceiver = new BroadcastReceiver() {
         @Override
         public void onReceive(Context context, Intent intent) {
-            int newUserId = ActivityManager.getCurrentUser();
+            int newUserId = mUserTracker.getUserId();
             if (DEBUG) {
                 Log.d(TAG, "UserSwitched newUserId=" + newUserId);
             }
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/SystemActions.java b/packages/SystemUI/src/com/android/systemui/accessibility/SystemActions.java
index 0a2dc5b..d60cc75 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/SystemActions.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/SystemActions.java
@@ -291,8 +291,11 @@
         mA11yManager.registerSystemAction(actionBack, SYSTEM_ACTION_ID_BACK);
         mA11yManager.registerSystemAction(actionHome, SYSTEM_ACTION_ID_HOME);
         mA11yManager.registerSystemAction(actionRecents, SYSTEM_ACTION_ID_RECENTS);
-        mA11yManager.registerSystemAction(actionNotifications, SYSTEM_ACTION_ID_NOTIFICATIONS);
-        mA11yManager.registerSystemAction(actionQuickSettings, SYSTEM_ACTION_ID_QUICK_SETTINGS);
+        if (mCentralSurfacesOptionalLazy.get().isPresent()) {
+            // These two actions require the CentralSurfaces instance.
+            mA11yManager.registerSystemAction(actionNotifications, SYSTEM_ACTION_ID_NOTIFICATIONS);
+            mA11yManager.registerSystemAction(actionQuickSettings, SYSTEM_ACTION_ID_QUICK_SETTINGS);
+        }
         mA11yManager.registerSystemAction(actionPowerDialog, SYSTEM_ACTION_ID_POWER_DIALOG);
         mA11yManager.registerSystemAction(actionLockScreen, SYSTEM_ACTION_ID_LOCK_SCREEN);
         mA11yManager.registerSystemAction(actionTakeScreenshot, SYSTEM_ACTION_ID_TAKE_SCREENSHOT);
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayer.java b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayer.java
index 33e155d..b8f14ae 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayer.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayer.java
@@ -16,14 +16,19 @@
 
 package com.android.systemui.accessibility.floatingmenu;
 
+import static com.android.internal.accessibility.common.ShortcutConstants.AccessibilityFragmentType.INVISIBLE_TOGGLE;
+import static com.android.internal.accessibility.util.AccessibilityUtils.getAccessibilityServiceFragmentType;
+import static com.android.internal.accessibility.util.AccessibilityUtils.setAccessibilityServiceState;
 import static com.android.systemui.accessibility.floatingmenu.MenuMessageView.Index;
 
+import android.accessibilityservice.AccessibilityServiceInfo;
 import android.annotation.IntDef;
 import android.annotation.SuppressLint;
 import android.content.Context;
 import android.content.res.Configuration;
 import android.os.Handler;
 import android.os.Looper;
+import android.os.UserHandle;
 import android.provider.Settings;
 import android.util.PluralsMessageFormatter;
 import android.view.MotionEvent;
@@ -82,8 +87,22 @@
     final Runnable mDismissMenuAction = new Runnable() {
         @Override
         public void run() {
-            Settings.Secure.putString(getContext().getContentResolver(),
-                    Settings.Secure.ACCESSIBILITY_BUTTON_TARGETS, /* value= */ "");
+            Settings.Secure.putStringForUser(getContext().getContentResolver(),
+                    Settings.Secure.ACCESSIBILITY_BUTTON_TARGETS, /* value= */ "",
+                    UserHandle.USER_CURRENT);
+
+            // Should disable the corresponding service when the fragment type is
+            // INVISIBLE_TOGGLE, which will enable service when the shortcut is on.
+            final List<AccessibilityServiceInfo> serviceInfoList =
+                    mAccessibilityManager.getEnabledAccessibilityServiceList(
+                            AccessibilityServiceInfo.FEEDBACK_ALL_MASK);
+            serviceInfoList.forEach(info -> {
+                if (getAccessibilityServiceFragmentType(info) == INVISIBLE_TOGGLE) {
+                    setAccessibilityServiceState(mContext, info.getComponentName(), /* enabled= */
+                            false);
+                }
+            });
+
             mFloatingMenu.hide();
         }
     };
diff --git a/packages/SystemUI/src/com/android/systemui/battery/BatteryMeterViewController.java b/packages/SystemUI/src/com/android/systemui/battery/BatteryMeterViewController.java
index 77cb9d1..f4ec33a 100644
--- a/packages/SystemUI/src/com/android/systemui/battery/BatteryMeterViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/battery/BatteryMeterViewController.java
@@ -17,21 +17,23 @@
 
 import static android.provider.Settings.System.SHOW_BATTERY_PERCENT;
 
-import android.app.ActivityManager;
 import android.content.ContentResolver;
+import android.content.Context;
 import android.database.ContentObserver;
 import android.net.Uri;
 import android.os.Handler;
+import android.os.HandlerExecutor;
 import android.provider.Settings;
 import android.text.TextUtils;
 import android.util.ArraySet;
 import android.view.View;
 
-import com.android.systemui.broadcast.BroadcastDispatcher;
+import androidx.annotation.NonNull;
+
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.flags.FeatureFlags;
 import com.android.systemui.flags.Flags;
-import com.android.systemui.settings.CurrentUserTracker;
+import com.android.systemui.settings.UserTracker;
 import com.android.systemui.statusbar.phone.StatusBarIconController;
 import com.android.systemui.statusbar.policy.BatteryController;
 import com.android.systemui.statusbar.policy.ConfigurationController;
@@ -44,12 +46,13 @@
 public class BatteryMeterViewController extends ViewController<BatteryMeterView> {
     private final ConfigurationController mConfigurationController;
     private final TunerService mTunerService;
+    private final Handler mMainHandler;
     private final ContentResolver mContentResolver;
     private final BatteryController mBatteryController;
 
     private final String mSlotBattery;
     private final SettingObserver mSettingObserver;
-    private final CurrentUserTracker mCurrentUserTracker;
+    private final UserTracker mUserTracker;
 
     private final ConfigurationController.ConfigurationListener mConfigurationListener =
             new ConfigurationController.ConfigurationListener() {
@@ -93,6 +96,16 @@
                 }
             };
 
+    private final UserTracker.Callback mUserChangedCallback =
+            new UserTracker.Callback() {
+                @Override
+                public void onUserChanged(int newUser, @NonNull Context userContext) {
+                    mContentResolver.unregisterContentObserver(mSettingObserver);
+                    registerShowBatteryPercentObserver(newUser);
+                    mView.updateShowPercent();
+                }
+            };
+
     // Some places may need to show the battery conditionally, and not obey the tuner
     private boolean mIgnoreTunerUpdates;
     private boolean mIsSubscribedForTunerUpdates;
@@ -100,16 +113,18 @@
     @Inject
     public BatteryMeterViewController(
             BatteryMeterView view,
+            UserTracker userTracker,
             ConfigurationController configurationController,
             TunerService tunerService,
-            BroadcastDispatcher broadcastDispatcher,
             @Main Handler mainHandler,
             ContentResolver contentResolver,
             FeatureFlags featureFlags,
             BatteryController batteryController) {
         super(view);
+        mUserTracker = userTracker;
         mConfigurationController = configurationController;
         mTunerService = tunerService;
+        mMainHandler = mainHandler;
         mContentResolver = contentResolver;
         mBatteryController = batteryController;
 
@@ -117,15 +132,7 @@
         mView.setDisplayShieldEnabled(featureFlags.isEnabled(Flags.BATTERY_SHIELD_ICON));
 
         mSlotBattery = getResources().getString(com.android.internal.R.string.status_bar_battery);
-        mSettingObserver = new SettingObserver(mainHandler);
-        mCurrentUserTracker = new CurrentUserTracker(broadcastDispatcher) {
-            @Override
-            public void onUserSwitched(int newUserId) {
-                contentResolver.unregisterContentObserver(mSettingObserver);
-                registerShowBatteryPercentObserver(newUserId);
-                mView.updateShowPercent();
-            }
-        };
+        mSettingObserver = new SettingObserver(mMainHandler);
     }
 
     @Override
@@ -134,9 +141,9 @@
         subscribeForTunerUpdates();
         mBatteryController.addCallback(mBatteryStateChangeCallback);
 
-        registerShowBatteryPercentObserver(ActivityManager.getCurrentUser());
+        registerShowBatteryPercentObserver(mUserTracker.getUserId());
         registerGlobalBatteryUpdateObserver();
-        mCurrentUserTracker.startTracking();
+        mUserTracker.addCallback(mUserChangedCallback, new HandlerExecutor(mMainHandler));
 
         mView.updateShowPercent();
     }
@@ -147,7 +154,7 @@
         unsubscribeFromTunerUpdates();
         mBatteryController.removeCallback(mBatteryStateChangeCallback);
 
-        mCurrentUserTracker.stopTracking();
+        mUserTracker.removeCallback(mUserChangedCallback);
         mContentResolver.unregisterContentObserver(mSettingObserver);
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/TEST_MAPPING b/packages/SystemUI/src/com/android/systemui/biometrics/TEST_MAPPING
new file mode 100644
index 0000000..794eba4
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/TEST_MAPPING
@@ -0,0 +1,16 @@
+{
+  "presubmit": [
+    {
+      // TODO(b/251476085): Consider merging with SystemUIGoogleScreenshotTests (in U+)
+      "name": "SystemUIGoogleBiometricsScreenshotTests",
+      "options": [
+        {
+          "exclude-annotation": "org.junit.Ignore"
+        },
+        {
+          "exclude-annotation": "androidx.test.filters.FlakyTest"
+        }
+      ]
+    }
+  ]
+}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsAnimationView.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsAnimationView.java
index ad96612..bdad413 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsAnimationView.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsAnimationView.java
@@ -37,6 +37,9 @@
     private float mDialogSuggestedAlpha = 1f;
     private float mNotificationShadeExpansion = 0f;
 
+    // Used for Udfps ellipse detection when flag is true, set by AnimationViewController
+    boolean mUseExpandedOverlay = false;
+
     // mAlpha takes into consideration the status bar expansion amount and dialog suggested alpha
     private int mAlpha;
     boolean mPauseAuth;
@@ -118,6 +121,24 @@
     }
 
     /**
+     * Converts coordinates of RectF relative to the screen to coordinates relative to this view.
+     *
+     * @param bounds RectF based off screen coordinates in current orientation
+     */
+    RectF getBoundsRelativeToView(RectF bounds) {
+        int[] pos = getLocationOnScreen();
+
+        RectF output = new RectF(
+                bounds.left - pos[0],
+                bounds.top - pos[1],
+                bounds.right - pos[0],
+                bounds.bottom - pos[1]
+        );
+
+        return output;
+    }
+
+    /**
      * Set the suggested alpha based on whether a dialog was recently shown or hidden.
      * @param dialogSuggestedAlpha value from 0f to 1f.
      */
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
index 5469d29..1d4281f 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
@@ -166,6 +166,7 @@
 
     // The current request from FingerprintService. Null if no current request.
     @Nullable UdfpsControllerOverlay mOverlay;
+    @Nullable private UdfpsEllipseDetection mUdfpsEllipseDetection;
 
     // The fingerprint AOD trigger doesn't provide an ACTION_UP/ACTION_CANCEL event to tell us when
     // to turn off high brightness mode. To get around this limitation, the state of the AOD
@@ -354,6 +355,10 @@
         if (!mOverlayParams.equals(overlayParams)) {
             mOverlayParams = overlayParams;
 
+            if (mFeatureFlags.isEnabled(Flags.UDFPS_ELLIPSE_DETECTION)) {
+                mUdfpsEllipseDetection.updateOverlayParams(overlayParams);
+            }
+
             final boolean wasShowingAltAuth = mKeyguardViewManager.isShowingAlternateBouncer();
 
             // When the bounds change it's always necessary to re-create the overlay's window with
@@ -493,8 +498,23 @@
                     mVelocityTracker.clear();
                 }
 
-                boolean withinSensorArea =
+                boolean withinSensorArea;
+                if (mFeatureFlags.isEnabled(Flags.UDFPS_NEW_TOUCH_DETECTION)) {
+                    if (mFeatureFlags.isEnabled(Flags.UDFPS_ELLIPSE_DETECTION)) {
+                        // Ellipse detection
+                        withinSensorArea = mUdfpsEllipseDetection.isGoodEllipseOverlap(event);
+                    } else {
+                        // Centroid with expanded overlay
+                        withinSensorArea =
+                            isWithinSensorArea(udfpsView, event.getRawX(),
+                                        event.getRawY(), fromUdfpsView);
+                    }
+                } else {
+                    // Centroid with sensor sized view
+                    withinSensorArea =
                         isWithinSensorArea(udfpsView, event.getX(), event.getY(), fromUdfpsView);
+                }
+
                 if (withinSensorArea) {
                     Trace.beginAsyncSection("UdfpsController.e2e.onPointerDown", 0);
                     Log.v(TAG, "onTouch | action down");
@@ -525,9 +545,25 @@
                         ? event.getPointerId(0)
                         : event.findPointerIndex(mActivePointerId);
                 if (idx == event.getActionIndex()) {
-                    boolean actionMoveWithinSensorArea =
-                            isWithinSensorArea(udfpsView, event.getX(idx), event.getY(idx),
-                                    fromUdfpsView);
+                    boolean actionMoveWithinSensorArea;
+                    if (mFeatureFlags.isEnabled(Flags.UDFPS_NEW_TOUCH_DETECTION)) {
+                        if (mFeatureFlags.isEnabled(Flags.UDFPS_ELLIPSE_DETECTION)) {
+                            // Ellipse detection
+                            actionMoveWithinSensorArea =
+                                    mUdfpsEllipseDetection.isGoodEllipseOverlap(event);
+                        } else {
+                            // Centroid with expanded overlay
+                            actionMoveWithinSensorArea =
+                                isWithinSensorArea(udfpsView, event.getRawX(idx),
+                                        event.getRawY(idx), fromUdfpsView);
+                        }
+                    } else {
+                        // Centroid with sensor sized view
+                        actionMoveWithinSensorArea =
+                            isWithinSensorArea(udfpsView, event.getX(idx),
+                                    event.getY(idx), fromUdfpsView);
+                    }
+
                     if ((fromUdfpsView || actionMoveWithinSensorArea)
                             && shouldTryToDismissKeyguard()) {
                         Log.v(TAG, "onTouch | dismiss keyguard ACTION_MOVE");
@@ -725,6 +761,10 @@
 
         udfpsHapticsSimulator.setUdfpsController(this);
         udfpsShell.setUdfpsOverlayController(mUdfpsOverlayController);
+
+        if (featureFlags.isEnabled(Flags.UDFPS_ELLIPSE_DETECTION)) {
+            mUdfpsEllipseDetection = new UdfpsEllipseDetection(mOverlayParams);
+        }
     }
 
     /**
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt
index 0bb24f8..8db4927 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt
@@ -49,6 +49,7 @@
 import com.android.systemui.animation.ActivityLaunchAnimator
 import com.android.systemui.dump.DumpManager
 import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.flags.Flags
 import com.android.systemui.keyguard.domain.interactor.PrimaryBouncerInteractor
 import com.android.systemui.plugins.statusbar.StatusBarStateController
 import com.android.systemui.shade.ShadeExpansionStateManager
@@ -103,6 +104,7 @@
         private set
 
     private var overlayParams: UdfpsOverlayParams = UdfpsOverlayParams()
+    private var sensorBounds: Rect = Rect()
 
     private var overlayTouchListener: TouchExplorationStateChangeListener? = null
 
@@ -120,6 +122,10 @@
         privateFlags = WindowManager.LayoutParams.PRIVATE_FLAG_TRUSTED_OVERLAY
         // Avoid announcing window title.
         accessibilityTitle = " "
+
+        if (featureFlags.isEnabled(Flags.UDFPS_NEW_TOUCH_DETECTION)) {
+            inputFeatures = WindowManager.LayoutParams.INPUT_FEATURE_SPY
+        }
     }
 
     /** A helper if the [requestReason] was due to enrollment. */
@@ -160,6 +166,7 @@
     fun show(controller: UdfpsController, params: UdfpsOverlayParams): Boolean {
         if (overlayView == null) {
             overlayParams = params
+            sensorBounds = Rect(params.sensorBounds)
             try {
                 overlayView = (inflater.inflate(
                     R.layout.udfps_view, null, false
@@ -178,6 +185,7 @@
                     }
 
                     windowManager.addView(this, coreLayoutParams.updateDimensions(animation))
+                    sensorRect = sensorBounds
                     touchExplorationEnabled = accessibilityManager.isTouchExplorationEnabled
                     overlayTouchListener = TouchExplorationStateChangeListener {
                         if (accessibilityManager.isTouchExplorationEnabled) {
@@ -194,6 +202,7 @@
                         overlayTouchListener!!
                     )
                     overlayTouchListener?.onTouchExplorationStateChanged(true)
+                    useExpandedOverlay = featureFlags.isEnabled(Flags.UDFPS_NEW_TOUCH_DETECTION)
                 }
             } catch (e: RuntimeException) {
                 Log.e(TAG, "showUdfpsOverlay | failed to add window", e)
@@ -225,13 +234,14 @@
             REASON_ENROLL_ENROLLING -> {
                 UdfpsEnrollViewController(
                     view.addUdfpsView(R.layout.udfps_enroll_view) {
-                        updateSensorLocation(overlayParams.sensorBounds)
+                        updateSensorLocation(sensorBounds)
                     },
                     enrollHelper ?: throw IllegalStateException("no enrollment helper"),
                     statusBarStateController,
                     shadeExpansionStateManager,
                     dialogManager,
                     dumpManager,
+                    featureFlags,
                     overlayParams.scaleFactor
                 )
             }
@@ -420,7 +430,12 @@
         }
 
         // Original sensorBounds assume portrait mode.
-        val rotatedSensorBounds = Rect(overlayParams.sensorBounds)
+        var rotatedBounds =
+            if (featureFlags.isEnabled(Flags.UDFPS_NEW_TOUCH_DETECTION)) {
+                Rect(overlayParams.overlayBounds)
+            } else {
+                Rect(overlayParams.sensorBounds)
+            }
 
         val rot = overlayParams.rotation
         if (rot == Surface.ROTATION_90 || rot == Surface.ROTATION_270) {
@@ -434,18 +449,27 @@
             } else {
                 Log.v(TAG, "Rotate UDFPS bounds " + Surface.rotationToString(rot))
                 RotationUtils.rotateBounds(
-                    rotatedSensorBounds,
+                    rotatedBounds,
                     overlayParams.naturalDisplayWidth,
                     overlayParams.naturalDisplayHeight,
                     rot
                 )
+
+                if (featureFlags.isEnabled(Flags.UDFPS_NEW_TOUCH_DETECTION)) {
+                    RotationUtils.rotateBounds(
+                            sensorBounds,
+                            overlayParams.naturalDisplayWidth,
+                            overlayParams.naturalDisplayHeight,
+                            rot
+                    )
+                }
             }
         }
 
-        x = rotatedSensorBounds.left - paddingX
-        y = rotatedSensorBounds.top - paddingY
-        height = rotatedSensorBounds.height() + 2 * paddingX
-        width = rotatedSensorBounds.width() + 2 * paddingY
+        x = rotatedBounds.left - paddingX
+        y = rotatedBounds.top - paddingY
+        height = rotatedBounds.height() + 2 * paddingX
+        width = rotatedBounds.width() + 2 * paddingY
 
         return this
     }
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEllipseDetection.kt b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEllipseDetection.kt
new file mode 100644
index 0000000..8ae4775
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEllipseDetection.kt
@@ -0,0 +1,92 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.biometrics
+
+import android.graphics.Point
+import android.graphics.Rect
+import android.util.RotationUtils
+import android.view.MotionEvent
+import kotlin.math.cos
+import kotlin.math.pow
+import kotlin.math.sin
+
+private const val TAG = "UdfpsEllipseDetection"
+
+private const val NEEDED_POINTS = 2
+
+class UdfpsEllipseDetection(overlayParams: UdfpsOverlayParams) {
+    var sensorRect = Rect()
+    var points: Array<Point> = emptyArray()
+
+    init {
+        sensorRect = Rect(overlayParams.sensorBounds)
+
+        points = calculateSensorPoints(sensorRect)
+    }
+
+    fun updateOverlayParams(params: UdfpsOverlayParams) {
+        sensorRect = Rect(params.sensorBounds)
+
+        val rot = params.rotation
+        RotationUtils.rotateBounds(
+            sensorRect,
+            params.naturalDisplayWidth,
+            params.naturalDisplayHeight,
+            rot
+        )
+
+        points = calculateSensorPoints(sensorRect)
+    }
+
+    fun isGoodEllipseOverlap(event: MotionEvent): Boolean {
+        return points.count { checkPoint(event, it) } >= NEEDED_POINTS
+    }
+
+    private fun checkPoint(event: MotionEvent, point: Point): Boolean {
+        // Calculate if sensor point is within ellipse
+        // Formula: ((cos(o)(xE - xS) + sin(o)(yE - yS))^2 / a^2) + ((sin(o)(xE - xS) + cos(o)(yE -
+        // yS))^2 / b^2) <= 1
+        val a: Float = cos(event.orientation) * (point.x - event.rawX)
+        val b: Float = sin(event.orientation) * (point.y - event.rawY)
+        val c: Float = sin(event.orientation) * (point.x - event.rawX)
+        val d: Float = cos(event.orientation) * (point.y - event.rawY)
+        val result =
+            (a + b).pow(2) / (event.touchMinor / 2).pow(2) +
+                (c - d).pow(2) / (event.touchMajor / 2).pow(2)
+
+        return result <= 1
+    }
+}
+
+fun calculateSensorPoints(sensorRect: Rect): Array<Point> {
+    val sensorX = sensorRect.centerX()
+    val sensorY = sensorRect.centerY()
+    val cornerOffset: Int = sensorRect.width() / 4
+    val sideOffset: Int = sensorRect.width() / 3
+
+    return arrayOf(
+        Point(sensorX - cornerOffset, sensorY - cornerOffset),
+        Point(sensorX, sensorY - sideOffset),
+        Point(sensorX + cornerOffset, sensorY - cornerOffset),
+        Point(sensorX - sideOffset, sensorY),
+        Point(sensorX, sensorY),
+        Point(sensorX + sideOffset, sensorY),
+        Point(sensorX - cornerOffset, sensorY + cornerOffset),
+        Point(sensorX, sensorY + sideOffset),
+        Point(sensorX + cornerOffset, sensorY + cornerOffset)
+    )
+}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollProgressBarDrawable.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollProgressBarDrawable.java
index 49e378e..af7e0b6 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollProgressBarDrawable.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollProgressBarDrawable.java
@@ -99,12 +99,11 @@
         mProgressColor = context.getColor(R.color.udfps_enroll_progress);
         final AccessibilityManager am = context.getSystemService(AccessibilityManager.class);
         mIsAccessibilityEnabled = am.isTouchExplorationEnabled();
+        mOnFirstBucketFailedColor = context.getColor(R.color.udfps_moving_target_fill_error);
         if (!mIsAccessibilityEnabled) {
             mHelpColor = context.getColor(R.color.udfps_enroll_progress_help);
-            mOnFirstBucketFailedColor = context.getColor(R.color.udfps_moving_target_fill_error);
         } else {
             mHelpColor = context.getColor(R.color.udfps_enroll_progress_help_with_talkback);
-            mOnFirstBucketFailedColor = mHelpColor;
         }
         mCheckmarkDrawable = context.getDrawable(R.drawable.udfps_enroll_checkmark);
         mCheckmarkDrawable.mutate();
@@ -197,6 +196,7 @@
             }
         }
 
+        mShowingHelp = showingHelp;
         mRemainingSteps = remainingSteps;
         mTotalSteps = totalSteps;
 
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollView.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollView.java
index 69c37b2..87be42c 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollView.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollView.java
@@ -18,6 +18,7 @@
 
 import android.content.Context;
 import android.graphics.Rect;
+import android.graphics.RectF;
 import android.os.Handler;
 import android.os.Looper;
 import android.util.AttributeSet;
@@ -41,6 +42,9 @@
     @NonNull private ImageView mFingerprintView;
     @NonNull private ImageView mFingerprintProgressView;
 
+    private LayoutParams mProgressParams;
+    private float mProgressBarRadius;
+
     public UdfpsEnrollView(Context context, @Nullable AttributeSet attrs) {
         super(context, attrs);
         mFingerprintDrawable = new UdfpsEnrollDrawable(mContext);
@@ -57,6 +61,32 @@
     }
 
     @Override
+    void onSensorRectUpdated(RectF bounds) {
+        if (mUseExpandedOverlay) {
+            RectF converted = getBoundsRelativeToView(bounds);
+
+            mProgressParams = new LayoutParams(
+                    (int) (converted.width() + mProgressBarRadius * 2),
+                    (int) (converted.height() + mProgressBarRadius * 2));
+            mProgressParams.setMargins(
+                    (int) (converted.left - mProgressBarRadius),
+                    (int) (converted.top - mProgressBarRadius),
+                    (int) (converted.right + mProgressBarRadius),
+                    (int) (converted.bottom + mProgressBarRadius)
+            );
+
+            mFingerprintProgressView.setLayoutParams(mProgressParams);
+            super.onSensorRectUpdated(converted);
+        } else {
+            super.onSensorRectUpdated(bounds);
+        }
+    }
+
+    void setProgressBarRadius(float radius) {
+        mProgressBarRadius = radius;
+    }
+
+    @Override
     public UdfpsDrawable getDrawable() {
         return mFingerprintDrawable;
     }
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollViewController.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollViewController.java
index e01273f..4017665 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollViewController.java
@@ -21,6 +21,8 @@
 
 import com.android.systemui.R;
 import com.android.systemui.dump.DumpManager;
+import com.android.systemui.flags.FeatureFlags;
+import com.android.systemui.flags.Flags;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.shade.ShadeExpansionStateManager;
 import com.android.systemui.statusbar.phone.SystemUIDialogManager;
@@ -57,6 +59,7 @@
             @NonNull ShadeExpansionStateManager shadeExpansionStateManager,
             @NonNull SystemUIDialogManager systemUIDialogManager,
             @NonNull DumpManager dumpManager,
+            @NonNull FeatureFlags featureFlags,
             float scaleFactor) {
         super(view, statusBarStateController, shadeExpansionStateManager, systemUIDialogManager,
                 dumpManager);
@@ -64,6 +67,11 @@
                 R.integer.config_udfpsEnrollProgressBar));
         mEnrollHelper = enrollHelper;
         mView.setEnrollHelper(mEnrollHelper);
+        mView.setProgressBarRadius(mEnrollProgressBarRadius);
+
+        if (featureFlags.isEnabled(Flags.UDFPS_NEW_TOUCH_DETECTION)) {
+            mView.mUseExpandedOverlay = true;
+        }
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardView.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardView.java
index bc274a0..339b8ca 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardView.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardView.java
@@ -26,6 +26,7 @@
 import android.content.Context;
 import android.graphics.PorterDuff;
 import android.graphics.PorterDuffColorFilter;
+import android.graphics.RectF;
 import android.util.AttributeSet;
 import android.util.MathUtils;
 import android.view.View;
@@ -75,6 +76,8 @@
     private int mAnimationType = ANIMATION_NONE;
     private boolean mFullyInflated;
 
+    private LayoutParams mParams;
+
     public UdfpsKeyguardView(Context context, @Nullable AttributeSet attrs) {
         super(context, attrs);
         mFingerprintDrawable = new UdfpsFpDrawable(context);
@@ -239,6 +242,22 @@
         updateAlpha();
     }
 
+    @Override
+    void onSensorRectUpdated(RectF bounds) {
+        super.onSensorRectUpdated(bounds);
+
+        if (mUseExpandedOverlay) {
+            mParams = new LayoutParams((int) bounds.width(), (int) bounds.height());
+            RectF converted = getBoundsRelativeToView(bounds);
+            mParams.setMargins(
+                    (int) converted.left,
+                    (int) converted.top,
+                    (int) converted.right,
+                    (int) converted.bottom
+            );
+        }
+    }
+
     /**
      * Animates in the bg protection circle behind the fp icon to highlight the icon.
      */
@@ -277,6 +296,7 @@
         pw.println("    mUdfpsRequested=" + mUdfpsRequested);
         pw.println("    mInterpolatedDarkAmount=" + mInterpolatedDarkAmount);
         pw.println("    mAnimationType=" + mAnimationType);
+        pw.println("    mUseExpandedOverlay=" + mUseExpandedOverlay);
     }
 
     private final AsyncLayoutInflater.OnInflateFinishedListener mLayoutInflaterFinishListener =
@@ -291,7 +311,12 @@
             updatePadding();
             updateColor();
             updateAlpha();
-            parent.addView(view);
+
+            if (mUseExpandedOverlay) {
+                parent.addView(view, mParams);
+            } else {
+                parent.addView(view);
+            }
 
             // requires call to invalidate to update the color
             mLockScreenFp.addValueCallback(
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewController.kt b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewController.kt
index 91967f9..63144fc 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewController.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewController.kt
@@ -52,7 +52,6 @@
 import java.io.PrintWriter
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.Job
-import kotlinx.coroutines.flow.collect
 import kotlinx.coroutines.launch
 
 /** Class that coordinates non-HBM animations during keyguard authentication. */
@@ -82,6 +81,8 @@
         systemUIDialogManager,
         dumpManager
     ) {
+    private val useExpandedOverlay: Boolean =
+        featureFlags.isEnabled(Flags.UDFPS_NEW_TOUCH_DETECTION)
     private val isModernBouncerEnabled: Boolean = featureFlags.isEnabled(Flags.MODERN_BOUNCER)
     private var showingUdfpsBouncer = false
     private var udfpsRequested = false
@@ -233,7 +234,13 @@
                 if (transitionToFullShadeProgress != 0f) {
                     return
                 }
-                udfpsController.onTouch(event)
+
+                // Forwarding touches not needed with expanded overlay
+                if (useExpandedOverlay) {
+                    return
+                } else {
+                    udfpsController.onTouch(event)
+                }
             }
         }
 
@@ -322,6 +329,7 @@
         keyguardViewManager.setAlternateBouncer(mAlternateBouncer)
         lockScreenShadeTransitionController.udfpsKeyguardViewController = this
         activityLaunchAnimator.addListener(activityLaunchAnimatorListener)
+        view.mUseExpandedOverlay = useExpandedOverlay
     }
 
     override fun onViewDetached() {
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsView.kt b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsView.kt
index a15456d..4a8877e 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsView.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsView.kt
@@ -20,6 +20,7 @@
 import android.graphics.Color
 import android.graphics.Paint
 import android.graphics.PointF
+import android.graphics.Rect
 import android.graphics.RectF
 import android.util.AttributeSet
 import android.util.Log
@@ -38,9 +39,12 @@
     attrs: AttributeSet?
 ) : FrameLayout(context, attrs), DozeReceiver {
 
+    // Use expanded overlay when feature flag is true, set by UdfpsViewController
+    var useExpandedOverlay: Boolean = false
+
     // sensorRect may be bigger than the sensor. True sensor dimensions are defined in
     // overlayParams.sensorBounds
-    private val sensorRect = RectF()
+    var sensorRect = Rect()
     private var mUdfpsDisplayMode: UdfpsDisplayModeProvider? = null
     private val debugTextPaint = Paint().apply {
         isAntiAlias = true
@@ -92,13 +96,19 @@
         val paddingX = animationViewController?.paddingX ?: 0
         val paddingY = animationViewController?.paddingY ?: 0
 
-        sensorRect.set(
-            paddingX.toFloat(),
-            paddingY.toFloat(),
-            (overlayParams.sensorBounds.width() + paddingX).toFloat(),
-            (overlayParams.sensorBounds.height() + paddingY).toFloat()
-        )
-        animationViewController?.onSensorRectUpdated(RectF(sensorRect))
+        // Updates sensor rect in relation to the overlay view
+        if (useExpandedOverlay) {
+            animationViewController?.onSensorRectUpdated(RectF(sensorRect))
+        } else {
+            sensorRect.set(
+                    paddingX,
+                    paddingY,
+                    (overlayParams.sensorBounds.width() + paddingX),
+                    (overlayParams.sensorBounds.height() + paddingY)
+            )
+
+            animationViewController?.onSensorRectUpdated(RectF(sensorRect))
+        }
     }
 
     fun onTouchOutsideView() {
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/CredentialPasswordViewBinder.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/CredentialPasswordViewBinder.kt
index c619648..6fb8e34 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/CredentialPasswordViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/CredentialPasswordViewBinder.kt
@@ -6,6 +6,8 @@
 import android.view.inputmethod.InputMethodManager
 import android.widget.ImeAwareEditText
 import android.widget.TextView
+import android.window.OnBackInvokedCallback
+import android.window.OnBackInvokedDispatcher
 import androidx.lifecycle.Lifecycle
 import androidx.lifecycle.repeatOnLifecycle
 import com.android.systemui.R
@@ -13,7 +15,7 @@
 import com.android.systemui.biometrics.ui.CredentialView
 import com.android.systemui.biometrics.ui.viewmodel.CredentialViewModel
 import com.android.systemui.lifecycle.repeatWhenAttached
-import kotlinx.coroutines.flow.collect
+import kotlinx.coroutines.awaitCancellation
 import kotlinx.coroutines.launch
 
 /** Sub-binder for the [CredentialPasswordView]. */
@@ -24,14 +26,19 @@
         view: CredentialPasswordView,
         host: CredentialView.Host,
         viewModel: CredentialViewModel,
+        requestFocusForInput: Boolean,
     ) {
         val imeManager = view.context.getSystemService(InputMethodManager::class.java)!!
 
         val passwordField: ImeAwareEditText = view.requireViewById(R.id.lockPassword)
 
+        val onBackInvokedCallback = OnBackInvokedCallback { host.onCredentialAborted() }
+
         view.repeatWhenAttached {
-            passwordField.requestFocus()
-            passwordField.scheduleShowSoftInput()
+            if (requestFocusForInput) {
+                passwordField.requestFocus()
+                passwordField.scheduleShowSoftInput()
+            }
 
             repeatOnLifecycle(Lifecycle.State.STARTED) {
                 // observe credential validation attempts and submit/cancel buttons
@@ -43,9 +50,7 @@
                                 launch { viewModel.checkCredential(text, header) }
                             }
                         )
-                        passwordField.setOnKeyListener(
-                            OnBackButtonListener { host.onCredentialAborted() }
-                        )
+                        passwordField.setOnKeyListener(OnBackButtonListener(onBackInvokedCallback))
                     }
                 }
 
@@ -66,18 +71,35 @@
                         }
                     }
                 }
+
+                val onBackInvokedDispatcher = view.findOnBackInvokedDispatcher()
+                if (onBackInvokedDispatcher != null) {
+                    launch {
+                            onBackInvokedDispatcher.registerOnBackInvokedCallback(
+                                OnBackInvokedDispatcher.PRIORITY_DEFAULT,
+                                onBackInvokedCallback
+                            )
+                            awaitCancellation()
+                        }
+                        .invokeOnCompletion {
+                            onBackInvokedDispatcher.unregisterOnBackInvokedCallback(
+                                onBackInvokedCallback
+                            )
+                        }
+                }
             }
         }
     }
 }
 
-private class OnBackButtonListener(private val onBack: () -> Unit) : View.OnKeyListener {
+private class OnBackButtonListener(private val onBackInvokedCallback: OnBackInvokedCallback) :
+    View.OnKeyListener {
     override fun onKey(v: View, keyCode: Int, event: KeyEvent): Boolean {
         if (keyCode != KeyEvent.KEYCODE_BACK) {
             return false
         }
         if (event.action == KeyEvent.ACTION_UP) {
-            onBack()
+            onBackInvokedCallback.onBackInvoked()
         }
         return true
     }
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/CredentialPatternViewBinder.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/CredentialPatternViewBinder.kt
index 4765551..b692ad3 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/CredentialPatternViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/CredentialPatternViewBinder.kt
@@ -9,7 +9,6 @@
 import com.android.systemui.biometrics.ui.CredentialView
 import com.android.systemui.biometrics.ui.viewmodel.CredentialViewModel
 import com.android.systemui.lifecycle.repeatWhenAttached
-import kotlinx.coroutines.flow.collect
 import kotlinx.coroutines.launch
 
 /** Sub-binder for the [CredentialPatternView]. */
@@ -30,7 +29,7 @@
                     viewModel.header.collect { header ->
                         lockPatternView.setOnPatternListener(
                             OnPatternDetectedListener { pattern ->
-                                if (pattern.isPatternLongEnough()) {
+                                if (pattern.isPatternTooShort()) {
                                     // Pattern size is less than the minimum
                                     // do not count it as a failed attempt
                                     viewModel.showPatternTooShortError()
@@ -71,5 +70,5 @@
     }
 }
 
-private fun List<LockPatternView.Cell>.isPatternLongEnough(): Boolean =
+private fun List<LockPatternView.Cell>.isPatternTooShort(): Boolean =
     size < LockPatternUtils.MIN_PATTERN_REGISTER_FAIL
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/CredentialViewBinder.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/CredentialViewBinder.kt
index fcc9487..e2d36dc 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/CredentialViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/CredentialViewBinder.kt
@@ -17,7 +17,6 @@
 import com.android.systemui.lifecycle.repeatWhenAttached
 import kotlinx.coroutines.Job
 import kotlinx.coroutines.delay
-import kotlinx.coroutines.flow.collect
 import kotlinx.coroutines.flow.filter
 import kotlinx.coroutines.flow.onEach
 import kotlinx.coroutines.launch
@@ -40,6 +39,7 @@
         panelViewController: AuthPanelController,
         animatePanel: Boolean,
         maxErrorDuration: Long = 3_000L,
+        requestFocusForInput: Boolean = true,
     ) {
         val titleView: TextView = view.requireViewById(R.id.title)
         val subtitleView: TextView = view.requireViewById(R.id.subtitle)
@@ -110,7 +110,8 @@
 
         // bind the auth widget
         when (view) {
-            is CredentialPasswordView -> CredentialPasswordViewBinder.bind(view, host, viewModel)
+            is CredentialPasswordView ->
+                CredentialPasswordViewBinder.bind(view, host, viewModel, requestFocusForInput)
             is CredentialPatternView -> CredentialPatternViewBinder.bind(view, host, viewModel)
             else -> throw IllegalStateException("unexpected view type: ${view.javaClass.name}")
         }
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/BrightLineFalsingManager.java b/packages/SystemUI/src/com/android/systemui/classifier/BrightLineFalsingManager.java
index beaccba..e8e1f2e 100644
--- a/packages/SystemUI/src/com/android/systemui/classifier/BrightLineFalsingManager.java
+++ b/packages/SystemUI/src/com/android/systemui/classifier/BrightLineFalsingManager.java
@@ -231,7 +231,8 @@
 
         // check for false tap if it is a seekbar interaction
         if (interactionType == MEDIA_SEEKBAR) {
-            localResult[0] &= isFalseTap(LOW_PENALTY);
+            localResult[0] &= isFalseTap(mFeatureFlags.isEnabled(Flags.MEDIA_FALSING_PENALTY)
+                    ? FalsingManager.MODERATE_PENALTY : FalsingManager.LOW_PENALTY);
         }
 
         logDebug("False Gesture (type: " + interactionType + "): " + localResult[0]);
diff --git a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsEditingActivity.kt b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsEditingActivity.kt
index b11103a..7df0865 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsEditingActivity.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsEditingActivity.kt
@@ -18,6 +18,7 @@
 
 import android.app.ActivityOptions
 import android.content.ComponentName
+import android.content.Context
 import android.content.Intent
 import android.os.Bundle
 import android.util.Log
@@ -33,21 +34,23 @@
 import androidx.recyclerview.widget.ItemTouchHelper
 import androidx.recyclerview.widget.RecyclerView
 import com.android.systemui.R
-import com.android.systemui.broadcast.BroadcastDispatcher
 import com.android.systemui.controls.CustomIconCache
 import com.android.systemui.controls.controller.ControlsControllerImpl
 import com.android.systemui.controls.controller.StructureInfo
 import com.android.systemui.controls.ui.ControlsActivity
 import com.android.systemui.controls.ui.ControlsUiController
-import com.android.systemui.settings.CurrentUserTracker
+import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.settings.UserTracker
+import java.util.concurrent.Executor
 import javax.inject.Inject
 
 /**
  * Activity for rearranging and removing controls for a given structure
  */
 open class ControlsEditingActivity @Inject constructor(
+    @Main private val mainExecutor: Executor,
     private val controller: ControlsControllerImpl,
-    private val broadcastDispatcher: BroadcastDispatcher,
+    private val userTracker: UserTracker,
     private val customIconCache: CustomIconCache,
     private val uiController: ControlsUiController
 ) : ComponentActivity() {
@@ -66,12 +69,12 @@
     private lateinit var subtitle: TextView
     private lateinit var saveButton: View
 
-    private val currentUserTracker = object : CurrentUserTracker(broadcastDispatcher) {
+    private val userTrackerCallback: UserTracker.Callback = object : UserTracker.Callback {
         private val startingUser = controller.currentUserId
 
-        override fun onUserSwitched(newUserId: Int) {
-            if (newUserId != startingUser) {
-                stopTracking()
+        override fun onUserChanged(newUser: Int, userContext: Context) {
+            if (newUser != startingUser) {
+                userTracker.removeCallback(this)
                 finish()
             }
         }
@@ -104,7 +107,7 @@
         super.onStart()
         setUpList()
 
-        currentUserTracker.startTracking()
+        userTracker.addCallback(userTrackerCallback, mainExecutor)
 
         if (DEBUG) {
             Log.d(TAG, "Registered onBackInvokedCallback")
@@ -115,7 +118,7 @@
 
     override fun onStop() {
         super.onStop()
-        currentUserTracker.stopTracking()
+        userTracker.removeCallback(userTrackerCallback)
 
         if (DEBUG) {
             Log.d(TAG, "Unregistered onBackInvokedCallback")
@@ -248,7 +251,7 @@
     }
 
     override fun onDestroy() {
-        currentUserTracker.stopTracking()
+        userTracker.removeCallback(userTrackerCallback)
         super.onDestroy()
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsFavoritingActivity.kt b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsFavoritingActivity.kt
index 9b2a728..3e97d31 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsFavoritingActivity.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsFavoritingActivity.kt
@@ -20,6 +20,7 @@
 import android.animation.AnimatorListenerAdapter
 import android.app.ActivityOptions
 import android.content.ComponentName
+import android.content.Context
 import android.content.Intent
 import android.content.res.Configuration
 import android.os.Bundle
@@ -39,7 +40,6 @@
 import androidx.viewpager2.widget.ViewPager2
 import com.android.systemui.Prefs
 import com.android.systemui.R
-import com.android.systemui.broadcast.BroadcastDispatcher
 import com.android.systemui.controls.ControlsServiceInfo
 import com.android.systemui.controls.TooltipManager
 import com.android.systemui.controls.controller.ControlsControllerImpl
@@ -47,7 +47,7 @@
 import com.android.systemui.controls.ui.ControlsActivity
 import com.android.systemui.controls.ui.ControlsUiController
 import com.android.systemui.dagger.qualifiers.Main
-import com.android.systemui.settings.CurrentUserTracker
+import com.android.systemui.settings.UserTracker
 import java.text.Collator
 import java.util.concurrent.Executor
 import java.util.function.Consumer
@@ -57,7 +57,7 @@
     @Main private val executor: Executor,
     private val controller: ControlsControllerImpl,
     private val listingController: ControlsListingController,
-    private val broadcastDispatcher: BroadcastDispatcher,
+    private val userTracker: UserTracker,
     private val uiController: ControlsUiController
 ) : ComponentActivity() {
 
@@ -95,12 +95,12 @@
     private var cancelLoadRunnable: Runnable? = null
     private var isPagerLoaded = false
 
-    private val currentUserTracker = object : CurrentUserTracker(broadcastDispatcher) {
+    private val userTrackerCallback: UserTracker.Callback = object : UserTracker.Callback {
         private val startingUser = controller.currentUserId
 
-        override fun onUserSwitched(newUserId: Int) {
-            if (newUserId != startingUser) {
-                stopTracking()
+        override fun onUserChanged(newUser: Int, userContext: Context) {
+            if (newUser != startingUser) {
+                userTracker.removeCallback(this)
                 finish()
             }
         }
@@ -363,7 +363,7 @@
         super.onStart()
 
         listingController.addCallback(listingCallback)
-        currentUserTracker.startTracking()
+        userTracker.addCallback(userTrackerCallback, executor)
 
         if (DEBUG) {
             Log.d(TAG, "Registered onBackInvokedCallback")
@@ -388,7 +388,7 @@
         super.onStop()
 
         listingController.removeCallback(listingCallback)
-        currentUserTracker.stopTracking()
+        userTracker.removeCallback(userTrackerCallback)
 
         if (DEBUG) {
             Log.d(TAG, "Unregistered onBackInvokedCallback")
diff --git a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsListingControllerImpl.kt b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsListingControllerImpl.kt
index 115edd11..c6428ef 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsListingControllerImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsListingControllerImpl.kt
@@ -91,11 +91,12 @@
     override var currentUserId = userTracker.userId
         private set
 
-    private val serviceListingCallback = ServiceListing.Callback {
+    private val serviceListingCallback = ServiceListing.Callback { list ->
+        Log.d(TAG, "ServiceConfig reloaded, count: ${list.size}")
+        val newServices = list.map { ControlsServiceInfo(userTracker.userContext, it) }
+        // After here, `list` is not captured, so we don't risk modifying it outside of the callback
         backgroundExecutor.execute {
             if (userChangeInProgress.get() > 0) return@execute
-            Log.d(TAG, "ServiceConfig reloaded, count: ${it.size}")
-            val newServices = it.map { ControlsServiceInfo(userTracker.userContext, it) }
             if (featureFlags.isEnabled(Flags.USE_APP_PANELS)) {
                 newServices.forEach(ControlsServiceInfo::resolvePanelActivity)
             }
diff --git a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsProviderSelectorActivity.kt b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsProviderSelectorActivity.kt
index 47690a7..90bc5d0 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsProviderSelectorActivity.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsProviderSelectorActivity.kt
@@ -18,6 +18,7 @@
 
 import android.app.ActivityOptions
 import android.content.ComponentName
+import android.content.Context
 import android.content.Intent
 import android.os.Bundle
 import android.util.Log
@@ -33,13 +34,12 @@
 import androidx.recyclerview.widget.LinearLayoutManager
 import androidx.recyclerview.widget.RecyclerView
 import com.android.systemui.R
-import com.android.systemui.broadcast.BroadcastDispatcher
 import com.android.systemui.controls.controller.ControlsController
 import com.android.systemui.controls.ui.ControlsActivity
 import com.android.systemui.controls.ui.ControlsUiController
 import com.android.systemui.dagger.qualifiers.Background
 import com.android.systemui.dagger.qualifiers.Main
-import com.android.systemui.settings.CurrentUserTracker
+import com.android.systemui.settings.UserTracker
 import java.util.concurrent.Executor
 import javax.inject.Inject
 
@@ -51,7 +51,7 @@
     @Background private val backExecutor: Executor,
     private val listingController: ControlsListingController,
     private val controlsController: ControlsController,
-    private val broadcastDispatcher: BroadcastDispatcher,
+    private val userTracker: UserTracker,
     private val uiController: ControlsUiController
 ) : ComponentActivity() {
 
@@ -62,12 +62,12 @@
     }
     private var backShouldExit = false
     private lateinit var recyclerView: RecyclerView
-    private val currentUserTracker = object : CurrentUserTracker(broadcastDispatcher) {
+    private val userTrackerCallback: UserTracker.Callback = object : UserTracker.Callback {
         private val startingUser = listingController.currentUserId
 
-        override fun onUserSwitched(newUserId: Int) {
-            if (newUserId != startingUser) {
-                stopTracking()
+        override fun onUserChanged(newUser: Int, userContext: Context) {
+            if (newUser != startingUser) {
+                userTracker.removeCallback(this)
                 finish()
             }
         }
@@ -129,7 +129,7 @@
 
     override fun onStart() {
         super.onStart()
-        currentUserTracker.startTracking()
+        userTracker.addCallback(userTrackerCallback, executor)
 
         recyclerView.alpha = 0.0f
         recyclerView.adapter = AppAdapter(
@@ -161,7 +161,7 @@
 
     override fun onStop() {
         super.onStop()
-        currentUserTracker.stopTracking()
+        userTracker.removeCallback(userTrackerCallback)
 
         if (DEBUG) {
             Log.d(TAG, "Unregistered onBackInvokedCallback")
@@ -190,7 +190,7 @@
     }
 
     override fun onDestroy() {
-        currentUserTracker.stopTracking()
+        userTracker.removeCallback(userTrackerCallback)
         super.onDestroy()
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsRequestDialog.kt b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsRequestDialog.kt
index b376455..86bde5c 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsRequestDialog.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsRequestDialog.kt
@@ -19,6 +19,7 @@
 import android.app.AlertDialog
 import android.app.Dialog
 import android.content.ComponentName
+import android.content.Context
 import android.content.DialogInterface
 import android.content.Intent
 import android.os.Bundle
@@ -32,18 +33,20 @@
 import android.widget.TextView
 import androidx.activity.ComponentActivity
 import com.android.systemui.R
-import com.android.systemui.broadcast.BroadcastDispatcher
 import com.android.systemui.controls.ControlsServiceInfo
 import com.android.systemui.controls.controller.ControlInfo
 import com.android.systemui.controls.controller.ControlsController
 import com.android.systemui.controls.ui.RenderInfo
-import com.android.systemui.settings.CurrentUserTracker
+import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.settings.UserTracker
 import com.android.systemui.statusbar.phone.SystemUIDialog
+import java.util.concurrent.Executor
 import javax.inject.Inject
 
 open class ControlsRequestDialog @Inject constructor(
+    @Main private val mainExecutor: Executor,
     private val controller: ControlsController,
-    private val broadcastDispatcher: BroadcastDispatcher,
+    private val userTracker: UserTracker,
     private val controlsListingController: ControlsListingController
 ) : ComponentActivity(), DialogInterface.OnClickListener, DialogInterface.OnCancelListener {
 
@@ -58,12 +61,12 @@
         override fun onServicesUpdated(serviceInfos: List<ControlsServiceInfo>) {}
     }
 
-    private val currentUserTracker = object : CurrentUserTracker(broadcastDispatcher) {
+    private val userTrackerCallback: UserTracker.Callback = object : UserTracker.Callback {
         private val startingUser = controller.currentUserId
 
-        override fun onUserSwitched(newUserId: Int) {
-            if (newUserId != startingUser) {
-                stopTracking()
+        override fun onUserChanged(newUser: Int, userContext: Context) {
+            if (newUser != startingUser) {
+                userTracker.removeCallback(this)
                 finish()
             }
         }
@@ -72,7 +75,7 @@
     override fun onCreate(savedInstanceState: Bundle?) {
         super.onCreate(savedInstanceState)
 
-        currentUserTracker.startTracking()
+        userTracker.addCallback(userTrackerCallback, mainExecutor)
         controlsListingController.addCallback(callback)
 
         val requestUser = intent.getIntExtra(Intent.EXTRA_USER_ID, UserHandle.USER_NULL)
@@ -118,7 +121,7 @@
 
     override fun onDestroy() {
         dialog?.dismiss()
-        currentUserTracker.stopTracking()
+        userTracker.removeCallback(userTrackerCallback)
         controlsListingController.removeCallback(callback)
         super.onDestroy()
     }
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSysUIComponent.java b/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSysUIComponent.java
index d3555ee..b30e0c2 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSysUIComponent.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSysUIComponent.java
@@ -17,6 +17,7 @@
 package com.android.systemui.dagger;
 
 import com.android.systemui.keyguard.KeyguardQuickAffordanceProvider;
+import com.android.systemui.statusbar.NotificationInsetsModule;
 import com.android.systemui.statusbar.QsFrameTranslateModule;
 
 import dagger.Subcomponent;
@@ -28,6 +29,7 @@
 @Subcomponent(modules = {
         DefaultComponentBinder.class,
         DependencyProvider.class,
+        NotificationInsetsModule.class,
         QsFrameTranslateModule.class,
         SystemUIBinder.class,
         SystemUIModule.class,
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java b/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java
index a14b0ee..6dc4f5c 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java
@@ -28,6 +28,7 @@
 import com.android.systemui.media.muteawait.MediaMuteAwaitConnectionCli;
 import com.android.systemui.media.nearby.NearbyMediaDevicesManager;
 import com.android.systemui.people.PeopleProvider;
+import com.android.systemui.statusbar.NotificationInsetsModule;
 import com.android.systemui.statusbar.QsFrameTranslateModule;
 import com.android.systemui.statusbar.policy.ConfigurationController;
 import com.android.systemui.unfold.FoldStateLogger;
@@ -65,6 +66,7 @@
 @Subcomponent(modules = {
         DefaultComponentBinder.class,
         DependencyProvider.class,
+        NotificationInsetsModule.class,
         QsFrameTranslateModule.class,
         SystemUIBinder.class,
         SystemUIModule.class,
diff --git a/packages/SystemUI/src/com/android/systemui/demomode/DemoModeController.kt b/packages/SystemUI/src/com/android/systemui/demomode/DemoModeController.kt
index d537d4b..000bbe6 100644
--- a/packages/SystemUI/src/com/android/systemui/demomode/DemoModeController.kt
+++ b/packages/SystemUI/src/com/android/systemui/demomode/DemoModeController.kt
@@ -54,6 +54,9 @@
     private val receiverMap: Map<String, MutableList<DemoMode>>
 
     init {
+        // Don't persist demo mode across restarts.
+        requestFinishDemoMode()
+
         val m = mutableMapOf<String, MutableList<DemoMode>>()
         DemoMode.COMMANDS.map { command ->
             m.put(command, mutableListOf())
@@ -74,7 +77,6 @@
         // content changes to know if the setting turned on or off
         tracker.startTracking()
 
-        // TODO: We should probably exit demo mode if we booted up with it on
         isInDemoMode = tracker.isInDemoMode
 
         val demoFilter = IntentFilter()
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeLog.java b/packages/SystemUI/src/com/android/systemui/doze/DozeLog.java
index b69afeb..0c14ed5 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeLog.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeLog.java
@@ -133,9 +133,9 @@
     /**
      * Appends fling event to the logs
      */
-    public void traceFling(boolean expand, boolean aboveThreshold, boolean thresholdNeeded,
+    public void traceFling(boolean expand, boolean aboveThreshold,
             boolean screenOnFromTouch) {
-        mLogger.logFling(expand, aboveThreshold, thresholdNeeded, screenOnFromTouch);
+        mLogger.logFling(expand, aboveThreshold, screenOnFromTouch);
     }
 
     /**
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeLogger.kt b/packages/SystemUI/src/com/android/systemui/doze/DozeLogger.kt
index 18c8e01..b5dbe21 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeLogger.kt
@@ -96,13 +96,11 @@
     fun logFling(
         expand: Boolean,
         aboveThreshold: Boolean,
-        thresholdNeeded: Boolean,
         screenOnFromTouch: Boolean
     ) {
         buffer.log(TAG, DEBUG, {
             bool1 = expand
             bool2 = aboveThreshold
-            bool3 = thresholdNeeded
             bool4 = screenOnFromTouch
         }, {
             "Fling expand=$bool1 aboveThreshold=$bool2 thresholdNeeded=$bool3 " +
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeSensors.java b/packages/SystemUI/src/com/android/systemui/doze/DozeSensors.java
index d0258d3..f64d918 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeSensors.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeSensors.java
@@ -24,7 +24,6 @@
 import static com.android.systemui.plugins.SensorManagerPlugin.Sensor.TYPE_WAKE_LOCK_SCREEN;
 
 import android.annotation.AnyThread;
-import android.app.ActivityManager;
 import android.database.ContentObserver;
 import android.hardware.Sensor;
 import android.hardware.SensorManager;
@@ -50,6 +49,7 @@
 import com.android.keyguard.KeyguardUpdateMonitor;
 import com.android.systemui.biometrics.AuthController;
 import com.android.systemui.plugins.SensorManagerPlugin;
+import com.android.systemui.settings.UserTracker;
 import com.android.systemui.statusbar.phone.DozeParameters;
 import com.android.systemui.statusbar.policy.DevicePostureController;
 import com.android.systemui.util.sensors.AsyncSensorManager;
@@ -99,6 +99,7 @@
     private final SecureSettings mSecureSettings;
     private final DevicePostureController mDevicePostureController;
     private final AuthController mAuthController;
+    private final UserTracker mUserTracker;
     private final boolean mScreenOffUdfpsEnabled;
 
     // Sensors
@@ -152,7 +153,8 @@
             ProximitySensor proximitySensor,
             SecureSettings secureSettings,
             AuthController authController,
-            DevicePostureController devicePostureController
+            DevicePostureController devicePostureController,
+            UserTracker userTracker
     ) {
         mSensorManager = sensorManager;
         mConfig = config;
@@ -170,6 +172,7 @@
         mDevicePostureController = devicePostureController;
         mDevicePosture = mDevicePostureController.getDevicePosture();
         mAuthController = authController;
+        mUserTracker = userTracker;
 
         mUdfpsEnrolled =
                 mAuthController.isUdfpsEnrolled(KeyguardUpdateMonitor.getCurrentUser());
@@ -441,7 +444,7 @@
     private final ContentObserver mSettingsObserver = new ContentObserver(mHandler) {
         @Override
         public void onChange(boolean selfChange, Collection<Uri> uris, int flags, int userId) {
-            if (userId != ActivityManager.getCurrentUser()) {
+            if (userId != mUserTracker.getUserId()) {
                 return;
             }
             for (TriggerSensor s : mTriggerSensors) {
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java b/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java
index 32cb1c0..0b69b80 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java
@@ -45,6 +45,7 @@
 import com.android.systemui.doze.DozeMachine.State;
 import com.android.systemui.doze.dagger.DozeScope;
 import com.android.systemui.log.SessionTracker;
+import com.android.systemui.settings.UserTracker;
 import com.android.systemui.statusbar.phone.DozeParameters;
 import com.android.systemui.statusbar.policy.DevicePostureController;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
@@ -188,7 +189,8 @@
             UiEventLogger uiEventLogger,
             SessionTracker sessionTracker,
             KeyguardStateController keyguardStateController,
-            DevicePostureController devicePostureController) {
+            DevicePostureController devicePostureController,
+            UserTracker userTracker) {
         mContext = context;
         mDozeHost = dozeHost;
         mConfig = config;
@@ -200,7 +202,7 @@
 
         mDozeSensors = new DozeSensors(mSensorManager, dozeParameters,
                 config, wakeLock, this::onSensor, this::onProximityFar, dozeLog, proximitySensor,
-                secureSettings, authController, devicePostureController);
+                secureSettings, authController, devicePostureController, userTracker);
         mDockManager = dockManager;
         mProxCheck = proxCheck;
         mDozeLog = dozeLog;
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayAnimationsController.kt b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayAnimationsController.kt
index d8dd6a2..0087c84 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayAnimationsController.kt
+++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayAnimationsController.kt
@@ -17,17 +17,19 @@
 package com.android.systemui.dreams
 
 import android.animation.Animator
-import android.animation.AnimatorListenerAdapter
 import android.animation.AnimatorSet
 import android.animation.ValueAnimator
 import android.view.View
+import android.view.animation.Interpolator
+import androidx.annotation.FloatRange
 import androidx.core.animation.doOnEnd
 import com.android.systemui.animation.Interpolators
 import com.android.systemui.dreams.complication.ComplicationHostViewController
 import com.android.systemui.dreams.complication.ComplicationLayoutParams
+import com.android.systemui.dreams.complication.ComplicationLayoutParams.Position
 import com.android.systemui.dreams.dagger.DreamOverlayModule
 import com.android.systemui.statusbar.BlurUtils
-import java.util.function.Consumer
+import com.android.systemui.statusbar.CrossFadeHelper
 import javax.inject.Inject
 import javax.inject.Named
 
@@ -40,108 +42,239 @@
     private val mStatusBarViewController: DreamOverlayStatusBarViewController,
     private val mOverlayStateController: DreamOverlayStateController,
     @Named(DreamOverlayModule.DREAM_IN_BLUR_ANIMATION_DURATION)
-    private val mDreamInBlurAnimDuration: Int,
-    @Named(DreamOverlayModule.DREAM_IN_BLUR_ANIMATION_DELAY) private val mDreamInBlurAnimDelay: Int,
+    private val mDreamInBlurAnimDurationMs: Long,
+    @Named(DreamOverlayModule.DREAM_IN_BLUR_ANIMATION_DELAY)
+    private val mDreamInBlurAnimDelayMs: Long,
     @Named(DreamOverlayModule.DREAM_IN_COMPLICATIONS_ANIMATION_DURATION)
-    private val mDreamInComplicationsAnimDuration: Int,
+    private val mDreamInComplicationsAnimDurationMs: Long,
     @Named(DreamOverlayModule.DREAM_IN_TOP_COMPLICATIONS_ANIMATION_DELAY)
-    private val mDreamInTopComplicationsAnimDelay: Int,
+    private val mDreamInTopComplicationsAnimDelayMs: Long,
     @Named(DreamOverlayModule.DREAM_IN_BOTTOM_COMPLICATIONS_ANIMATION_DELAY)
-    private val mDreamInBottomComplicationsAnimDelay: Int
+    private val mDreamInBottomComplicationsAnimDelayMs: Long,
+    @Named(DreamOverlayModule.DREAM_OUT_TRANSLATION_Y_DISTANCE)
+    private val mDreamOutTranslationYDistance: Int,
+    @Named(DreamOverlayModule.DREAM_OUT_TRANSLATION_Y_DURATION)
+    private val mDreamOutTranslationYDurationMs: Long,
+    @Named(DreamOverlayModule.DREAM_OUT_TRANSLATION_Y_DELAY_BOTTOM)
+    private val mDreamOutTranslationYDelayBottomMs: Long,
+    @Named(DreamOverlayModule.DREAM_OUT_TRANSLATION_Y_DELAY_TOP)
+    private val mDreamOutTranslationYDelayTopMs: Long,
+    @Named(DreamOverlayModule.DREAM_OUT_ALPHA_DURATION) private val mDreamOutAlphaDurationMs: Long,
+    @Named(DreamOverlayModule.DREAM_OUT_ALPHA_DELAY_BOTTOM)
+    private val mDreamOutAlphaDelayBottomMs: Long,
+    @Named(DreamOverlayModule.DREAM_OUT_ALPHA_DELAY_TOP) private val mDreamOutAlphaDelayTopMs: Long,
+    @Named(DreamOverlayModule.DREAM_OUT_BLUR_DURATION) private val mDreamOutBlurDurationMs: Long
 ) {
 
-    var mEntryAnimations: AnimatorSet? = null
+    private var mAnimator: Animator? = null
+
+    /**
+     * Store the current alphas at the various positions. This is so that we may resume an animation
+     * at the current alpha.
+     */
+    private var mCurrentAlphaAtPosition = mutableMapOf<Int, Float>()
+
+    @FloatRange(from = 0.0, to = 1.0) private var mBlurProgress: Float = 0f
 
     /** Starts the dream content and dream overlay entry animations. */
-    fun startEntryAnimations(view: View) {
-        cancelRunningEntryAnimations()
+    @JvmOverloads
+    fun startEntryAnimations(view: View, animatorBuilder: () -> AnimatorSet = { AnimatorSet() }) {
+        cancelAnimations()
 
-        mEntryAnimations = AnimatorSet()
-        mEntryAnimations?.apply {
-            playTogether(
-                buildDreamInBlurAnimator(view),
-                buildDreamInTopComplicationsAnimator(),
-                buildDreamInBottomComplicationsAnimator()
-            )
-            doOnEnd { mOverlayStateController.setEntryAnimationsFinished(true) }
-            start()
-        }
+        mAnimator =
+            animatorBuilder().apply {
+                playTogether(
+                    blurAnimator(
+                        view = view,
+                        from = 1f,
+                        to = 0f,
+                        durationMs = mDreamInBlurAnimDurationMs,
+                        delayMs = mDreamInBlurAnimDelayMs
+                    ),
+                    alphaAnimator(
+                        from = 0f,
+                        to = 1f,
+                        durationMs = mDreamInComplicationsAnimDurationMs,
+                        delayMs = mDreamInTopComplicationsAnimDelayMs,
+                        position = ComplicationLayoutParams.POSITION_TOP
+                    ),
+                    alphaAnimator(
+                        from = 0f,
+                        to = 1f,
+                        durationMs = mDreamInComplicationsAnimDurationMs,
+                        delayMs = mDreamInBottomComplicationsAnimDelayMs,
+                        position = ComplicationLayoutParams.POSITION_BOTTOM
+                    )
+                )
+                doOnEnd {
+                    mAnimator = null
+                    mOverlayStateController.setEntryAnimationsFinished(true)
+                }
+                start()
+            }
+    }
+
+    /** Starts the dream content and dream overlay exit animations. */
+    @JvmOverloads
+    fun startExitAnimations(
+        view: View,
+        doneCallback: () -> Unit,
+        animatorBuilder: () -> AnimatorSet = { AnimatorSet() }
+    ) {
+        cancelAnimations()
+
+        mAnimator =
+            animatorBuilder().apply {
+                playTogether(
+                    blurAnimator(
+                        view = view,
+                        // Start the blurring wherever the entry animation ended, in
+                        // case it was cancelled early.
+                        from = mBlurProgress,
+                        to = 1f,
+                        durationMs = mDreamOutBlurDurationMs
+                    ),
+                    translationYAnimator(
+                        from = 0f,
+                        to = mDreamOutTranslationYDistance.toFloat(),
+                        durationMs = mDreamOutTranslationYDurationMs,
+                        delayMs = mDreamOutTranslationYDelayBottomMs,
+                        position = ComplicationLayoutParams.POSITION_BOTTOM,
+                        animInterpolator = Interpolators.EMPHASIZED_ACCELERATE
+                    ),
+                    translationYAnimator(
+                        from = 0f,
+                        to = mDreamOutTranslationYDistance.toFloat(),
+                        durationMs = mDreamOutTranslationYDurationMs,
+                        delayMs = mDreamOutTranslationYDelayTopMs,
+                        position = ComplicationLayoutParams.POSITION_TOP,
+                        animInterpolator = Interpolators.EMPHASIZED_ACCELERATE
+                    ),
+                    alphaAnimator(
+                        from =
+                            mCurrentAlphaAtPosition.getOrDefault(
+                                key = ComplicationLayoutParams.POSITION_BOTTOM,
+                                defaultValue = 1f
+                            ),
+                        to = 0f,
+                        durationMs = mDreamOutAlphaDurationMs,
+                        delayMs = mDreamOutAlphaDelayBottomMs,
+                        position = ComplicationLayoutParams.POSITION_BOTTOM
+                    ),
+                    alphaAnimator(
+                        from =
+                            mCurrentAlphaAtPosition.getOrDefault(
+                                key = ComplicationLayoutParams.POSITION_TOP,
+                                defaultValue = 1f
+                            ),
+                        to = 0f,
+                        durationMs = mDreamOutAlphaDurationMs,
+                        delayMs = mDreamOutAlphaDelayTopMs,
+                        position = ComplicationLayoutParams.POSITION_TOP
+                    )
+                )
+                doOnEnd {
+                    mAnimator = null
+                    mOverlayStateController.setExitAnimationsRunning(false)
+                    doneCallback()
+                }
+                start()
+            }
+        mOverlayStateController.setExitAnimationsRunning(true)
     }
 
     /** Cancels the dream content and dream overlay animations, if they're currently running. */
-    fun cancelRunningEntryAnimations() {
-        if (mEntryAnimations?.isRunning == true) {
-            mEntryAnimations?.cancel()
-        }
-        mEntryAnimations = null
+    fun cancelAnimations() {
+        mAnimator =
+            mAnimator?.let {
+                it.cancel()
+                null
+            }
     }
 
-    private fun buildDreamInBlurAnimator(view: View): Animator {
-        return ValueAnimator.ofFloat(1f, 0f).apply {
-            duration = mDreamInBlurAnimDuration.toLong()
-            startDelay = mDreamInBlurAnimDelay.toLong()
+    private fun blurAnimator(
+        view: View,
+        from: Float,
+        to: Float,
+        durationMs: Long,
+        delayMs: Long = 0
+    ): Animator {
+        return ValueAnimator.ofFloat(from, to).apply {
+            duration = durationMs
+            startDelay = delayMs
             interpolator = Interpolators.LINEAR
             addUpdateListener { animator: ValueAnimator ->
+                mBlurProgress = animator.animatedValue as Float
                 mBlurUtils.applyBlur(
-                    view.viewRootImpl,
-                    mBlurUtils.blurRadiusOfRatio(animator.animatedValue as Float).toInt(),
-                    false /*opaque*/
+                    viewRootImpl = view.viewRootImpl,
+                    radius = mBlurUtils.blurRadiusOfRatio(mBlurProgress).toInt(),
+                    opaque = false
                 )
             }
         }
     }
 
-    private fun buildDreamInTopComplicationsAnimator(): Animator {
-        return ValueAnimator.ofFloat(0f, 1f).apply {
-            duration = mDreamInComplicationsAnimDuration.toLong()
-            startDelay = mDreamInTopComplicationsAnimDelay.toLong()
+    private fun alphaAnimator(
+        from: Float,
+        to: Float,
+        durationMs: Long,
+        delayMs: Long,
+        @Position position: Int
+    ): Animator {
+        return ValueAnimator.ofFloat(from, to).apply {
+            duration = durationMs
+            startDelay = delayMs
             interpolator = Interpolators.LINEAR
             addUpdateListener { va: ValueAnimator ->
-                setTopElementsAlpha(va.animatedValue as Float)
+                setElementsAlphaAtPosition(
+                    alpha = va.animatedValue as Float,
+                    position = position,
+                    fadingOut = to < from
+                )
             }
         }
     }
 
-    private fun buildDreamInBottomComplicationsAnimator(): Animator {
-        return ValueAnimator.ofFloat(0f, 1f).apply {
-            duration = mDreamInComplicationsAnimDuration.toLong()
-            startDelay = mDreamInBottomComplicationsAnimDelay.toLong()
-            interpolator = Interpolators.LINEAR
+    private fun translationYAnimator(
+        from: Float,
+        to: Float,
+        durationMs: Long,
+        delayMs: Long,
+        @Position position: Int,
+        animInterpolator: Interpolator
+    ): Animator {
+        return ValueAnimator.ofFloat(from, to).apply {
+            duration = durationMs
+            startDelay = delayMs
+            interpolator = animInterpolator
             addUpdateListener { va: ValueAnimator ->
-                setBottomElementsAlpha(va.animatedValue as Float)
+                setElementsTranslationYAtPosition(va.animatedValue as Float, position)
             }
-            addListener(
-                object : AnimatorListenerAdapter() {
-                    override fun onAnimationStart(animation: Animator) {
-                        mComplicationHostViewController
-                            .getViewsAtPosition(ComplicationLayoutParams.POSITION_BOTTOM)
-                            .forEach(Consumer { v: View -> v.visibility = View.VISIBLE })
-                    }
-                }
-            )
         }
     }
 
-    /** Sets alpha of top complications and the status bar. */
-    private fun setTopElementsAlpha(alpha: Float) {
-        mComplicationHostViewController
-            .getViewsAtPosition(ComplicationLayoutParams.POSITION_TOP)
-            .forEach(Consumer { v: View -> setAlphaAndEnsureVisible(v, alpha) })
-        mStatusBarViewController.setAlpha(alpha)
-    }
-
-    /** Sets alpha of bottom complications. */
-    private fun setBottomElementsAlpha(alpha: Float) {
-        mComplicationHostViewController
-            .getViewsAtPosition(ComplicationLayoutParams.POSITION_BOTTOM)
-            .forEach(Consumer { v: View -> setAlphaAndEnsureVisible(v, alpha) })
-    }
-
-    private fun setAlphaAndEnsureVisible(view: View, alpha: Float) {
-        if (alpha > 0 && view.visibility != View.VISIBLE) {
-            view.visibility = View.VISIBLE
+    /** Sets alpha of complications at the specified position. */
+    private fun setElementsAlphaAtPosition(alpha: Float, position: Int, fadingOut: Boolean) {
+        mCurrentAlphaAtPosition[position] = alpha
+        mComplicationHostViewController.getViewsAtPosition(position).forEach { view ->
+            if (fadingOut) {
+                CrossFadeHelper.fadeOut(view, 1 - alpha, /* remap= */ false)
+            } else {
+                CrossFadeHelper.fadeIn(view, alpha, /* remap= */ false)
+            }
         }
+        if (position == ComplicationLayoutParams.POSITION_TOP) {
+            mStatusBarViewController.setFadeAmount(alpha, fadingOut)
+        }
+    }
 
-        view.alpha = alpha
+    /** Sets y translation of complications at the specified position. */
+    private fun setElementsTranslationYAtPosition(translationY: Float, position: Int) {
+        mComplicationHostViewController.getViewsAtPosition(position).forEach { v ->
+            v.translationY = translationY
+        }
+        if (position == ComplicationLayoutParams.POSITION_TOP) {
+            mStatusBarViewController.setTranslationY(translationY)
+        }
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayContainerViewController.java b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayContainerViewController.java
index 5c6d248..9d7ad30 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayContainerViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayContainerViewController.java
@@ -29,6 +29,8 @@
 import android.view.View;
 import android.view.ViewGroup;
 
+import androidx.annotation.NonNull;
+
 import com.android.systemui.R;
 import com.android.systemui.animation.Interpolators;
 import com.android.systemui.dagger.qualifiers.Main;
@@ -42,6 +44,7 @@
 import com.android.systemui.util.ViewController;
 
 import java.util.Arrays;
+import java.util.concurrent.Executor;
 
 import javax.inject.Inject;
 import javax.inject.Named;
@@ -194,7 +197,7 @@
         }
         mPrimaryBouncerCallbackInteractor.removeBouncerExpansionCallback(mBouncerExpansionCallback);
 
-        mDreamOverlayAnimationsController.cancelRunningEntryAnimations();
+        mDreamOverlayAnimationsController.cancelAnimations();
     }
 
     View getContainerView() {
@@ -251,4 +254,17 @@
                         : aboutToShowBouncerProgress(expansion + 0.03f));
         return MathUtils.lerp(-mDreamOverlayMaxTranslationY, 0, fraction);
     }
+
+    /**
+     * Handle the dream waking up and run any necessary animations.
+     *
+     * @param onAnimationEnd Callback to trigger once animations are finished.
+     * @param callbackExecutor Executor to execute the callback on.
+     */
+    public void wakeUp(@NonNull Runnable onAnimationEnd, @NonNull Executor callbackExecutor) {
+        mDreamOverlayAnimationsController.startExitAnimations(mView, () -> {
+            callbackExecutor.execute(onAnimationEnd);
+            return null;
+        });
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java
index 8542412..e76d5b3 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java
@@ -213,6 +213,15 @@
         mLifecycleRegistry.setCurrentState(state);
     }
 
+    @Override
+    public void onWakeUp(@NonNull Runnable onCompletedCallback) {
+        mExecutor.execute(() -> {
+            if (mDreamOverlayContainerViewController != null) {
+                mDreamOverlayContainerViewController.wakeUp(onCompletedCallback, mExecutor);
+            }
+        });
+    }
+
     /**
      * Inserts {@link Window} to host the dream overlay into the dream's parent window. Must be
      * called from the main executing thread. The window attributes closely mirror those that are
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStateController.java b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStateController.java
index e80d0be..5f942b6 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStateController.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStateController.java
@@ -52,6 +52,7 @@
     public static final int STATE_DREAM_OVERLAY_ACTIVE = 1 << 0;
     public static final int STATE_LOW_LIGHT_ACTIVE = 1 << 1;
     public static final int STATE_DREAM_ENTRY_ANIMATIONS_FINISHED = 1 << 2;
+    public static final int STATE_DREAM_EXIT_ANIMATIONS_RUNNING = 1 << 3;
 
     private static final int OP_CLEAR_STATE = 1;
     private static final int OP_SET_STATE = 2;
@@ -211,6 +212,14 @@
         return containsState(STATE_DREAM_ENTRY_ANIMATIONS_FINISHED);
     }
 
+    /**
+     * Returns whether the dream content and dream overlay exit animations are running.
+     * @return {@code true} if animations are running, {@code false} otherwise.
+     */
+    public boolean areExitAnimationsRunning() {
+        return containsState(STATE_DREAM_EXIT_ANIMATIONS_RUNNING);
+    }
+
     private boolean containsState(int state) {
         return (mState & state) != 0;
     }
@@ -257,6 +266,15 @@
     }
 
     /**
+     * Sets whether dream content and dream overlay exit animations are running.
+     * @param running {@code true} if exit animations are running, {@code false} otherwise.
+     */
+    public void setExitAnimationsRunning(boolean running) {
+        modifyState(running ? OP_SET_STATE : OP_CLEAR_STATE,
+                STATE_DREAM_EXIT_ANIMATIONS_RUNNING);
+    }
+
+    /**
      * Returns the available complication types.
      */
     @Complication.ComplicationType
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStatusBarViewController.java b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStatusBarViewController.java
index d17fbe3..f1bb156 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStatusBarViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStatusBarViewController.java
@@ -37,6 +37,7 @@
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.dreams.DreamOverlayStatusBarItemsProvider.StatusBarItem;
 import com.android.systemui.dreams.dagger.DreamOverlayComponent;
+import com.android.systemui.statusbar.CrossFadeHelper;
 import com.android.systemui.statusbar.policy.IndividualSensorPrivacyController;
 import com.android.systemui.statusbar.policy.NextAlarmController;
 import com.android.systemui.statusbar.policy.ZenModeController;
@@ -217,18 +218,29 @@
     }
 
     /**
-     * Sets alpha of the dream overlay status bar.
+     * Sets fade of the dream overlay status bar.
      *
      * No-op if the dream overlay status bar should not be shown.
      */
-    protected void setAlpha(float alpha) {
+    protected void setFadeAmount(float fadeAmount, boolean fadingOut) {
         updateVisibility();
 
         if (mView.getVisibility() != View.VISIBLE) {
             return;
         }
 
-        mView.setAlpha(alpha);
+        if (fadingOut) {
+            CrossFadeHelper.fadeOut(mView, 1 - fadeAmount, /* remap= */ false);
+        } else {
+            CrossFadeHelper.fadeIn(mView, fadeAmount, /* remap= */ false);
+        }
+    }
+
+    /**
+     * Sets the y translation of the dream overlay status bar.
+     */
+    public void setTranslationY(float translationY) {
+        mView.setTranslationY(translationY);
     }
 
     private boolean shouldShowStatusBar() {
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/complication/Complication.java b/packages/SystemUI/src/com/android/systemui/dreams/complication/Complication.java
index 41f5578..b07efdf 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/complication/Complication.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/complication/Complication.java
@@ -197,11 +197,11 @@
      */
     interface VisibilityController {
         /**
-         * Called to set the visibility of all shown and future complications.
+         * Called to set the visibility of all shown and future complications. Changes in visibility
+         * will always be animated.
          * @param visibility The desired future visibility.
-         * @param animate whether the change should be animated.
          */
-        void setVisibility(@View.Visibility int visibility, boolean animate);
+        void setVisibility(@View.Visibility int visibility);
     }
 
     /**
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationLayoutEngine.java b/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationLayoutEngine.java
index 440dcbc..48159ae 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationLayoutEngine.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationLayoutEngine.java
@@ -21,12 +21,9 @@
 import static com.android.systemui.dreams.complication.dagger.ComplicationHostViewModule.COMPLICATION_MARGIN_DEFAULT;
 import static com.android.systemui.dreams.complication.dagger.ComplicationHostViewModule.SCOPED_COMPLICATIONS_LAYOUT;
 
-import android.animation.Animator;
-import android.animation.AnimatorListenerAdapter;
 import android.util.Log;
 import android.view.View;
 import android.view.ViewGroup;
-import android.view.ViewPropertyAnimator;
 
 import androidx.constraintlayout.widget.ConstraintLayout;
 import androidx.constraintlayout.widget.Constraints;
@@ -34,6 +31,7 @@
 import com.android.systemui.R;
 import com.android.systemui.dreams.complication.ComplicationLayoutParams.Position;
 import com.android.systemui.dreams.dagger.DreamOverlayComponent;
+import com.android.systemui.statusbar.CrossFadeHelper;
 import com.android.systemui.touch.TouchInsetManager;
 
 import java.util.ArrayList;
@@ -481,7 +479,6 @@
     private final TouchInsetManager.TouchInsetSession mSession;
     private final int mFadeInDuration;
     private final int mFadeOutDuration;
-    private ViewPropertyAnimator mViewPropertyAnimator;
 
     /** */
     @Inject
@@ -498,26 +495,16 @@
     }
 
     @Override
-    public void setVisibility(int visibility, boolean animate) {
-        final boolean appearing = visibility == View.VISIBLE;
-
-        if (mViewPropertyAnimator != null) {
-            mViewPropertyAnimator.cancel();
+    public void setVisibility(int visibility) {
+        if (visibility == View.VISIBLE) {
+            CrossFadeHelper.fadeIn(mLayout, mFadeInDuration, /* delay= */ 0);
+        } else {
+            CrossFadeHelper.fadeOut(
+                    mLayout,
+                    mFadeOutDuration,
+                    /* delay= */ 0,
+                    /* endRunnable= */ null);
         }
-
-        if (appearing) {
-            mLayout.setVisibility(View.VISIBLE);
-        }
-
-        mViewPropertyAnimator = mLayout.animate()
-                .alpha(appearing ? 1f : 0f)
-                .setDuration(appearing ? mFadeInDuration : mFadeOutDuration)
-                .setListener(new AnimatorListenerAdapter() {
-                    @Override
-                    public void onAnimationEnd(Animator animation) {
-                        mLayout.setVisibility(visibility);
-                    }
-                });
     }
 
     /**
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationLayoutParams.java b/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationLayoutParams.java
index 2b32d34..4fae68d 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationLayoutParams.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationLayoutParams.java
@@ -38,7 +38,7 @@
             POSITION_START,
     })
 
-    @interface Position {}
+    public @interface Position {}
     /** Align view with the top of parent or bottom of preceding {@link Complication}. */
     public static final int POSITION_TOP = 1 << 0;
     /** Align view with the bottom of parent or top of preceding {@link Complication}. */
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/complication/dagger/ComplicationHostViewModule.java b/packages/SystemUI/src/com/android/systemui/dreams/complication/dagger/ComplicationHostViewModule.java
index c9fecc9..09cc7c5 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/complication/dagger/ComplicationHostViewModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/complication/dagger/ComplicationHostViewModule.java
@@ -41,6 +41,7 @@
     public static final String COMPLICATIONS_FADE_OUT_DURATION = "complications_fade_out_duration";
     public static final String COMPLICATIONS_FADE_IN_DURATION = "complications_fade_in_duration";
     public static final String COMPLICATIONS_RESTORE_TIMEOUT = "complication_restore_timeout";
+    public static final String COMPLICATIONS_FADE_OUT_DELAY = "complication_fade_out_delay";
 
     /**
      * Generates a {@link ConstraintLayout}, which can host
@@ -75,6 +76,16 @@
     }
 
     /**
+     * Provides the delay to wait for before fading out complications.
+     */
+    @Provides
+    @Named(COMPLICATIONS_FADE_OUT_DELAY)
+    @DreamOverlayComponent.DreamOverlayScope
+    static int providesComplicationsFadeOutDelay(@Main Resources resources) {
+        return resources.getInteger(R.integer.complicationFadeOutDelayMs);
+    }
+
+    /**
      * Provides the fade in duration for complications.
      */
     @Provides
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamOverlayModule.java b/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamOverlayModule.java
index cb012fa..ed0e1d9 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamOverlayModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamOverlayModule.java
@@ -55,6 +55,22 @@
             "dream_in_top_complications_anim_delay";
     public static final String DREAM_IN_BOTTOM_COMPLICATIONS_ANIMATION_DELAY =
             "dream_in_bottom_complications_anim_delay";
+    public static final String DREAM_OUT_TRANSLATION_Y_DISTANCE =
+            "dream_out_complications_translation_y";
+    public static final String DREAM_OUT_TRANSLATION_Y_DURATION =
+            "dream_out_complications_translation_y_duration";
+    public static final String DREAM_OUT_TRANSLATION_Y_DELAY_BOTTOM =
+            "dream_out_complications_translation_y_delay_bottom";
+    public static final String DREAM_OUT_TRANSLATION_Y_DELAY_TOP =
+            "dream_out_complications_translation_y_delay_top";
+    public static final String DREAM_OUT_ALPHA_DURATION =
+            "dream_out_complications_alpha_duration";
+    public static final String DREAM_OUT_ALPHA_DELAY_BOTTOM =
+            "dream_out_complications_alpha_delay_bottom";
+    public static final String DREAM_OUT_ALPHA_DELAY_TOP =
+            "dream_out_complications_alpha_delay_top";
+    public static final String DREAM_OUT_BLUR_DURATION =
+            "dream_out_blur_duration";
 
     /** */
     @Provides
@@ -127,8 +143,8 @@
      */
     @Provides
     @Named(DREAM_IN_BLUR_ANIMATION_DURATION)
-    static int providesDreamInBlurAnimationDuration(@Main Resources resources) {
-        return resources.getInteger(R.integer.config_dreamOverlayInBlurDurationMs);
+    static long providesDreamInBlurAnimationDuration(@Main Resources resources) {
+        return (long) resources.getInteger(R.integer.config_dreamOverlayInBlurDurationMs);
     }
 
     /**
@@ -136,8 +152,8 @@
      */
     @Provides
     @Named(DREAM_IN_BLUR_ANIMATION_DELAY)
-    static int providesDreamInBlurAnimationDelay(@Main Resources resources) {
-        return resources.getInteger(R.integer.config_dreamOverlayInBlurDelayMs);
+    static long providesDreamInBlurAnimationDelay(@Main Resources resources) {
+        return (long) resources.getInteger(R.integer.config_dreamOverlayInBlurDelayMs);
     }
 
     /**
@@ -145,8 +161,8 @@
      */
     @Provides
     @Named(DREAM_IN_COMPLICATIONS_ANIMATION_DURATION)
-    static int providesDreamInComplicationsAnimationDuration(@Main Resources resources) {
-        return resources.getInteger(R.integer.config_dreamOverlayInComplicationsDurationMs);
+    static long providesDreamInComplicationsAnimationDuration(@Main Resources resources) {
+        return (long) resources.getInteger(R.integer.config_dreamOverlayInComplicationsDurationMs);
     }
 
     /**
@@ -154,8 +170,8 @@
      */
     @Provides
     @Named(DREAM_IN_TOP_COMPLICATIONS_ANIMATION_DELAY)
-    static int providesDreamInTopComplicationsAnimationDelay(@Main Resources resources) {
-        return resources.getInteger(R.integer.config_dreamOverlayInTopComplicationsDelayMs);
+    static long providesDreamInTopComplicationsAnimationDelay(@Main Resources resources) {
+        return (long) resources.getInteger(R.integer.config_dreamOverlayInTopComplicationsDelayMs);
     }
 
     /**
@@ -163,8 +179,69 @@
      */
     @Provides
     @Named(DREAM_IN_BOTTOM_COMPLICATIONS_ANIMATION_DELAY)
-    static int providesDreamInBottomComplicationsAnimationDelay(@Main Resources resources) {
-        return resources.getInteger(R.integer.config_dreamOverlayInBottomComplicationsDelayMs);
+    static long providesDreamInBottomComplicationsAnimationDelay(@Main Resources resources) {
+        return (long) resources.getInteger(
+                R.integer.config_dreamOverlayInBottomComplicationsDelayMs);
+    }
+
+    /**
+     * Provides the number of pixels to translate complications when waking up from dream.
+     */
+    @Provides
+    @Named(DREAM_OUT_TRANSLATION_Y_DISTANCE)
+    @DreamOverlayComponent.DreamOverlayScope
+    static int providesDreamOutComplicationsTranslationY(@Main Resources resources) {
+        return resources.getDimensionPixelSize(R.dimen.dream_overlay_exit_y_offset);
+    }
+
+    @Provides
+    @Named(DREAM_OUT_TRANSLATION_Y_DURATION)
+    @DreamOverlayComponent.DreamOverlayScope
+    static long providesDreamOutComplicationsTranslationYDuration(@Main Resources resources) {
+        return (long) resources.getInteger(R.integer.config_dreamOverlayOutTranslationYDurationMs);
+    }
+
+    @Provides
+    @Named(DREAM_OUT_TRANSLATION_Y_DELAY_BOTTOM)
+    @DreamOverlayComponent.DreamOverlayScope
+    static long providesDreamOutComplicationsTranslationYDelayBottom(@Main Resources resources) {
+        return (long) resources.getInteger(
+                R.integer.config_dreamOverlayOutTranslationYDelayBottomMs);
+    }
+
+    @Provides
+    @Named(DREAM_OUT_TRANSLATION_Y_DELAY_TOP)
+    @DreamOverlayComponent.DreamOverlayScope
+    static long providesDreamOutComplicationsTranslationYDelayTop(@Main Resources resources) {
+        return (long) resources.getInteger(R.integer.config_dreamOverlayOutTranslationYDelayTopMs);
+    }
+
+    @Provides
+    @Named(DREAM_OUT_ALPHA_DURATION)
+    @DreamOverlayComponent.DreamOverlayScope
+    static long providesDreamOutComplicationsAlphaDuration(@Main Resources resources) {
+        return (long) resources.getInteger(R.integer.config_dreamOverlayOutAlphaDurationMs);
+    }
+
+    @Provides
+    @Named(DREAM_OUT_ALPHA_DELAY_BOTTOM)
+    @DreamOverlayComponent.DreamOverlayScope
+    static long providesDreamOutComplicationsAlphaDelayBottom(@Main Resources resources) {
+        return (long) resources.getInteger(R.integer.config_dreamOverlayOutAlphaDelayBottomMs);
+    }
+
+    @Provides
+    @Named(DREAM_OUT_ALPHA_DELAY_TOP)
+    @DreamOverlayComponent.DreamOverlayScope
+    static long providesDreamOutComplicationsAlphaDelayTop(@Main Resources resources) {
+        return (long) resources.getInteger(R.integer.config_dreamOverlayOutAlphaDelayTopMs);
+    }
+
+    @Provides
+    @Named(DREAM_OUT_BLUR_DURATION)
+    @DreamOverlayComponent.DreamOverlayScope
+    static long providesDreamOutBlurDuration(@Main Resources resources) {
+        return (long) resources.getInteger(R.integer.config_dreamOverlayOutBlurDurationMs);
     }
 
     @Provides
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/touch/HideComplicationTouchHandler.java b/packages/SystemUI/src/com/android/systemui/dreams/touch/HideComplicationTouchHandler.java
index 3087cdf..e276e0c 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/touch/HideComplicationTouchHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/touch/HideComplicationTouchHandler.java
@@ -16,22 +16,26 @@
 
 package com.android.systemui.dreams.touch;
 
+import static com.android.systemui.dreams.complication.dagger.ComplicationHostViewModule.COMPLICATIONS_FADE_OUT_DELAY;
 import static com.android.systemui.dreams.complication.dagger.ComplicationHostViewModule.COMPLICATIONS_RESTORE_TIMEOUT;
 
-import android.os.Handler;
 import android.util.Log;
 import android.view.MotionEvent;
 import android.view.View;
 
+import androidx.annotation.Nullable;
+
 import com.android.systemui.dagger.qualifiers.Main;
+import com.android.systemui.dreams.DreamOverlayStateController;
 import com.android.systemui.dreams.complication.Complication;
 import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
 import com.android.systemui.touch.TouchInsetManager;
+import com.android.systemui.util.concurrency.DelayableExecutor;
 
 import com.google.common.util.concurrent.ListenableFuture;
 
+import java.util.ArrayDeque;
 import java.util.concurrent.ExecutionException;
-import java.util.concurrent.Executor;
 
 import javax.inject.Inject;
 import javax.inject.Named;
@@ -49,33 +53,58 @@
     private static final String TAG = "HideComplicationHandler";
     private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
 
-    private final Complication.VisibilityController mVisibilityController;
     private final int mRestoreTimeout;
+    private final int mFadeOutDelay;
     private final StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
-    private final Handler mHandler;
-    private final Executor mExecutor;
+    private final DelayableExecutor mExecutor;
+    private final DreamOverlayStateController mOverlayStateController;
     private final TouchInsetManager mTouchInsetManager;
+    private final Complication.VisibilityController mVisibilityController;
+    private boolean mHidden = false;
+    @Nullable
+    private Runnable mHiddenCallback;
+    private final ArrayDeque<Runnable> mCancelCallbacks = new ArrayDeque<>();
+
 
     private final Runnable mRestoreComplications = new Runnable() {
         @Override
         public void run() {
-            mVisibilityController.setVisibility(View.VISIBLE, true);
+            mVisibilityController.setVisibility(View.VISIBLE);
+            mHidden = false;
+        }
+    };
+
+    private final Runnable mHideComplications = new Runnable() {
+        @Override
+        public void run() {
+            if (mOverlayStateController.areExitAnimationsRunning()) {
+                // Avoid interfering with the exit animations.
+                return;
+            }
+            mVisibilityController.setVisibility(View.INVISIBLE);
+            mHidden = true;
+            if (mHiddenCallback != null) {
+                mHiddenCallback.run();
+                mHiddenCallback = null;
+            }
         }
     };
 
     @Inject
     HideComplicationTouchHandler(Complication.VisibilityController visibilityController,
             @Named(COMPLICATIONS_RESTORE_TIMEOUT) int restoreTimeout,
+            @Named(COMPLICATIONS_FADE_OUT_DELAY) int fadeOutDelay,
             TouchInsetManager touchInsetManager,
             StatusBarKeyguardViewManager statusBarKeyguardViewManager,
-            @Main Executor executor,
-            @Main Handler handler) {
+            @Main DelayableExecutor executor,
+            DreamOverlayStateController overlayStateController) {
         mVisibilityController = visibilityController;
         mRestoreTimeout = restoreTimeout;
+        mFadeOutDelay = fadeOutDelay;
         mStatusBarKeyguardViewManager = statusBarKeyguardViewManager;
-        mHandler = handler;
         mTouchInsetManager = touchInsetManager;
         mExecutor = executor;
+        mOverlayStateController = overlayStateController;
     }
 
     @Override
@@ -87,7 +116,8 @@
         final boolean bouncerShowing = mStatusBarKeyguardViewManager.isBouncerShowing();
 
         // If other sessions are interested in this touch, do not fade out elements.
-        if (session.getActiveSessionCount() > 1 || bouncerShowing) {
+        if (session.getActiveSessionCount() > 1 || bouncerShowing
+                || mOverlayStateController.areExitAnimationsRunning()) {
             if (DEBUG) {
                 Log.d(TAG, "not fading. Active session count: " + session.getActiveSessionCount()
                         + ". Bouncer showing: " + bouncerShowing);
@@ -115,8 +145,11 @@
                 touchCheck.addListener(() -> {
                     try {
                         if (!touchCheck.get()) {
-                            mHandler.removeCallbacks(mRestoreComplications);
-                            mVisibilityController.setVisibility(View.INVISIBLE, true);
+                            // Cancel all pending callbacks.
+                            while (!mCancelCallbacks.isEmpty()) mCancelCallbacks.pop().run();
+                            mCancelCallbacks.add(
+                                    mExecutor.executeDelayed(
+                                            mHideComplications, mFadeOutDelay));
                         } else {
                             // If a touch occurred inside the dream overlay touch insets, do not
                             // handle the touch.
@@ -130,7 +163,23 @@
                     || motionEvent.getAction() == MotionEvent.ACTION_UP) {
                 // End session and initiate delayed reappearance of the complications.
                 session.pop();
-                mHandler.postDelayed(mRestoreComplications, mRestoreTimeout);
+                runAfterHidden(() -> mCancelCallbacks.add(
+                        mExecutor.executeDelayed(mRestoreComplications,
+                                mRestoreTimeout)));
+            }
+        });
+    }
+
+    /**
+     * Triggers a runnable after complications have been hidden. Will override any previously set
+     * runnable currently waiting for hide to happen.
+     */
+    private void runAfterHidden(Runnable runnable) {
+        mExecutor.execute(() -> {
+            if (mHidden) {
+                runnable.run();
+            } else {
+                mHiddenCallback = runnable;
             }
         });
     }
diff --git a/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsDebug.java b/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsDebug.java
index b03ae59..81df4ed 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsDebug.java
+++ b/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsDebug.java
@@ -337,7 +337,6 @@
             Log.i(TAG, "Android Restart Suppressed");
             return;
         }
-        Log.i(TAG, "Restarting Android");
         mRestarter.restart();
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsDebugRestarter.kt b/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsDebugRestarter.kt
new file mode 100644
index 0000000..3d9f627
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsDebugRestarter.kt
@@ -0,0 +1,51 @@
+/*
+ * 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.flags
+
+import android.util.Log
+import com.android.systemui.keyguard.WakefulnessLifecycle
+import javax.inject.Inject
+
+/** Restarts SystemUI when the screen is locked. */
+class FeatureFlagsDebugRestarter
+@Inject
+constructor(
+    private val wakefulnessLifecycle: WakefulnessLifecycle,
+    private val systemExitRestarter: SystemExitRestarter,
+) : Restarter {
+
+    val observer =
+        object : WakefulnessLifecycle.Observer {
+            override fun onFinishedGoingToSleep() {
+                Log.d(FeatureFlagsDebug.TAG, "Restarting due to systemui flag change")
+                restartNow()
+            }
+        }
+
+    override fun restart() {
+        Log.d(FeatureFlagsDebug.TAG, "Restart requested. Restarting on next screen off.")
+        if (wakefulnessLifecycle.wakefulness == WakefulnessLifecycle.WAKEFULNESS_ASLEEP) {
+            restartNow()
+        } else {
+            wakefulnessLifecycle.addObserver(observer)
+        }
+    }
+
+    private fun restartNow() {
+        systemExitRestarter.restart()
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsReleaseRestarter.kt b/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsReleaseRestarter.kt
new file mode 100644
index 0000000..a3f0f66
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsReleaseRestarter.kt
@@ -0,0 +1,82 @@
+/*
+ * 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.flags
+
+import android.util.Log
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.keyguard.WakefulnessLifecycle
+import com.android.systemui.keyguard.WakefulnessLifecycle.WAKEFULNESS_ASLEEP
+import com.android.systemui.statusbar.policy.BatteryController
+import com.android.systemui.util.concurrency.DelayableExecutor
+import java.util.concurrent.TimeUnit
+import javax.inject.Inject
+
+/** Restarts SystemUI when the device appears idle. */
+class FeatureFlagsReleaseRestarter
+@Inject
+constructor(
+    private val wakefulnessLifecycle: WakefulnessLifecycle,
+    private val batteryController: BatteryController,
+    @Background private val bgExecutor: DelayableExecutor,
+    private val systemExitRestarter: SystemExitRestarter
+) : Restarter {
+    var shouldRestart = false
+    var pendingRestart: Runnable? = null
+
+    val observer =
+        object : WakefulnessLifecycle.Observer {
+            override fun onFinishedGoingToSleep() {
+                maybeScheduleRestart()
+            }
+        }
+
+    val batteryCallback =
+        object : BatteryController.BatteryStateChangeCallback {
+            override fun onBatteryLevelChanged(level: Int, pluggedIn: Boolean, charging: Boolean) {
+                maybeScheduleRestart()
+            }
+        }
+
+    override fun restart() {
+        Log.d(FeatureFlagsDebug.TAG, "Restart requested. Restarting when plugged in and idle.")
+        if (!shouldRestart) {
+            // Don't bother scheduling twice.
+            shouldRestart = true
+            wakefulnessLifecycle.addObserver(observer)
+            batteryController.addCallback(batteryCallback)
+            maybeScheduleRestart()
+        }
+    }
+
+    private fun maybeScheduleRestart() {
+        if (
+            wakefulnessLifecycle.wakefulness == WAKEFULNESS_ASLEEP && batteryController.isPluggedIn
+        ) {
+            if (pendingRestart == null) {
+                pendingRestart = bgExecutor.executeDelayed(this::restartNow, 30L, TimeUnit.SECONDS)
+            }
+        } else if (pendingRestart != null) {
+            pendingRestart?.run()
+            pendingRestart = null
+        }
+    }
+
+    private fun restartNow() {
+        Log.d(FeatureFlagsRelease.TAG, "Restarting due to systemui flag change")
+        systemExitRestarter.restart()
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
index 8b71580..26d2bc3 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
@@ -79,13 +79,19 @@
     val NOTIFICATION_GROUP_CORNER =
         unreleasedFlag(116, "notification_group_corner", teamfood = true)
 
+    // TODO(b/259217907)
+    @JvmField
+    val NOTIFICATION_GROUP_DISMISSAL_ANIMATION =
+        unreleasedFlag(259217907, "notification_group_dismissal_animation", teamfood = true)
+
     // TODO(b/257506350): Tracking Bug
     val FSI_CHROME = unreleasedFlag(117, "fsi_chrome")
 
     // TODO(b/257315550): Tracking Bug
     val NO_HUN_FOR_OLD_WHEN = unreleasedFlag(118, "no_hun_for_old_when")
 
-    // next id: 119
+    val FILTER_UNSEEN_NOTIFS_ON_KEYGUARD =
+        unreleasedFlag(254647461, "filter_unseen_notifs_on_keyguard", teamfood = true)
 
     // 200 - keyguard/lockscreen
     // ** Flag retired **
@@ -115,28 +121,6 @@
     @JvmField val MODERN_BOUNCER = releasedFlag(208, "modern_bouncer")
 
     /**
-     * Whether the user interactor and repository should use `UserSwitcherController`.
-     *
-     * If this is `false`, the interactor and repo skip the controller and directly access the
-     * framework APIs.
-     */
-    // TODO(b/254513286): Tracking Bug
-    val USER_INTERACTOR_AND_REPO_USE_CONTROLLER =
-        unreleasedFlag(210, "user_interactor_and_repo_use_controller")
-
-    /**
-     * Whether `UserSwitcherController` should use the user interactor.
-     *
-     * When this is `true`, the controller does not directly access framework APIs. Instead, it goes
-     * through the interactor.
-     *
-     * Note: do not set this to true if [.USER_INTERACTOR_AND_REPO_USE_CONTROLLER] is `true` as it
-     * would created a cycle between controller -> interactor -> controller.
-     */
-    // TODO(b/254513102): Tracking Bug
-    val USER_CONTROLLER_USES_INTERACTOR = releasedFlag(211, "user_controller_uses_interactor")
-
-    /**
      * Whether the clock on a wide lock screen should use the new "stepping" animation for moving
      * the digits when the clock moves.
      */
@@ -158,9 +142,9 @@
     /**
      * Whether to enable the code powering customizable lock screen quick affordances.
      *
-     * Note that this flag does not enable individual implementations of quick affordances like the
-     * new camera quick affordance. Look for individual flags for those.
+     * This flag enables any new prebuilt quick affordances as well.
      */
+    // TODO(b/255618149): Tracking Bug
     @JvmField
     val CUSTOMIZABLE_LOCK_SCREEN_QUICK_AFFORDANCES =
         unreleasedFlag(216, "customizable_lock_screen_quick_affordances", teamfood = false)
@@ -295,6 +279,8 @@
     // TODO(b/254513168): Tracking Bug
     @JvmField val UMO_SURFACE_RIPPLE = unreleasedFlag(907, "umo_surface_ripple")
 
+    @JvmField val MEDIA_FALSING_PENALTY = unreleasedFlag(908, "media_falsing_media")
+
     // 1000 - dock
     val SIMULATE_DOCK_THROUGH_CHARGING = releasedFlag(1000, "simulate_dock_through_charging")
 
@@ -305,7 +291,7 @@
     @Keep
     @JvmField
     val WM_ENABLE_SHELL_TRANSITIONS =
-        sysPropBooleanFlag(1100, "persist.wm.debug.shell_transit", default = true)
+        sysPropBooleanFlag(1100, "persist.wm.debug.shell_transit", default = false)
 
     // TODO(b/254513207): Tracking Bug
     @Keep
@@ -401,9 +387,7 @@
         unreleasedFlag(1600, "a11y_floating_menu_fling_spring_animations")
 
     // 1700 - clipboard
-    @JvmField
-    val CLIPBOARD_OVERLAY_REFACTOR =
-        unreleasedFlag(1700, "clipboard_overlay_refactor", teamfood = true)
+    @JvmField val CLIPBOARD_OVERLAY_REFACTOR = releasedFlag(1700, "clipboard_overlay_refactor")
     @JvmField val CLIPBOARD_REMOTE_BEHAVIOR = unreleasedFlag(1701, "clipboard_remote_behavior")
 
     // 1800 - shade container
@@ -419,4 +403,10 @@
 
     // 2100 - Falsing Manager
     @JvmField val FALSING_FOR_LONG_TAPS = releasedFlag(2100, "falsing_for_long_taps")
+
+    // 2200 - udfps
+    // TODO(b/259264861): Tracking Bug
+    @JvmField val UDFPS_NEW_TOUCH_DETECTION = unreleasedFlag(2200, "udfps_new_touch_detection")
+    @JvmField val UDFPS_ELLIPSE_DEBUG_UI = unreleasedFlag(2201, "udfps_ellipse_debug")
+    @JvmField val UDFPS_ELLIPSE_DETECTION = unreleasedFlag(2202, "udfps_ellipse_detection")
 }
diff --git a/packages/SystemUI/src/com/android/systemui/flags/FlagsCommonModule.kt b/packages/SystemUI/src/com/android/systemui/flags/FlagsCommonModule.kt
index 18d7bcf..8442230 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/FlagsCommonModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/FlagsCommonModule.kt
@@ -15,7 +15,6 @@
  */
 package com.android.systemui.flags
 
-import com.android.internal.statusbar.IStatusBarService
 import dagger.Module
 import dagger.Provides
 import javax.inject.Named
@@ -32,15 +31,5 @@
         fun providesAllFlags(): Map<Int, Flag<*>> {
             return FlagsFactory.knownFlags.map { it.value.id to it.value }.toMap()
         }
-
-        @JvmStatic
-        @Provides
-        fun providesRestarter(barService: IStatusBarService): Restarter {
-            return object : Restarter {
-                override fun restart() {
-                    barService.restart()
-                }
-            }
-        }
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/flags/SystemExitRestarter.kt b/packages/SystemUI/src/com/android/systemui/flags/SystemExitRestarter.kt
new file mode 100644
index 0000000..f1b1be4
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/flags/SystemExitRestarter.kt
@@ -0,0 +1,25 @@
+/*
+ * 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.flags
+
+import javax.inject.Inject
+
+class SystemExitRestarter @Inject constructor() : Restarter {
+    override fun restart() {
+        System.exit(0)
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/fragments/ExtensionFragmentListener.java b/packages/SystemUI/src/com/android/systemui/fragments/ExtensionFragmentListener.java
index 18fb423..d9bcb50 100644
--- a/packages/SystemUI/src/com/android/systemui/fragments/ExtensionFragmentListener.java
+++ b/packages/SystemUI/src/com/android/systemui/fragments/ExtensionFragmentListener.java
@@ -50,13 +50,12 @@
 
     @Override
     public void accept(T extension) {
-        try {
-            Fragment.class.cast(extension);
+        if (Fragment.class.isInstance(extension)) {
             mFragmentHostManager.getExtensionManager().setCurrentExtension(mId, mTag,
                     mOldClass, extension.getClass().getName(), mExtension.getContext());
             mOldClass = extension.getClass().getName();
-        } catch (ClassCastException e) {
-            Log.e(TAG, extension.getClass().getName() + " must be a Fragment", e);
+        } else {
+            Log.e(TAG, extension.getClass().getName() + " must be a Fragment");
         }
         mExtension.clearItem(true);
     }
diff --git a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java
index 3ef5499..db2cd91 100644
--- a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java
+++ b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java
@@ -125,6 +125,7 @@
 import com.android.systemui.plugins.GlobalActions.GlobalActionsManager;
 import com.android.systemui.plugins.GlobalActionsPanelPlugin;
 import com.android.systemui.scrim.ScrimDrawable;
+import com.android.systemui.settings.UserTracker;
 import com.android.systemui.statusbar.NotificationShadeWindowController;
 import com.android.systemui.statusbar.VibratorHelper;
 import com.android.systemui.statusbar.phone.CentralSurfaces;
@@ -201,6 +202,7 @@
     protected final SecureSettings mSecureSettings;
     protected final Resources mResources;
     private final ConfigurationController mConfigurationController;
+    private final UserTracker mUserTracker;
     private final UserManager mUserManager;
     private final TrustManager mTrustManager;
     private final IActivityManager mIActivityManager;
@@ -339,6 +341,7 @@
             @NonNull VibratorHelper vibrator,
             @Main Resources resources,
             ConfigurationController configurationController,
+            UserTracker userTracker,
             KeyguardStateController keyguardStateController,
             UserManager userManager,
             TrustManager trustManager,
@@ -370,6 +373,7 @@
         mSecureSettings = secureSettings;
         mResources = resources;
         mConfigurationController = configurationController;
+        mUserTracker = userTracker;
         mUserManager = userManager;
         mTrustManager = trustManager;
         mIActivityManager = iActivityManager;
@@ -1198,11 +1202,7 @@
     }
 
     protected UserInfo getCurrentUser() {
-        try {
-            return mIActivityManager.getCurrentUser();
-        } catch (RemoteException re) {
-            return null;
-        }
+        return mUserTracker.getUserInfo();
     }
 
     /**
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardSliceProvider.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardSliceProvider.java
index 5d564f7..bafd2e7 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardSliceProvider.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardSliceProvider.java
@@ -17,7 +17,6 @@
 package com.android.systemui.keyguard;
 
 import android.annotation.AnyThread;
-import android.app.ActivityManager;
 import android.app.AlarmManager;
 import android.app.PendingIntent;
 import android.content.BroadcastReceiver;
@@ -52,6 +51,7 @@
 import com.android.systemui.R;
 import com.android.systemui.SystemUIAppComponentFactory;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
+import com.android.systemui.settings.UserTracker;
 import com.android.systemui.statusbar.NotificationMediaManager;
 import com.android.systemui.statusbar.StatusBarState;
 import com.android.systemui.statusbar.phone.DozeParameters;
@@ -140,6 +140,8 @@
     public KeyguardBypassController mKeyguardBypassController;
     @Inject
     public KeyguardUpdateMonitor mKeyguardUpdateMonitor;
+    @Inject
+    UserTracker mUserTracker;
     private CharSequence mMediaTitle;
     private CharSequence mMediaArtist;
     protected boolean mDozing;
@@ -355,7 +357,7 @@
         synchronized (this) {
             if (withinNHoursLocked(mNextAlarmInfo, ALARM_VISIBILITY_HOURS)) {
                 String pattern = android.text.format.DateFormat.is24HourFormat(getContext(),
-                        ActivityManager.getCurrentUser()) ? "HH:mm" : "h:mm";
+                        mUserTracker.getUserId()) ? "HH:mm" : "h:mm";
                 mNextAlarm = android.text.format.DateFormat.format(pattern,
                         mNextAlarmInfo.getTriggerTime()).toString();
             } else {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
index d52efab..3d976d4 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
@@ -36,7 +36,6 @@
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
 import android.animation.ValueAnimator;
-import android.app.ActivityManager;
 import android.app.ActivityTaskManager;
 import android.app.AlarmManager;
 import android.app.PendingIntent;
@@ -124,6 +123,7 @@
 import com.android.systemui.keyguard.dagger.KeyguardModule;
 import com.android.systemui.navigationbar.NavigationModeController;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
+import com.android.systemui.settings.UserTracker;
 import com.android.systemui.shade.NotificationPanelViewController;
 import com.android.systemui.shade.ShadeController;
 import com.android.systemui.shade.ShadeExpansionStateManager;
@@ -263,6 +263,7 @@
     private AlarmManager mAlarmManager;
     private AudioManager mAudioManager;
     private StatusBarManager mStatusBarManager;
+    private final UserTracker mUserTracker;
     private final SysuiStatusBarStateController mStatusBarStateController;
     private final Executor mUiBgExecutor;
     private final ScreenOffAnimationController mScreenOffAnimationController;
@@ -409,6 +410,11 @@
     private final int mDreamOpenAnimationDuration;
 
     /**
+     * The duration in milliseconds of the dream close animation.
+     */
+    private final int mDreamCloseAnimationDuration;
+
+    /**
      * The animation used for hiding keyguard. This is used to fetch the animation timings if
      * WindowManager is not providing us with them.
      */
@@ -715,7 +721,7 @@
 
         @Override
         public void keyguardDone(boolean strongAuth, int targetUserId) {
-            if (targetUserId != ActivityManager.getCurrentUser()) {
+            if (targetUserId != mUserTracker.getUserId()) {
                 return;
             }
             if (DEBUG) Log.d(TAG, "keyguardDone");
@@ -738,7 +744,7 @@
         public void keyguardDonePending(boolean strongAuth, int targetUserId) {
             Trace.beginSection("KeyguardViewMediator.mViewMediatorCallback#keyguardDonePending");
             if (DEBUG) Log.d(TAG, "keyguardDonePending");
-            if (targetUserId != ActivityManager.getCurrentUser()) {
+            if (targetUserId != mUserTracker.getUserId()) {
                 Trace.endSection();
                 return;
             }
@@ -1055,7 +1061,8 @@
                         }
 
                         mUnoccludeAnimator = ValueAnimator.ofFloat(1f, 0f);
-                        mUnoccludeAnimator.setDuration(UNOCCLUDE_ANIMATION_DURATION);
+                        mUnoccludeAnimator.setDuration(isDream ? mDreamCloseAnimationDuration
+                                : UNOCCLUDE_ANIMATION_DURATION);
                         mUnoccludeAnimator.setInterpolator(Interpolators.TOUCH_RESPONSE);
                         mUnoccludeAnimator.addUpdateListener(
                                 animation -> {
@@ -1131,6 +1138,7 @@
      */
     public KeyguardViewMediator(
             Context context,
+            UserTracker userTracker,
             FalsingCollector falsingCollector,
             LockPatternUtils lockPatternUtils,
             BroadcastDispatcher broadcastDispatcher,
@@ -1156,6 +1164,7 @@
             Lazy<NotificationShadeWindowController> notificationShadeWindowControllerLazy,
             Lazy<ActivityLaunchAnimator> activityLaunchAnimator) {
         mContext = context;
+        mUserTracker = userTracker;
         mFalsingCollector = falsingCollector;
         mLockPatternUtils = lockPatternUtils;
         mBroadcastDispatcher = broadcastDispatcher;
@@ -1205,6 +1214,8 @@
 
         mDreamOpenAnimationDuration = context.getResources().getInteger(
                 com.android.internal.R.integer.config_dreamOpenAnimationDuration);
+        mDreamCloseAnimationDuration = context.getResources().getInteger(
+                com.android.internal.R.integer.config_dreamCloseAnimationDuration);
     }
 
     public void userActivity() {
@@ -1234,7 +1245,7 @@
 
         mAlarmManager = (AlarmManager) mContext.getSystemService(Context.ALARM_SERVICE);
 
-        KeyguardUpdateMonitor.setCurrentUser(ActivityManager.getCurrentUser());
+        KeyguardUpdateMonitor.setCurrentUser(mUserTracker.getUserId());
 
         // Assume keyguard is showing (unless it's disabled) until we know for sure, unless Keyguard
         // is disabled.
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java b/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java
index 78a7c9e..ef3c443 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java
@@ -47,6 +47,7 @@
 import com.android.systemui.keyguard.domain.interactor.StartKeyguardTransitionModule;
 import com.android.systemui.keyguard.domain.quickaffordance.KeyguardQuickAffordanceModule;
 import com.android.systemui.navigationbar.NavigationModeController;
+import com.android.systemui.settings.UserTracker;
 import com.android.systemui.shade.ShadeController;
 import com.android.systemui.statusbar.NotificationShadeDepthController;
 import com.android.systemui.statusbar.NotificationShadeWindowController;
@@ -86,6 +87,7 @@
     @SysUISingleton
     public static KeyguardViewMediator newKeyguardViewMediator(
             Context context,
+            UserTracker userTracker,
             FalsingCollector falsingCollector,
             LockPatternUtils lockPatternUtils,
             BroadcastDispatcher broadcastDispatcher,
@@ -114,6 +116,7 @@
             Lazy<ActivityLaunchAnimator> activityLaunchAnimator) {
         return new KeyguardViewMediator(
                 context,
+                userTracker,
                 falsingCollector,
                 lockPatternUtils,
                 broadcastDispatcher,
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/BuiltInKeyguardQuickAffordanceKeys.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/BuiltInKeyguardQuickAffordanceKeys.kt
index a069582..f5220b8 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/BuiltInKeyguardQuickAffordanceKeys.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/BuiltInKeyguardQuickAffordanceKeys.kt
@@ -24,6 +24,7 @@
  */
 object BuiltInKeyguardQuickAffordanceKeys {
     // Please keep alphabetical order of const names to simplify future maintenance.
+    const val CAMERA = "camera"
     const val HOME_CONTROLS = "home"
     const val QR_CODE_SCANNER = "qr_code_scanner"
     const val QUICK_ACCESS_WALLET = "wallet"
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/CameraQuickAffordanceConfig.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/CameraQuickAffordanceConfig.kt
new file mode 100644
index 0000000..3c09aab
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/CameraQuickAffordanceConfig.kt
@@ -0,0 +1,62 @@
+/*
+ *  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.keyguard.data.quickaffordance
+
+import android.app.StatusBarManager
+import android.content.Context
+import com.android.systemui.R
+import com.android.systemui.animation.Expandable
+import com.android.systemui.camera.CameraGestureHelper
+import com.android.systemui.common.shared.model.ContentDescription
+import com.android.systemui.common.shared.model.Icon
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.flowOf
+import javax.inject.Inject
+
+@SysUISingleton
+class CameraQuickAffordanceConfig @Inject constructor(
+        @Application private val context: Context,
+        private val cameraGestureHelper: CameraGestureHelper,
+) : KeyguardQuickAffordanceConfig {
+
+    override val key: String
+        get() = BuiltInKeyguardQuickAffordanceKeys.CAMERA
+
+    override val pickerName: String
+        get() = context.getString(R.string.accessibility_camera_button)
+
+    override val pickerIconResourceId: Int
+        get() = com.android.internal.R.drawable.perm_group_camera
+
+    override val lockScreenState: Flow<KeyguardQuickAffordanceConfig.LockScreenState>
+        get() = flowOf(
+            KeyguardQuickAffordanceConfig.LockScreenState.Visible(
+                    icon = Icon.Resource(
+                            com.android.internal.R.drawable.perm_group_camera,
+                            ContentDescription.Resource(R.string.accessibility_camera_button)
+                    )
+            )
+        )
+
+    override fun onTriggered(expandable: Expandable?): KeyguardQuickAffordanceConfig.OnTriggeredResult {
+        cameraGestureHelper.launchCamera(StatusBarManager.CAMERA_LAUNCH_SOURCE_QUICK_AFFORDANCE)
+        return KeyguardQuickAffordanceConfig.OnTriggeredResult.Handled
+    }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardDataQuickAffordanceModule.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardDataQuickAffordanceModule.kt
index bea9363..f7225a2 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardDataQuickAffordanceModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardDataQuickAffordanceModule.kt
@@ -29,8 +29,10 @@
         home: HomeControlsKeyguardQuickAffordanceConfig,
         quickAccessWallet: QuickAccessWalletKeyguardQuickAffordanceConfig,
         qrCodeScanner: QrCodeScannerKeyguardQuickAffordanceConfig,
+        camera: CameraQuickAffordanceConfig,
     ): Set<KeyguardQuickAffordanceConfig> {
         return setOf(
+            camera,
             home,
             quickAccessWallet,
             qrCodeScanner,
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardQuickAffordanceRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardQuickAffordanceRepository.kt
index 95f614f..9fda98c 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardQuickAffordanceRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardQuickAffordanceRepository.kt
@@ -17,6 +17,8 @@
 
 package com.android.systemui.keyguard.data.repository
 
+import android.content.Context
+import com.android.systemui.R
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.dagger.qualifiers.Background
@@ -24,7 +26,6 @@
 import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceSelectionManager
 import com.android.systemui.keyguard.shared.model.KeyguardQuickAffordancePickerRepresentation
 import com.android.systemui.keyguard.shared.model.KeyguardSlotPickerRepresentation
-import com.android.systemui.shared.keyguard.shared.model.KeyguardQuickAffordanceSlots
 import javax.inject.Inject
 import kotlinx.coroutines.CoroutineDispatcher
 import kotlinx.coroutines.CoroutineScope
@@ -39,6 +40,7 @@
 class KeyguardQuickAffordanceRepository
 @Inject
 constructor(
+    @Application private val appContext: Context,
     @Application private val scope: CoroutineScope,
     @Background private val backgroundDispatcher: CoroutineDispatcher,
     private val selectionManager: KeyguardQuickAffordanceSelectionManager,
@@ -61,6 +63,30 @@
                 initialValue = emptyMap(),
             )
 
+    private val _slotPickerRepresentations: List<KeyguardSlotPickerRepresentation> by lazy {
+        fun parseSlot(unparsedSlot: String): Pair<String, Int> {
+            val split = unparsedSlot.split(SLOT_CONFIG_DELIMITER)
+            check(split.size == 2)
+            val slotId = split[0]
+            val slotCapacity = split[1].toInt()
+            return slotId to slotCapacity
+        }
+
+        val unparsedSlots =
+            appContext.resources.getStringArray(R.array.config_keyguardQuickAffordanceSlots)
+
+        val seenSlotIds = mutableSetOf<String>()
+        unparsedSlots.mapNotNull { unparsedSlot ->
+            val (slotId, slotCapacity) = parseSlot(unparsedSlot)
+            check(!seenSlotIds.contains(slotId)) { "Duplicate slot \"$slotId\"!" }
+            seenSlotIds.add(slotId)
+            KeyguardSlotPickerRepresentation(
+                id = slotId,
+                maxSelectedAffordances = slotCapacity,
+            )
+        }
+    }
+
     /**
      * Returns a snapshot of the [KeyguardQuickAffordanceConfig] instances of the affordances at the
      * slot with the given ID. The configs are sorted in descending priority order.
@@ -115,14 +141,10 @@
      * each slot and select which affordance(s) is/are installed in each slot on the keyguard.
      */
     fun getSlotPickerRepresentations(): List<KeyguardSlotPickerRepresentation> {
-        // TODO(b/256195304): source these from a config XML file.
-        return listOf(
-            KeyguardSlotPickerRepresentation(
-                id = KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START,
-            ),
-            KeyguardSlotPickerRepresentation(
-                id = KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_END,
-            ),
-        )
+        return _slotPickerRepresentations
+    }
+
+    companion object {
+        private const val SLOT_CONFIG_DELIMITER = ":"
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDataFilter.kt b/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDataFilter.kt
index 45b319b..cf71d67 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDataFilter.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDataFilter.kt
@@ -20,13 +20,12 @@
 import android.os.SystemProperties
 import android.util.Log
 import com.android.internal.annotations.VisibleForTesting
-import com.android.systemui.broadcast.BroadcastDispatcher
 import com.android.systemui.broadcast.BroadcastSender
 import com.android.systemui.dagger.qualifiers.Main
 import com.android.systemui.media.controls.models.player.MediaData
 import com.android.systemui.media.controls.models.recommendation.SmartspaceMediaData
 import com.android.systemui.media.controls.util.MediaUiEventLogger
-import com.android.systemui.settings.CurrentUserTracker
+import com.android.systemui.settings.UserTracker
 import com.android.systemui.statusbar.NotificationLockscreenUserManager
 import com.android.systemui.util.time.SystemClock
 import java.util.SortedMap
@@ -62,14 +61,13 @@
 @Inject
 constructor(
     private val context: Context,
-    private val broadcastDispatcher: BroadcastDispatcher,
+    private val userTracker: UserTracker,
     private val broadcastSender: BroadcastSender,
     private val lockscreenUserManager: NotificationLockscreenUserManager,
     @Main private val executor: Executor,
     private val systemClock: SystemClock,
     private val logger: MediaUiEventLogger
 ) : MediaDataManager.Listener {
-    private val userTracker: CurrentUserTracker
     private val _listeners: MutableSet<MediaDataManager.Listener> = mutableSetOf()
     internal val listeners: Set<MediaDataManager.Listener>
         get() = _listeners.toSet()
@@ -81,15 +79,15 @@
     private var smartspaceMediaData: SmartspaceMediaData = EMPTY_SMARTSPACE_MEDIA_DATA
     private var reactivatedKey: String? = null
 
-    init {
-        userTracker =
-            object : CurrentUserTracker(broadcastDispatcher) {
-                override fun onUserSwitched(newUserId: Int) {
-                    // Post this so we can be sure lockscreenUserManager already got the broadcast
-                    executor.execute { handleUserSwitched(newUserId) }
-                }
+    private val userTrackerCallback =
+        object : UserTracker.Callback {
+            override fun onUserChanged(newUser: Int, userContext: Context) {
+                handleUserSwitched(newUser)
             }
-        userTracker.startTracking()
+        }
+
+    init {
+        userTracker.addCallback(userTrackerCallback, executor)
     }
 
     override fun onMediaDataLoaded(
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDataManager.kt b/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDataManager.kt
index 14dd990..3012bb4 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDataManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDataManager.kt
@@ -82,7 +82,6 @@
 import java.io.IOException
 import java.io.PrintWriter
 import java.util.concurrent.Executor
-import java.util.concurrent.Executors
 import javax.inject.Inject
 
 // URI fields to try loading album art from
@@ -154,6 +153,7 @@
 class MediaDataManager(
     private val context: Context,
     @Background private val backgroundExecutor: Executor,
+    @Main private val uiExecutor: Executor,
     @Main private val foregroundExecutor: DelayableExecutor,
     private val mediaControllerFactory: MediaControllerFactory,
     private val broadcastDispatcher: BroadcastDispatcher,
@@ -171,7 +171,8 @@
     private val systemClock: SystemClock,
     private val tunerService: TunerService,
     private val mediaFlags: MediaFlags,
-    private val logger: MediaUiEventLogger
+    private val logger: MediaUiEventLogger,
+    private val smartspaceManager: SmartspaceManager,
 ) : Dumpable, BcSmartspaceDataPlugin.SmartspaceTargetListener {
 
     companion object {
@@ -218,6 +219,7 @@
     constructor(
         context: Context,
         @Background backgroundExecutor: Executor,
+        @Main uiExecutor: Executor,
         @Main foregroundExecutor: DelayableExecutor,
         mediaControllerFactory: MediaControllerFactory,
         dumpManager: DumpManager,
@@ -233,10 +235,12 @@
         clock: SystemClock,
         tunerService: TunerService,
         mediaFlags: MediaFlags,
-        logger: MediaUiEventLogger
+        logger: MediaUiEventLogger,
+        smartspaceManager: SmartspaceManager,
     ) : this(
         context,
         backgroundExecutor,
+        uiExecutor,
         foregroundExecutor,
         mediaControllerFactory,
         broadcastDispatcher,
@@ -254,7 +258,8 @@
         clock,
         tunerService,
         mediaFlags,
-        logger
+        logger,
+        smartspaceManager,
     )
 
     private val appChangeReceiver =
@@ -314,21 +319,18 @@
 
         // Register for Smartspace data updates.
         smartspaceMediaDataProvider.registerListener(this)
-        val smartspaceManager: SmartspaceManager =
-            context.getSystemService(SmartspaceManager::class.java)
         smartspaceSession =
             smartspaceManager.createSmartspaceSession(
                 SmartspaceConfig.Builder(context, SMARTSPACE_UI_SURFACE_LABEL).build()
             )
         smartspaceSession?.let {
             it.addOnTargetsAvailableListener(
-                // Use a new thread listening to Smartspace updates instead of using the existing
-                // backgroundExecutor. SmartspaceSession has scheduled routine updates which can be
-                // unpredictable on test simulators, using the backgroundExecutor makes it's hard to
-                // test the threads numbers.
-                // Switch to use backgroundExecutor when SmartspaceSession has a good way to be
-                // mocked.
-                Executors.newCachedThreadPool(),
+                // Use a main uiExecutor thread listening to Smartspace updates instead of using
+                // the existing background executor.
+                // SmartspaceSession has scheduled routine updates which can be unpredictable on
+                // test simulators, using the backgroundExecutor makes it's hard to test the threads
+                // numbers.
+                uiExecutor,
                 SmartspaceSession.OnTargetsAvailableListener { targets ->
                     smartspaceMediaDataProvider.onTargetsAvailable(targets)
                 }
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaControlPanel.java b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaControlPanel.java
index 4747052..21e64e2 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaControlPanel.java
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaControlPanel.java
@@ -589,7 +589,10 @@
         seamlessView.setContentDescription(deviceString);
         seamlessView.setOnClickListener(
                 v -> {
-                    if (mFalsingManager.isFalseTap(FalsingManager.LOW_PENALTY)) {
+                    if (mFalsingManager.isFalseTap(
+                            mFeatureFlags.isEnabled(Flags.MEDIA_FALSING_PENALTY)
+                                    ? FalsingManager.MODERATE_PENALTY :
+                                    FalsingManager.LOW_PENALTY)) {
                         return;
                     }
 
@@ -1012,7 +1015,10 @@
             } else {
                 button.setEnabled(true);
                 button.setOnClickListener(v -> {
-                    if (!mFalsingManager.isFalseTap(FalsingManager.LOW_PENALTY)) {
+                    if (!mFalsingManager.isFalseTap(
+                            mFeatureFlags.isEnabled(Flags.MEDIA_FALSING_PENALTY)
+                                    ? FalsingManager.MODERATE_PENALTY :
+                                    FalsingManager.LOW_PENALTY)) {
                         mLogger.logTapAction(button.getId(), mUid, mPackageName, mInstanceId);
                         logSmartspaceCardReported(SMARTSPACE_CARD_CLICK_EVENT);
                         action.run();
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/data/RecentTaskListProvider.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/data/RecentTaskListProvider.kt
index e8b49cd..7a77c47 100644
--- a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/data/RecentTaskListProvider.kt
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/data/RecentTaskListProvider.kt
@@ -16,19 +16,19 @@
 
 package com.android.systemui.mediaprojection.appselector.data
 
-import android.app.ActivityManager
 import android.app.ActivityManager.RECENT_IGNORE_UNAVAILABLE
 import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.settings.UserTracker
 import com.android.systemui.util.kotlin.getOrNull
 import com.android.wm.shell.recents.RecentTasks
 import com.android.wm.shell.util.GroupedRecentTaskInfo
 import java.util.Optional
+import java.util.concurrent.Executor
 import javax.inject.Inject
 import kotlin.coroutines.resume
 import kotlin.coroutines.suspendCoroutine
 import kotlinx.coroutines.CoroutineDispatcher
 import kotlinx.coroutines.withContext
-import java.util.concurrent.Executor
 
 interface RecentTaskListProvider {
     /** Loads recent tasks, the returned task list is from the most-recent to least-recent order */
@@ -40,7 +40,8 @@
 constructor(
     @Background private val coroutineDispatcher: CoroutineDispatcher,
     @Background private val backgroundExecutor: Executor,
-    private val recentTasks: Optional<RecentTasks>
+    private val recentTasks: Optional<RecentTasks>,
+    private val userTracker: UserTracker
 ) : RecentTaskListProvider {
 
     private val recents by lazy { recentTasks.getOrNull() }
@@ -67,10 +68,8 @@
             getRecentTasks(
                 Integer.MAX_VALUE,
                 RECENT_IGNORE_UNAVAILABLE,
-                ActivityManager.getCurrentUser(),
+                userTracker.userId,
                 backgroundExecutor
-            ) { tasks ->
-                continuation.resume(tasks)
-            }
+            ) { tasks -> continuation.resume(tasks) }
         }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarInflaterView.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarInflaterView.java
index 59bb2278e..2a7704f 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarInflaterView.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarInflaterView.java
@@ -45,11 +45,10 @@
 import com.android.systemui.shared.system.QuickStepContract;
 
 import java.io.PrintWriter;
+import java.lang.ref.WeakReference;
 import java.util.Objects;
 
-public class NavigationBarInflaterView extends FrameLayout
-        implements NavigationModeController.ModeChangedListener {
-
+public class NavigationBarInflaterView extends FrameLayout {
     private static final String TAG = "NavBarInflater";
 
     public static final String NAV_BAR_VIEWS = "sysui_nav_bar";
@@ -83,6 +82,24 @@
     private static final String ABSOLUTE_SUFFIX = "A";
     private static final String ABSOLUTE_VERTICAL_CENTERED_SUFFIX = "C";
 
+    private static class Listener implements NavigationModeController.ModeChangedListener {
+        private final WeakReference<NavigationBarInflaterView> mSelf;
+
+        Listener(NavigationBarInflaterView self) {
+            mSelf = new WeakReference<>(self);
+        }
+
+        @Override
+        public void onNavigationModeChanged(int mode) {
+            NavigationBarInflaterView self = mSelf.get();
+            if (self != null) {
+                self.onNavigationModeChanged(mode);
+            }
+        }
+    }
+
+    private final Listener mListener;
+
     protected LayoutInflater mLayoutInflater;
     protected LayoutInflater mLandscapeInflater;
 
@@ -106,7 +123,8 @@
         super(context, attrs);
         createInflaters();
         mOverviewProxyService = Dependency.get(OverviewProxyService.class);
-        mNavBarMode = Dependency.get(NavigationModeController.class).addListener(this);
+        mListener = new Listener(this);
+        mNavBarMode = Dependency.get(NavigationModeController.class).addListener(mListener);
     }
 
     @VisibleForTesting
@@ -146,14 +164,13 @@
         return getContext().getString(defaultResource);
     }
 
-    @Override
-    public void onNavigationModeChanged(int mode) {
+    private void onNavigationModeChanged(int mode) {
         mNavBarMode = mode;
     }
 
     @Override
     protected void onDetachedFromWindow() {
-        Dependency.get(NavigationModeController.class).removeListener(this);
+        Dependency.get(NavigationModeController.class).removeListener(mListener);
         super.onDetachedFromWindow();
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java
index 7964d16..4e3831c 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java
@@ -59,7 +59,6 @@
 import com.android.internal.config.sysui.SystemUiDeviceConfigFlags;
 import com.android.internal.policy.GestureNavigationSettingsObserver;
 import com.android.systemui.R;
-import com.android.systemui.broadcast.BroadcastDispatcher;
 import com.android.systemui.dagger.qualifiers.Background;
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.flags.FeatureFlags;
@@ -71,7 +70,7 @@
 import com.android.systemui.plugins.NavigationEdgeBackPlugin;
 import com.android.systemui.plugins.PluginListener;
 import com.android.systemui.recents.OverviewProxyService;
-import com.android.systemui.settings.CurrentUserTracker;
+import com.android.systemui.settings.UserTracker;
 import com.android.systemui.shared.plugins.PluginManager;
 import com.android.systemui.shared.system.ActivityManagerWrapper;
 import com.android.systemui.shared.system.InputChannelCompat;
@@ -102,8 +101,8 @@
 /**
  * Utility class to handle edge swipes for back gesture
  */
-public class EdgeBackGestureHandler extends CurrentUserTracker
-        implements PluginListener<NavigationEdgeBackPlugin>, ProtoTraceable<SystemUiTraceProto> {
+public class EdgeBackGestureHandler implements PluginListener<NavigationEdgeBackPlugin>,
+        ProtoTraceable<SystemUiTraceProto> {
 
     private static final String TAG = "EdgeBackGestureHandler";
     private static final int MAX_LONG_PRESS_TIMEOUT = SystemProperties.getInt(
@@ -172,6 +171,7 @@
 
 
     private final Context mContext;
+    private final UserTracker mUserTracker;
     private final OverviewProxyService mOverviewProxyService;
     private final SysUiState mSysUiState;
     private Runnable mStateChangeCallback;
@@ -321,6 +321,15 @@
     private final Consumer<Boolean> mOnIsInPipStateChangedListener =
             (isInPip) -> mIsInPip = isInPip;
 
+    private final UserTracker.Callback mUserChangedCallback =
+            new UserTracker.Callback() {
+                @Override
+                public void onUserChanged(int newUser, @NonNull Context userContext) {
+                    updateIsEnabled();
+                    updateCurrentUserResources();
+                }
+            };
+
     EdgeBackGestureHandler(
             Context context,
             OverviewProxyService overviewProxyService,
@@ -328,7 +337,7 @@
             PluginManager pluginManager,
             @Main Executor executor,
             @Background Executor backgroundExecutor,
-            BroadcastDispatcher broadcastDispatcher,
+            UserTracker userTracker,
             ProtoTracer protoTracer,
             NavigationModeController navigationModeController,
             BackPanelController.Factory backPanelControllerFactory,
@@ -340,11 +349,11 @@
             Provider<NavigationBarEdgePanel> navigationBarEdgePanelProvider,
             Provider<BackGestureTfClassifierProvider> backGestureTfClassifierProviderProvider,
             FeatureFlags featureFlags) {
-        super(broadcastDispatcher);
         mContext = context;
         mDisplayId = context.getDisplayId();
         mMainExecutor = executor;
         mBackgroundExecutor = backgroundExecutor;
+        mUserTracker = userTracker;
         mOverviewProxyService = overviewProxyService;
         mSysUiState = sysUiState;
         mPluginManager = pluginManager;
@@ -463,12 +472,6 @@
         }
     }
 
-    @Override
-    public void onUserSwitched(int newUserId) {
-        updateIsEnabled();
-        updateCurrentUserResources();
-    }
-
     /**
      * @see NavigationBarView#onAttachedToWindow()
      */
@@ -478,7 +481,7 @@
         mOverviewProxyService.addCallback(mQuickSwitchListener);
         mSysUiState.addCallback(mSysUiStateCallback);
         updateIsEnabled();
-        startTracking();
+        mUserTracker.addCallback(mUserChangedCallback, mMainExecutor);
     }
 
     /**
@@ -490,7 +493,7 @@
         mOverviewProxyService.removeCallback(mQuickSwitchListener);
         mSysUiState.removeCallback(mSysUiStateCallback);
         updateIsEnabled();
-        stopTracking();
+        mUserTracker.removeCallback(mUserChangedCallback);
     }
 
     /**
@@ -1093,7 +1096,7 @@
         private final PluginManager mPluginManager;
         private final Executor mExecutor;
         private final Executor mBackgroundExecutor;
-        private final BroadcastDispatcher mBroadcastDispatcher;
+        private final UserTracker mUserTracker;
         private final ProtoTracer mProtoTracer;
         private final NavigationModeController mNavigationModeController;
         private final BackPanelController.Factory mBackPanelControllerFactory;
@@ -1113,7 +1116,7 @@
                        PluginManager pluginManager,
                        @Main Executor executor,
                        @Background Executor backgroundExecutor,
-                       BroadcastDispatcher broadcastDispatcher,
+                       UserTracker userTracker,
                        ProtoTracer protoTracer,
                        NavigationModeController navigationModeController,
                        BackPanelController.Factory backPanelControllerFactory,
@@ -1131,7 +1134,7 @@
             mPluginManager = pluginManager;
             mExecutor = executor;
             mBackgroundExecutor = backgroundExecutor;
-            mBroadcastDispatcher = broadcastDispatcher;
+            mUserTracker = userTracker;
             mProtoTracer = protoTracer;
             mNavigationModeController = navigationModeController;
             mBackPanelControllerFactory = backPanelControllerFactory;
@@ -1154,7 +1157,7 @@
                     mPluginManager,
                     mExecutor,
                     mBackgroundExecutor,
-                    mBroadcastDispatcher,
+                    mUserTracker,
                     mProtoTracer,
                     mNavigationModeController,
                     mBackPanelControllerFactory,
diff --git a/packages/SystemUI/src/com/android/systemui/people/PeopleTileViewHelper.java b/packages/SystemUI/src/com/android/systemui/people/PeopleTileViewHelper.java
index be82b1f..67e9664 100644
--- a/packages/SystemUI/src/com/android/systemui/people/PeopleTileViewHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/people/PeopleTileViewHelper.java
@@ -1096,7 +1096,7 @@
             Pair<Integer, Integer> first = emojiIndices.get(i - 1);
 
             // Check if second emoji starts right after first starts
-            if (second.first == first.second) {
+            if (Objects.equals(second.first, first.second)) {
                 // Check if emojis in sequence are the same
                 if (Objects.equals(emojiTexts.get(i), emojiTexts.get(i - 1))) {
                     if (DEBUG) {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/SettingObserver.java b/packages/SystemUI/src/com/android/systemui/qs/SettingObserver.java
index 6b0abd4..7794fa0 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/SettingObserver.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/SettingObserver.java
@@ -16,7 +16,6 @@
 
 package com.android.systemui.qs;
 
-import android.app.ActivityManager;
 import android.database.ContentObserver;
 import android.os.Handler;
 
@@ -47,10 +46,6 @@
         this(settingsProxy, handler, settingName, userId, 0);
     }
 
-    public SettingObserver(SettingsProxy settingsProxy, Handler handler, String settingName) {
-        this(settingsProxy, handler, settingName, ActivityManager.getCurrentUser());
-    }
-
     public SettingObserver(SettingsProxy settingsProxy, Handler handler, String settingName,
             int userId, int defaultValue) {
         super(handler);
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/AirplaneModeTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/AirplaneModeTile.java
index 86d4fa3..033dbe0 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/AirplaneModeTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/AirplaneModeTile.java
@@ -48,6 +48,7 @@
 import com.android.systemui.qs.SettingObserver;
 import com.android.systemui.qs.logging.QSLogger;
 import com.android.systemui.qs.tileimpl.QSTileImpl;
+import com.android.systemui.settings.UserTracker;
 import com.android.systemui.util.settings.GlobalSettings;
 
 import javax.inject.Inject;
@@ -74,14 +75,16 @@
             QSLogger qsLogger,
             BroadcastDispatcher broadcastDispatcher,
             Lazy<ConnectivityManager> lazyConnectivityManager,
-            GlobalSettings globalSettings
+            GlobalSettings globalSettings,
+            UserTracker userTracker
     ) {
         super(host, backgroundLooper, mainHandler, falsingManager, metricsLogger,
                 statusBarStateController, activityStarter, qsLogger);
         mBroadcastDispatcher = broadcastDispatcher;
         mLazyConnectivityManager = lazyConnectivityManager;
 
-        mSetting = new SettingObserver(globalSettings, mHandler, Global.AIRPLANE_MODE_ON) {
+        mSetting = new SettingObserver(globalSettings, mHandler, Global.AIRPLANE_MODE_ON,
+                userTracker.getUserId()) {
             @Override
             protected void handleValueChanged(int value, boolean observedChange) {
                 // mHandler is the background handler so calling this is OK
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/DreamTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/DreamTile.java
index 4abe309..5bc209a 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/DreamTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/DreamTile.java
@@ -108,14 +108,14 @@
         mDreamManager = dreamManager;
         mBroadcastDispatcher = broadcastDispatcher;
         mEnabledSettingObserver = new SettingObserver(secureSettings, mHandler,
-                Settings.Secure.SCREENSAVER_ENABLED) {
+                Settings.Secure.SCREENSAVER_ENABLED, userTracker.getUserId()) {
             @Override
             protected void handleValueChanged(int value, boolean observedChange) {
                 refreshState();
             }
         };
         mDreamSettingObserver = new SettingObserver(secureSettings, mHandler,
-                Settings.Secure.SCREENSAVER_COMPONENTS) {
+                Settings.Secure.SCREENSAVER_COMPONENTS, userTracker.getUserId()) {
             @Override
             protected void handleValueChanged(int value, boolean observedChange) {
                 refreshState();
diff --git a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
index ba97297..547b496 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
@@ -78,8 +78,8 @@
 import com.android.internal.policy.ScreenDecorationsUtils;
 import com.android.internal.util.ScreenshotHelper;
 import com.android.systemui.Dumpable;
-import com.android.systemui.broadcast.BroadcastDispatcher;
 import com.android.systemui.dagger.SysUISingleton;
+import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.dump.DumpManager;
 import com.android.systemui.keyguard.KeyguardUnlockAnimationController;
 import com.android.systemui.keyguard.ScreenLifecycle;
@@ -90,12 +90,11 @@
 import com.android.systemui.navigationbar.NavigationModeController;
 import com.android.systemui.navigationbar.buttons.KeyButtonView;
 import com.android.systemui.recents.OverviewProxyService.OverviewProxyListener;
-import com.android.systemui.settings.CurrentUserTracker;
+import com.android.systemui.settings.UserTracker;
 import com.android.systemui.shade.NotificationPanelViewController;
 import com.android.systemui.shared.recents.IOverviewProxy;
 import com.android.systemui.shared.recents.ISystemUiProxy;
 import com.android.systemui.shared.recents.model.Task;
-import com.android.systemui.shared.system.ActivityManagerWrapper;
 import com.android.systemui.shared.system.QuickStepContract;
 import com.android.systemui.statusbar.CommandQueue;
 import com.android.systemui.statusbar.NotificationShadeWindowController;
@@ -108,20 +107,19 @@
 import java.util.ArrayList;
 import java.util.List;
 import java.util.Optional;
+import java.util.concurrent.Executor;
 import java.util.function.Supplier;
 
 import javax.inject.Inject;
 
 import dagger.Lazy;
 
-
 /**
  * Class to send information from overview to launcher with a binder.
  */
 @SysUISingleton
-public class OverviewProxyService extends CurrentUserTracker implements
-        CallbackController<OverviewProxyListener>, NavigationModeController.ModeChangedListener,
-        Dumpable {
+public class OverviewProxyService implements CallbackController<OverviewProxyListener>,
+        NavigationModeController.ModeChangedListener, Dumpable {
 
     private static final String ACTION_QUICKSTEP = "android.intent.action.QUICKSTEP_SERVICE";
 
@@ -133,6 +131,7 @@
     private static final long MAX_BACKOFF_MILLIS = 10 * 60 * 1000;
 
     private final Context mContext;
+    private final Executor mMainExecutor;
     private final ShellInterface mShellInterface;
     private final Lazy<Optional<CentralSurfaces>> mCentralSurfacesOptionalLazy;
     private SysUiState mSysUiState;
@@ -145,6 +144,7 @@
     private final Intent mQuickStepIntent;
     private final ScreenshotHelper mScreenshotHelper;
     private final CommandQueue mCommandQueue;
+    private final UserTracker mUserTracker;
     private final KeyguardUnlockAnimationController mSysuiUnlockAnimationController;
     private final UiEventLogger mUiEventLogger;
 
@@ -417,7 +417,7 @@
                 return;
             }
 
-            mCurrentBoundedUserId = getCurrentUserId();
+            mCurrentBoundedUserId = mUserTracker.getUserId();
             mOverviewProxy = IOverviewProxy.Stub.asInterface(service);
 
             Bundle params = new Bundle();
@@ -498,34 +498,44 @@
         }
     };
 
+    private final UserTracker.Callback mUserChangedCallback =
+            new UserTracker.Callback() {
+                @Override
+                public void onUserChanged(int newUser, @NonNull Context userContext) {
+                    mConnectionBackoffAttempts = 0;
+                    internalConnectToCurrentUser();
+                }
+            };
+
     @SuppressWarnings("OptionalUsedAsFieldOrParameterType")
     @Inject
     public OverviewProxyService(Context context,
+            @Main Executor mainExecutor,
             CommandQueue commandQueue,
             ShellInterface shellInterface,
             Lazy<NavigationBarController> navBarControllerLazy,
             Lazy<Optional<CentralSurfaces>> centralSurfacesOptionalLazy,
             NavigationModeController navModeController,
             NotificationShadeWindowController statusBarWinController, SysUiState sysUiState,
-            BroadcastDispatcher broadcastDispatcher,
+            UserTracker userTracker,
             ScreenLifecycle screenLifecycle,
             UiEventLogger uiEventLogger,
             KeyguardUnlockAnimationController sysuiUnlockAnimationController,
             AssistUtils assistUtils,
             DumpManager dumpManager) {
-        super(broadcastDispatcher);
-
         // b/241601880: This component shouldn't be running for a non-primary user
         if (!Process.myUserHandle().equals(UserHandle.SYSTEM)) {
             Log.e(TAG_OPS, "Unexpected initialization for non-primary user", new Throwable());
         }
 
         mContext = context;
+        mMainExecutor = mainExecutor;
         mShellInterface = shellInterface;
         mCentralSurfacesOptionalLazy = centralSurfacesOptionalLazy;
         mHandler = new Handler();
         mNavBarControllerLazy = navBarControllerLazy;
         mStatusBarWinController = statusBarWinController;
+        mUserTracker = userTracker;
         mConnectionBackoffAttempts = 0;
         mRecentsComponentName = ComponentName.unflattenFromString(context.getString(
                 com.android.internal.R.string.config_recentsComponentName));
@@ -566,7 +576,7 @@
         mCommandQueue = commandQueue;
 
         // Listen for user setup
-        startTracking();
+        mUserTracker.addCallback(mUserChangedCallback, mMainExecutor);
 
         screenLifecycle.addObserver(mLifecycleObserver);
 
@@ -579,12 +589,6 @@
         assistUtils.registerVoiceInteractionSessionListener(mVoiceInteractionSessionListener);
     }
 
-    @Override
-    public void onUserSwitched(int newUserId) {
-        mConnectionBackoffAttempts = 0;
-        internalConnectToCurrentUser();
-    }
-
     public void onVoiceSessionWindowVisibilityChanged(boolean visible) {
         mSysUiState.setFlag(SYSUI_STATE_VOICE_INTERACTION_WINDOW_SHOWING, visible)
                 .commitUpdate(mContext.getDisplayId());
@@ -712,7 +716,7 @@
             mBound = mContext.bindServiceAsUser(launcherServiceIntent,
                     mOverviewServiceConnection,
                     Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE_WHILE_AWAKE,
-                    UserHandle.of(getCurrentUserId()));
+                    UserHandle.of(mUserTracker.getUserId()));
         } catch (SecurityException e) {
             Log.e(TAG_OPS, "Unable to bind because of security error", e);
         }
@@ -941,7 +945,7 @@
     }
 
     private void updateEnabledState() {
-        final int currentUser = ActivityManagerWrapper.getInstance().getCurrentUserId();
+        final int currentUser = mUserTracker.getUserId();
         mIsEnabled = mContext.getPackageManager().resolveServiceAsUser(mQuickStepIntent,
                 MATCH_SYSTEM_ONLY, currentUser) != null;
     }
diff --git a/packages/SystemUI/src/com/android/systemui/settings/CurrentUserObservable.java b/packages/SystemUI/src/com/android/systemui/settings/CurrentUserObservable.java
deleted file mode 100644
index dea8c32..0000000
--- a/packages/SystemUI/src/com/android/systemui/settings/CurrentUserObservable.java
+++ /dev/null
@@ -1,63 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.settings;
-
-import androidx.lifecycle.LiveData;
-import androidx.lifecycle.MutableLiveData;
-
-import com.android.systemui.broadcast.BroadcastDispatcher;
-
-/**
- * A class that has an observable for the current user.
- */
-public class CurrentUserObservable {
-
-    private final CurrentUserTracker mTracker;
-
-    private final MutableLiveData<Integer> mCurrentUser = new MutableLiveData<Integer>() {
-        @Override
-        protected void onActive() {
-            super.onActive();
-            mTracker.startTracking();
-        }
-
-        @Override
-        protected void onInactive() {
-            super.onInactive();
-            mTracker.stopTracking();
-        }
-    };
-
-    public CurrentUserObservable(BroadcastDispatcher broadcastDispatcher) {
-        mTracker = new CurrentUserTracker(broadcastDispatcher) {
-            @Override
-            public void onUserSwitched(int newUserId) {
-                mCurrentUser.setValue(newUserId);
-            }
-        };
-    }
-
-    /**
-     * Returns the current user that can be observed.
-     */
-    public LiveData<Integer> getCurrentUser() {
-        if (mCurrentUser.getValue() == null) {
-            mCurrentUser.setValue(mTracker.getCurrentUserId());
-        }
-        return mCurrentUser;
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/settings/CurrentUserTracker.java b/packages/SystemUI/src/com/android/systemui/settings/CurrentUserTracker.java
deleted file mode 100644
index 9599d77..0000000
--- a/packages/SystemUI/src/com/android/systemui/settings/CurrentUserTracker.java
+++ /dev/null
@@ -1,130 +0,0 @@
-/*
- * Copyright (C) 2013 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS 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.settings;
-
-import android.app.ActivityManager;
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.os.UserHandle;
-
-import com.android.internal.annotations.VisibleForTesting;
-import com.android.systemui.broadcast.BroadcastDispatcher;
-
-import java.util.ArrayList;
-import java.util.List;
-import java.util.function.Consumer;
-
-public abstract class CurrentUserTracker {
-    private final UserReceiver mUserReceiver;
-
-    private Consumer<Integer> mCallback = this::onUserSwitched;
-
-    public CurrentUserTracker(BroadcastDispatcher broadcastDispatcher) {
-        this(UserReceiver.getInstance(broadcastDispatcher));
-    }
-
-    @VisibleForTesting
-    CurrentUserTracker(UserReceiver receiver) {
-        mUserReceiver = receiver;
-    }
-
-    public int getCurrentUserId() {
-        return mUserReceiver.getCurrentUserId();
-    }
-
-    public void startTracking() {
-        mUserReceiver.addTracker(mCallback);
-    }
-
-    public void stopTracking() {
-        mUserReceiver.removeTracker(mCallback);
-    }
-
-    public abstract void onUserSwitched(int newUserId);
-
-    @VisibleForTesting
-    static class UserReceiver extends BroadcastReceiver {
-        private static UserReceiver sInstance;
-
-        private boolean mReceiverRegistered;
-        private int mCurrentUserId;
-        private final BroadcastDispatcher mBroadcastDispatcher;
-
-        private List<Consumer<Integer>> mCallbacks = new ArrayList<>();
-
-        @VisibleForTesting
-        UserReceiver(BroadcastDispatcher broadcastDispatcher) {
-            mBroadcastDispatcher = broadcastDispatcher;
-        }
-
-        static UserReceiver getInstance(BroadcastDispatcher broadcastDispatcher) {
-            if (sInstance == null) {
-                sInstance = new UserReceiver(broadcastDispatcher);
-            }
-            return sInstance;
-        }
-
-        @Override
-        public void onReceive(Context context, Intent intent) {
-            if (Intent.ACTION_USER_SWITCHED.equals(intent.getAction())) {
-                notifyUserSwitched(intent.getIntExtra(Intent.EXTRA_USER_HANDLE, 0));
-            }
-        }
-
-        public int getCurrentUserId() {
-            return mCurrentUserId;
-        }
-
-        private void addTracker(Consumer<Integer> callback) {
-            if (!mCallbacks.contains(callback)) {
-                mCallbacks.add(callback);
-            }
-            if (!mReceiverRegistered) {
-                mCurrentUserId = ActivityManager.getCurrentUser();
-                IntentFilter filter = new IntentFilter(Intent.ACTION_USER_SWITCHED);
-                mBroadcastDispatcher.registerReceiver(this, filter, null,
-                        UserHandle.ALL);
-                mReceiverRegistered = true;
-            }
-        }
-
-        private void removeTracker(Consumer<Integer> callback) {
-            if (mCallbacks.contains(callback)) {
-                mCallbacks.remove(callback);
-                if (mCallbacks.size() == 0 && mReceiverRegistered) {
-                    mBroadcastDispatcher.unregisterReceiver(this);
-                    mReceiverRegistered = false;
-                }
-            }
-        }
-
-        private void notifyUserSwitched(int newUserId) {
-            if (mCurrentUserId != newUserId) {
-                mCurrentUserId = newUserId;
-                List<Consumer<Integer>> callbacks = new ArrayList<>(mCallbacks);
-                for (Consumer<Integer> consumer : callbacks) {
-                    // Accepting may modify this list
-                    if (mCallbacks.contains(consumer)) {
-                        consumer.accept(newUserId);
-                    }
-                }
-            }
-        }
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessController.java b/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessController.java
index 7801c68..5880003 100644
--- a/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessController.java
+++ b/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessController.java
@@ -21,6 +21,7 @@
 import static com.android.settingslib.display.BrightnessUtils.convertLinearToGammaFloat;
 
 import android.animation.ValueAnimator;
+import android.annotation.NonNull;
 import android.content.ContentResolver;
 import android.content.Context;
 import android.database.ContentObserver;
@@ -46,11 +47,13 @@
 import com.android.internal.logging.MetricsLogger;
 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
 import com.android.settingslib.RestrictedLockUtilsInternal;
-import com.android.systemui.broadcast.BroadcastDispatcher;
 import com.android.systemui.dagger.qualifiers.Background;
-import com.android.systemui.settings.CurrentUserTracker;
+import com.android.systemui.dagger.qualifiers.Main;
+import com.android.systemui.settings.UserTracker;
 import com.android.systemui.statusbar.policy.BrightnessMirrorController;
 
+import java.util.concurrent.Executor;
+
 import javax.inject.Inject;
 
 public class BrightnessController implements ToggleSlider.Listener, MirroredBrightnessController {
@@ -74,9 +77,10 @@
     private final Context mContext;
     private final ToggleSlider mControl;
     private final DisplayManager mDisplayManager;
-    private final CurrentUserTracker mUserTracker;
+    private final UserTracker mUserTracker;
     private final IVrManager mVrManager;
 
+    private final Executor mMainExecutor;
     private final Handler mBackgroundHandler;
     private final BrightnessObserver mBrightnessObserver;
 
@@ -169,7 +173,7 @@
             }
 
             mBrightnessObserver.startObserving();
-            mUserTracker.startTracking();
+            mUserTracker.addCallback(mUserChangedCallback, mMainExecutor);
 
             // Update the slider and mode before attaching the listener so we don't
             // receive the onChanged notifications for the initial values.
@@ -197,7 +201,7 @@
             }
 
             mBrightnessObserver.stopObserving();
-            mUserTracker.stopTracking();
+            mUserTracker.removeCallback(mUserChangedCallback);
 
             mHandler.sendEmptyMessage(MSG_DETACH_LISTENER);
         }
@@ -275,22 +279,27 @@
         }
     };
 
+    private final UserTracker.Callback mUserChangedCallback =
+            new UserTracker.Callback() {
+                @Override
+                public void onUserChanged(int newUser, @NonNull Context userContext) {
+                    mBackgroundHandler.post(mUpdateModeRunnable);
+                    mBackgroundHandler.post(mUpdateSliderRunnable);
+                }
+            };
+
     public BrightnessController(
             Context context,
             ToggleSlider control,
-            BroadcastDispatcher broadcastDispatcher,
+            UserTracker userTracker,
+            @Main Executor mainExecutor,
             @Background Handler bgHandler) {
         mContext = context;
         mControl = control;
         mControl.setMax(GAMMA_SPACE_MAX);
+        mMainExecutor = mainExecutor;
         mBackgroundHandler = bgHandler;
-        mUserTracker = new CurrentUserTracker(broadcastDispatcher) {
-            @Override
-            public void onUserSwitched(int newUserId) {
-                mBackgroundHandler.post(mUpdateModeRunnable);
-                mBackgroundHandler.post(mUpdateSliderRunnable);
-            }
-        };
+        mUserTracker = userTracker;
         mBrightnessObserver = new BrightnessObserver(mHandler);
 
         mDisplayId = mContext.getDisplayId();
@@ -364,7 +373,7 @@
                 mControl.setEnforcedAdmin(
                         RestrictedLockUtilsInternal.checkIfRestrictionEnforced(mContext,
                                 UserManager.DISALLOW_CONFIG_BRIGHTNESS,
-                                mUserTracker.getCurrentUserId()));
+                                mUserTracker.getUserId()));
             }
         });
     }
@@ -440,16 +449,19 @@
     /** Factory for creating a {@link BrightnessController}. */
     public static class Factory {
         private final Context mContext;
-        private final BroadcastDispatcher mBroadcastDispatcher;
+        private final UserTracker mUserTracker;
+        private final Executor mMainExecutor;
         private final Handler mBackgroundHandler;
 
         @Inject
         public Factory(
                 Context context,
-                BroadcastDispatcher broadcastDispatcher,
+                UserTracker userTracker,
+                @Main Executor mainExecutor,
                 @Background Handler bgHandler) {
             mContext = context;
-            mBroadcastDispatcher = broadcastDispatcher;
+            mUserTracker = userTracker;
+            mMainExecutor = mainExecutor;
             mBackgroundHandler = bgHandler;
         }
 
@@ -458,7 +470,8 @@
             return new BrightnessController(
                     mContext,
                     toggleSlider,
-                    mBroadcastDispatcher,
+                    mUserTracker,
+                    mMainExecutor,
                     mBackgroundHandler);
         }
     }
diff --git a/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessDialog.java b/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessDialog.java
index d5a3954..e208be9 100644
--- a/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessDialog.java
@@ -34,10 +34,12 @@
 import com.android.internal.logging.MetricsLogger;
 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
 import com.android.systemui.R;
-import com.android.systemui.broadcast.BroadcastDispatcher;
 import com.android.systemui.dagger.qualifiers.Background;
+import com.android.systemui.dagger.qualifiers.Main;
+import com.android.systemui.settings.UserTracker;
 
 import java.util.List;
+import java.util.concurrent.Executor;
 
 import javax.inject.Inject;
 
@@ -46,16 +48,19 @@
 
     private BrightnessController mBrightnessController;
     private final BrightnessSliderController.Factory mToggleSliderFactory;
-    private final BroadcastDispatcher mBroadcastDispatcher;
+    private final UserTracker mUserTracker;
+    private final Executor mMainExecutor;
     private final Handler mBackgroundHandler;
 
     @Inject
     public BrightnessDialog(
-            BroadcastDispatcher broadcastDispatcher,
+            UserTracker userTracker,
             BrightnessSliderController.Factory factory,
+            @Main Executor mainExecutor,
             @Background Handler bgHandler) {
-        mBroadcastDispatcher = broadcastDispatcher;
+        mUserTracker = userTracker;
         mToggleSliderFactory = factory;
+        mMainExecutor = mainExecutor;
         mBackgroundHandler = bgHandler;
     }
 
@@ -101,7 +106,7 @@
         frame.addView(controller.getRootView(), MATCH_PARENT, WRAP_CONTENT);
 
         mBrightnessController = new BrightnessController(
-                this, controller, mBroadcastDispatcher, mBackgroundHandler);
+                this, controller, mUserTracker, mMainExecutor, mBackgroundHandler);
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/systemui/shade/CombinedShadeHeadersConstraintManagerImpl.kt b/packages/SystemUI/src/com/android/systemui/shade/CombinedShadeHeadersConstraintManagerImpl.kt
index 954534d..5011227 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/CombinedShadeHeadersConstraintManagerImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/CombinedShadeHeadersConstraintManagerImpl.kt
@@ -51,6 +51,8 @@
                 connect(R.id.statusIcons, ConstraintSet.START, R.id.date, ConstraintSet.END)
                 connect(R.id.privacy_container, ConstraintSet.START, R.id.date, ConstraintSet.END)
                 constrainWidth(R.id.statusIcons, ViewGroup.LayoutParams.WRAP_CONTENT)
+                constrainedWidth(R.id.date, true)
+                constrainedWidth(R.id.statusIcons, true)
             }
         )
     }
@@ -92,6 +94,8 @@
                     centerEnd,
                     ConstraintSet.END
                 )
+                constrainedWidth(R.id.date, true)
+                constrainedWidth(R.id.statusIcons, true)
             },
             qsConstraintsChanges = {
                 setGuidelineBegin(centerStart, offsetFromEdge)
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
index ceef8c8..b92cf5a 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
@@ -177,6 +177,7 @@
 import com.android.systemui.statusbar.notification.row.ActivatableNotificationView;
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
 import com.android.systemui.statusbar.notification.row.ExpandableView;
+import com.android.systemui.statusbar.notification.row.NotificationGutsManager;
 import com.android.systemui.statusbar.notification.stack.AmbientState;
 import com.android.systemui.statusbar.notification.stack.AnimationProperties;
 import com.android.systemui.statusbar.notification.stack.NotificationListContainer;
@@ -253,6 +254,8 @@
     private static final int FLING_COLLAPSE = 1;
     /** Fling until QS is completely hidden. */
     private static final int FLING_HIDE = 2;
+    /** The delay to reset the hint text when the hint animation is finished running. */
+    private static final int HINT_RESET_DELAY_MS = 1200;
     private static final long ANIMATION_DELAY_ICON_FADE_IN =
             ActivityLaunchAnimator.TIMINGS.getTotalDuration()
                     - CollapsedStatusBarFragment.FADE_IN_DURATION
@@ -343,6 +346,7 @@
     private final FalsingTapListener mFalsingTapListener = this::falsingAdditionalTapRequired;
     private final FragmentListener mQsFragmentListener = new QsFragmentListener();
     private final AccessibilityDelegate mAccessibilityDelegate = new ShadeAccessibilityDelegate();
+    private final NotificationGutsManager mGutsManager;
 
     private long mDownTime;
     private boolean mTouchSlopExceededBeforeDown;
@@ -625,7 +629,6 @@
     private float mLastGesturedOverExpansion = -1;
     /** Whether the current animator is the spring back animation. */
     private boolean mIsSpringBackAnimation;
-    private boolean mInSplitShade;
     private float mHintDistance;
     private float mInitialOffsetOnTouch;
     private boolean mCollapsedAndHeadsUpOnDown;
@@ -702,6 +705,7 @@
             ConversationNotificationManager conversationNotificationManager,
             MediaHierarchyManager mediaHierarchyManager,
             StatusBarKeyguardViewManager statusBarKeyguardViewManager,
+            NotificationGutsManager gutsManager,
             NotificationsQSContainerController notificationsQSContainerController,
             NotificationStackScrollLayoutController notificationStackScrollLayoutController,
             KeyguardStatusViewComponent.Factory keyguardStatusViewComponentFactory,
@@ -755,6 +759,7 @@
         mLockscreenGestureLogger = lockscreenGestureLogger;
         mShadeExpansionStateManager = shadeExpansionStateManager;
         mShadeLog = shadeLogger;
+        mGutsManager = gutsManager;
         mView.addOnAttachStateChangeListener(new View.OnAttachStateChangeListener() {
             @Override
             public void onViewAttachedToWindow(View v) {
@@ -1061,7 +1066,6 @@
         mSlopMultiplier = configuration.getScaledAmbiguousGestureMultiplier();
         mHintDistance = mResources.getDimension(R.dimen.hint_move_distance);
         mPanelFlingOvershootAmount = mResources.getDimension(R.dimen.panel_overshoot_amount);
-        mInSplitShade = mResources.getBoolean(R.bool.config_use_split_notification_shade);
         mFlingAnimationUtils = mFlingAnimationUtilsBuilder.get()
                 .setMaxLengthSeconds(0.4f).build();
         mStatusBarMinHeight = SystemBarUtils.getStatusBarHeight(mView.getContext());
@@ -1569,23 +1573,31 @@
                     // Find the clock, so we can exclude it from this transition.
                     FrameLayout clockContainerView =
                             mView.findViewById(R.id.lockscreen_clock_view_large);
-                    View clockView = clockContainerView.getChildAt(0);
 
-                    transition.excludeTarget(clockView, /* exclude= */ true);
+                    // The clock container can sometimes be null. If it is, just fall back to the
+                    // old animation rather than setting up the custom animations.
+                    if (clockContainerView == null || clockContainerView.getChildCount() == 0) {
+                        TransitionManager.beginDelayedTransition(
+                                mNotificationContainerParent, transition);
+                    } else {
+                        View clockView = clockContainerView.getChildAt(0);
 
-                    TransitionSet set = new TransitionSet();
-                    set.addTransition(transition);
+                        transition.excludeTarget(clockView, /* exclude= */ true);
 
-                    SplitShadeTransitionAdapter adapter =
-                            new SplitShadeTransitionAdapter(mKeyguardStatusViewController);
+                        TransitionSet set = new TransitionSet();
+                        set.addTransition(transition);
 
-                    // Use linear here, so the actual clock can pick its own interpolator.
-                    adapter.setInterpolator(Interpolators.LINEAR);
-                    adapter.setDuration(StackStateAnimator.ANIMATION_DURATION_STANDARD);
-                    adapter.addTarget(clockView);
-                    set.addTransition(adapter);
+                        SplitShadeTransitionAdapter adapter =
+                                new SplitShadeTransitionAdapter(mKeyguardStatusViewController);
 
-                    TransitionManager.beginDelayedTransition(mNotificationContainerParent, set);
+                        // Use linear here, so the actual clock can pick its own interpolator.
+                        adapter.setInterpolator(Interpolators.LINEAR);
+                        adapter.setDuration(StackStateAnimator.ANIMATION_DURATION_STANDARD);
+                        adapter.addTarget(clockView);
+                        set.addTransition(adapter);
+
+                        TransitionManager.beginDelayedTransition(mNotificationContainerParent, set);
+                    }
                 } else {
                     TransitionManager.beginDelayedTransition(
                             mNotificationContainerParent, transition);
@@ -1760,7 +1772,7 @@
     }
 
     public void resetViews(boolean animate) {
-        mCentralSurfaces.getGutsManager().closeAndSaveGuts(true /* leavebehind */, true /* force */,
+        mGutsManager.closeAndSaveGuts(true /* leavebehind */, true /* force */,
                 true /* controls */, -1 /* x */, -1 /* y */, true /* resetMenu */);
         if (animate && !isFullyCollapsed()) {
             animateCloseQs(true /* animateAway */);
@@ -1836,6 +1848,10 @@
     public void closeQs() {
         cancelQsAnimation();
         setQsExpansionHeight(mQsMinExpansionHeight);
+        // qsExpandImmediate is a safety latch in case we're calling closeQS while we're in the
+        // middle of animation - we need to make sure that value is always false when shade if
+        // fully collapsed or expanded
+        setQsExpandImmediate(false);
     }
 
     @VisibleForTesting
@@ -1938,7 +1954,7 @@
         // we want to perform an overshoot animation when flinging open
         final boolean addOverscroll =
                 expand
-                        && !mInSplitShade // Split shade has its own overscroll logic
+                        && !mSplitShadeEnabled // Split shade has its own overscroll logic
                         && mStatusBarStateController.getState() != KEYGUARD
                         && mOverExpansion == 0.0f
                         && vel >= 0;
@@ -2727,8 +2743,10 @@
      * as well based on the bounds of the shade and QS state.
      */
     private void setQSClippingBounds() {
-        final int qsPanelBottomY = calculateQsBottomPosition(computeQsExpansionFraction());
-        final boolean qsVisible = (computeQsExpansionFraction() > 0 || qsPanelBottomY > 0);
+        float qsExpansionFraction = computeQsExpansionFraction();
+        final int qsPanelBottomY = calculateQsBottomPosition(qsExpansionFraction);
+        final boolean qsVisible = (qsExpansionFraction > 0 || qsPanelBottomY > 0);
+        checkCorrectScrimVisibility(qsExpansionFraction);
 
         int top = calculateTopQsClippingBound(qsPanelBottomY);
         int bottom = calculateBottomQsClippingBound(top);
@@ -2739,6 +2757,19 @@
         applyQSClippingBounds(left, top, right, bottom, qsVisible);
     }
 
+    private void checkCorrectScrimVisibility(float expansionFraction) {
+        // issues with scrims visible on keyguard occur only in split shade
+        if (mSplitShadeEnabled) {
+            boolean keyguardViewsVisible = mBarState == KEYGUARD && mKeyguardOnlyContentAlpha == 1;
+            // expansionFraction == 1 means scrims are fully visible as their size/visibility depend
+            // on QS expansion
+            if (expansionFraction == 1 && keyguardViewsVisible) {
+                Log.wtf(TAG,
+                        "Incorrect state, scrim is visible at the same time when clock is visible");
+            }
+        }
+    }
+
     private int calculateTopQsClippingBound(int qsPanelBottomY) {
         int top;
         if (mSplitShadeEnabled) {
@@ -3686,7 +3717,6 @@
     private void onTrackingStopped(boolean expand) {
         mFalsingCollector.onTrackingStopped();
         mTracking = false;
-        mCentralSurfaces.onTrackingStopped(expand);
         updatePanelExpansionAndVisibility();
         if (expand) {
             mNotificationStackScrollLayoutController.setOverScrollAmount(0.0f, true /* onTop */,
@@ -3729,14 +3759,16 @@
 
     @VisibleForTesting
     void onUnlockHintFinished() {
-        mCentralSurfaces.onHintFinished();
+        // Delay the reset a bit so the user can read the text.
+        mKeyguardIndicationController.hideTransientIndicationDelayed(HINT_RESET_DELAY_MS);
         mScrimController.setExpansionAffectsAlpha(true);
         mNotificationStackScrollLayoutController.setUnlockHintRunning(false);
     }
 
     @VisibleForTesting
     void onUnlockHintStarted() {
-        mCentralSurfaces.onUnlockHintStarted();
+        mFalsingCollector.onUnlockHintStarted();
+        mKeyguardIndicationController.showActionToUnlock();
         mScrimController.setExpansionAffectsAlpha(false);
         mNotificationStackScrollLayoutController.setUnlockHintRunning(true);
     }
@@ -4393,7 +4425,7 @@
         ipw.print("mPanelFlingOvershootAmount="); ipw.println(mPanelFlingOvershootAmount);
         ipw.print("mLastGesturedOverExpansion="); ipw.println(mLastGesturedOverExpansion);
         ipw.print("mIsSpringBackAnimation="); ipw.println(mIsSpringBackAnimation);
-        ipw.print("mInSplitShade="); ipw.println(mInSplitShade);
+        ipw.print("mSplitShadeEnabled="); ipw.println(mSplitShadeEnabled);
         ipw.print("mHintDistance="); ipw.println(mHintDistance);
         ipw.print("mInitialOffsetOnTouch="); ipw.println(mInitialOffsetOnTouch);
         ipw.print("mCollapsedAndHeadsUpOnDown="); ipw.println(mCollapsedAndHeadsUpOnDown);
@@ -4762,7 +4794,6 @@
             }
 
             mDozeLog.traceFling(expand, mTouchAboveFalsingThreshold,
-                    mCentralSurfaces.isFalsingThresholdNeeded(),
                     mCentralSurfaces.isWakeUpComingFromTouch());
             // Log collapse gesture if on lock screen.
             if (!expand && onKeyguard) {
@@ -4811,9 +4842,6 @@
      */
     private boolean isFalseTouch(float x, float y,
             @Classifier.InteractionType int interactionType) {
-        if (!mCentralSurfaces.isFalsingThresholdNeeded()) {
-            return false;
-        }
         if (mFalsingManager.isClassifierEnabled()) {
             return mFalsingManager.isFalseTouch(interactionType);
         }
@@ -4911,7 +4939,7 @@
             float maxPanelHeight = getMaxPanelTransitionDistance();
             if (mHeightAnimator == null) {
                 // Split shade has its own overscroll logic
-                if (mTracking && !mInSplitShade) {
+                if (mTracking && !mSplitShadeEnabled) {
                     float overExpansionPixels = Math.max(0, h - maxPanelHeight);
                     setOverExpansionInternal(overExpansionPixels, true /* isFromGesture */);
                 }
@@ -5459,6 +5487,12 @@
                 //  - from SHADE to KEYGUARD
                 //  - from SHADE_LOCKED to SHADE
                 //  - getting notified again about the current SHADE or KEYGUARD state
+                if (mSplitShadeEnabled && oldState == SHADE && statusBarState == KEYGUARD) {
+                    // user can go to keyguard from different shade states and closing animation
+                    // may not fully run - we always want to make sure we close QS when that happens
+                    // as we never need QS open in fresh keyguard state
+                    closeQs();
+                }
                 final boolean animatingUnlockedShadeToKeyguard = oldState == SHADE
                         && statusBarState == KEYGUARD
                         && mScreenOffAnimationController.isKeyguardShowDelayed();
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowView.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowView.java
index 400b0ba..6acf417 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowView.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowView.java
@@ -24,6 +24,7 @@
 import android.annotation.ColorInt;
 import android.annotation.DrawableRes;
 import android.annotation.LayoutRes;
+import android.annotation.Nullable;
 import android.content.Context;
 import android.content.res.Configuration;
 import android.content.res.TypedArray;
@@ -36,6 +37,7 @@
 import android.os.Bundle;
 import android.os.Trace;
 import android.util.AttributeSet;
+import android.util.Pair;
 import android.view.ActionMode;
 import android.view.DisplayCutout;
 import android.view.InputQueue;
@@ -74,6 +76,7 @@
     private ViewTreeObserver.OnPreDrawListener mFloatingToolbarPreDrawListener;
 
     private InteractionEventHandler mInteractionEventHandler;
+    private LayoutInsetsController mLayoutInsetProvider;
 
     public NotificationShadeWindowView(Context context, AttributeSet attrs) {
         super(context, attrs);
@@ -108,12 +111,10 @@
         mLeftInset = 0;
         mRightInset = 0;
         DisplayCutout displayCutout = getRootWindowInsets().getDisplayCutout();
-        if (displayCutout != null) {
-            mLeftInset = displayCutout.getSafeInsetLeft();
-            mRightInset = displayCutout.getSafeInsetRight();
-        }
-        mLeftInset = Math.max(insets.left, mLeftInset);
-        mRightInset = Math.max(insets.right, mRightInset);
+        Pair<Integer, Integer> pairInsets = mLayoutInsetProvider
+                .getinsets(windowInsets, displayCutout);
+        mLeftInset = pairInsets.first;
+        mRightInset = pairInsets.second;
         applyMargins();
         return windowInsets;
     }
@@ -172,6 +173,10 @@
         mInteractionEventHandler = listener;
     }
 
+    protected void setLayoutInsetsController(LayoutInsetsController provider) {
+        mLayoutInsetProvider = provider;
+    }
+
     @Override
     public boolean dispatchTouchEvent(MotionEvent ev) {
         Boolean result = mInteractionEventHandler.handleDispatchTouchEvent(ev);
@@ -353,6 +358,18 @@
         }
     }
 
+    /**
+     * Controller responsible for calculating insets for the shade window.
+     */
+    public interface LayoutInsetsController {
+
+        /**
+         * Update the insets and calculate them accordingly.
+         */
+        Pair<Integer, Integer> getinsets(@Nullable WindowInsets windowInsets,
+                @Nullable DisplayCutout displayCutout);
+    }
+
     interface InteractionEventHandler {
         /**
          * Returns a result for {@link ViewGroup#dispatchTouchEvent(MotionEvent)} or null to defer
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java
index bb67280c..8379e51 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java
@@ -42,6 +42,7 @@
 import com.android.systemui.keyguard.ui.viewmodel.KeyguardBouncerViewModel;
 import com.android.systemui.statusbar.DragDownHelper;
 import com.android.systemui.statusbar.LockscreenShadeTransitionController;
+import com.android.systemui.statusbar.NotificationInsetsController;
 import com.android.systemui.statusbar.NotificationShadeDepthController;
 import com.android.systemui.statusbar.NotificationShadeWindowController;
 import com.android.systemui.statusbar.SysuiStatusBarStateController;
@@ -76,6 +77,7 @@
     private final KeyguardUnlockAnimationController mKeyguardUnlockAnimationController;
     private final AmbientState mAmbientState;
     private final PulsingGestureListener mPulsingGestureListener;
+    private final NotificationInsetsController mNotificationInsetsController;
 
     private GestureDetector mPulsingWakeupGestureHandler;
     private View mBrightnessMirror;
@@ -111,6 +113,7 @@
             CentralSurfaces centralSurfaces,
             NotificationShadeWindowController controller,
             KeyguardUnlockAnimationController keyguardUnlockAnimationController,
+            NotificationInsetsController notificationInsetsController,
             AmbientState ambientState,
             PulsingGestureListener pulsingGestureListener,
             FeatureFlags featureFlags,
@@ -134,6 +137,7 @@
         mKeyguardUnlockAnimationController = keyguardUnlockAnimationController;
         mAmbientState = ambientState;
         mPulsingGestureListener = pulsingGestureListener;
+        mNotificationInsetsController = notificationInsetsController;
 
         // This view is not part of the newly inflated expanded status bar.
         mBrightnessMirror = mView.findViewById(R.id.brightness_mirror_container);
@@ -165,6 +169,7 @@
         mPulsingWakeupGestureHandler = new GestureDetector(mView.getContext(),
                 mPulsingGestureListener);
 
+        mView.setLayoutInsetsController(mNotificationInsetsController);
         mView.setInteractionEventHandler(new NotificationShadeWindowView.InteractionEventHandler() {
             @Override
             public Boolean handleDispatchTouchEvent(MotionEvent ev) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
index 2101efb..d21f2ab 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
@@ -67,6 +67,7 @@
 import android.view.ViewGroup;
 import android.view.accessibility.AccessibilityManager;
 
+import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 
 import com.android.internal.annotations.VisibleForTesting;
@@ -74,6 +75,7 @@
 import com.android.internal.widget.LockPatternUtils;
 import com.android.keyguard.KeyguardUpdateMonitor;
 import com.android.keyguard.KeyguardUpdateMonitorCallback;
+import com.android.keyguard.TrustGrantFlags;
 import com.android.keyguard.logging.KeyguardLogger;
 import com.android.settingslib.Utils;
 import com.android.settingslib.fuelgauge.BatteryStatus;
@@ -154,7 +156,8 @@
     private final AccessibilityManager mAccessibilityManager;
     private final Handler mHandler;
 
-    protected KeyguardIndicationRotateTextViewController mRotateTextViewController;
+    @VisibleForTesting
+    public KeyguardIndicationRotateTextViewController mRotateTextViewController;
     private BroadcastReceiver mBroadcastReceiver;
     private StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
 
@@ -195,7 +198,9 @@
         public void onScreenTurnedOn() {
             mHandler.removeMessages(MSG_RESET_ERROR_MESSAGE_ON_SCREEN_ON);
             if (mBiometricErrorMessageToShowOnScreenOn != null) {
-                showBiometricMessage(mBiometricErrorMessageToShowOnScreenOn);
+                String followUpMessage = mFaceLockedOutThisAuthSession
+                        ? faceLockedOutFollowupMessage() : null;
+                showBiometricMessage(mBiometricErrorMessageToShowOnScreenOn, followUpMessage);
                 // We want to keep this message around in case the screen was off
                 hideBiometricMessageDelayed(DEFAULT_HIDE_DELAY_MS);
                 mBiometricErrorMessageToShowOnScreenOn = null;
@@ -1188,9 +1193,9 @@
         }
 
         @Override
-        public void onTrustGrantedWithFlags(int flags, int userId, @Nullable String message) {
-            if (!isCurrentUser(userId)) return;
-            showTrustGrantedMessage(flags, message);
+        public void onTrustGrantedForCurrentUser(boolean dismissKeyguard,
+                @NonNull TrustGrantFlags flags, @Nullable String message) {
+            showTrustGrantedMessage(dismissKeyguard, message);
         }
 
         @Override
@@ -1254,15 +1259,13 @@
         return getCurrentUser() == userId;
     }
 
-    void showTrustGrantedMessage(int flags, @Nullable CharSequence message) {
+    protected void showTrustGrantedMessage(boolean dismissKeyguard, @Nullable String message) {
         mTrustGrantedIndication = message;
         updateDeviceEntryIndication(false);
     }
 
     private void handleFaceLockoutError(String errString) {
-        int followupMsgId = canUnlockWithFingerprint() ? R.string.keyguard_suggest_fingerprint
-                : R.string.keyguard_unlock;
-        String followupMessage = mContext.getString(followupMsgId);
+        String followupMessage = faceLockedOutFollowupMessage();
         // Lockout error can happen multiple times in a session because we trigger face auth
         // even when it is locked out so that the user is aware that face unlock would have
         // triggered but didn't because it is locked out.
@@ -1280,6 +1283,12 @@
         }
     }
 
+    private String faceLockedOutFollowupMessage() {
+        int followupMsgId = canUnlockWithFingerprint() ? R.string.keyguard_suggest_fingerprint
+                : R.string.keyguard_unlock;
+        return mContext.getString(followupMsgId);
+    }
+
     private static boolean isLockoutError(int msgId) {
         return msgId == FaceManager.FACE_ERROR_LOCKOUT_PERMANENT
                 || msgId == FaceManager.FACE_ERROR_LOCKOUT;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationInsetsController.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationInsetsController.java
new file mode 100644
index 0000000..39d7d66
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationInsetsController.java
@@ -0,0 +1,26 @@
+/*
+ * 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.statusbar;
+
+import com.android.systemui.shade.NotificationShadeWindowView;
+
+/**
+ * Calculates insets for the notification shade window view.
+ */
+public abstract class NotificationInsetsController
+        implements NotificationShadeWindowView.LayoutInsetsController {
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationInsetsImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationInsetsImpl.java
new file mode 100644
index 0000000..1ed704e
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationInsetsImpl.java
@@ -0,0 +1,58 @@
+/*
+ * 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.statusbar;
+
+import static android.view.WindowInsets.Type.systemBars;
+
+import android.annotation.Nullable;
+import android.graphics.Insets;
+import android.util.Pair;
+import android.view.DisplayCutout;
+import android.view.WindowInsets;
+
+import com.android.systemui.dagger.SysUISingleton;
+
+import javax.inject.Inject;
+
+/**
+ * Default implementation of NotificationsInsetsController.
+ */
+@SysUISingleton
+public class NotificationInsetsImpl extends NotificationInsetsController {
+
+    @Inject
+    public NotificationInsetsImpl() {
+
+    }
+
+    @Override
+    public Pair<Integer, Integer> getinsets(@Nullable WindowInsets windowInsets,
+            @Nullable DisplayCutout displayCutout) {
+        final Insets insets = windowInsets.getInsetsIgnoringVisibility(systemBars());
+        int leftInset = 0;
+        int rightInset = 0;
+
+        if (displayCutout != null) {
+            leftInset = displayCutout.getSafeInsetLeft();
+            rightInset = displayCutout.getSafeInsetRight();
+        }
+        leftInset = Math.max(insets.left, leftInset);
+        rightInset = Math.max(insets.right, rightInset);
+
+        return new Pair(leftInset, rightInset);
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationInsetsModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationInsetsModule.java
new file mode 100644
index 0000000..614bc0f
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationInsetsModule.java
@@ -0,0 +1,30 @@
+/*
+ * 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.systemui.statusbar;
+
+import com.android.systemui.dagger.SysUISingleton;
+
+import dagger.Binds;
+import dagger.Module;
+
+@Module
+public interface NotificationInsetsModule {
+
+    @Binds
+    @SysUISingleton
+    NotificationInsetsController bindNotificationInsetsController(NotificationInsetsImpl impl);
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java
index 184dc25..cdefae6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java
@@ -19,7 +19,6 @@
 
 import static com.android.systemui.DejankUtils.whitelistIpcs;
 
-import android.app.ActivityManager;
 import android.app.KeyguardManager;
 import android.app.Notification;
 import android.app.admin.DevicePolicyManager;
@@ -50,6 +49,7 @@
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.plugins.statusbar.StatusBarStateController.StateListener;
 import com.android.systemui.recents.OverviewProxyService;
+import com.android.systemui.settings.UserTracker;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 import com.android.systemui.statusbar.notification.collection.notifcollection.CommonNotifCollection;
 import com.android.systemui.statusbar.notification.collection.render.NotificationVisibilityProvider;
@@ -93,6 +93,7 @@
     private final SparseBooleanArray mUsersInLockdownLatestResult = new SparseBooleanArray();
     private final SparseBooleanArray mShouldHideNotifsLatestResult = new SparseBooleanArray();
     private final UserManager mUserManager;
+    private final UserTracker mUserTracker;
     private final List<UserChangedListener> mListeners = new ArrayList<>();
     private final BroadcastDispatcher mBroadcastDispatcher;
     private final NotificationClickNotifier mClickNotifier;
@@ -195,6 +196,7 @@
             BroadcastDispatcher broadcastDispatcher,
             DevicePolicyManager devicePolicyManager,
             UserManager userManager,
+            UserTracker userTracker,
             Lazy<NotificationVisibilityProvider> visibilityProviderLazy,
             Lazy<CommonNotifCollection> commonNotifCollectionLazy,
             NotificationClickNotifier clickNotifier,
@@ -210,7 +212,8 @@
         mMainHandler = mainHandler;
         mDevicePolicyManager = devicePolicyManager;
         mUserManager = userManager;
-        mCurrentUserId = ActivityManager.getCurrentUser();
+        mUserTracker = userTracker;
+        mCurrentUserId = mUserTracker.getUserId();
         mVisibilityProviderLazy = visibilityProviderLazy;
         mCommonNotifCollectionLazy = commonNotifCollectionLazy;
         mClickNotifier = clickNotifier;
@@ -295,7 +298,7 @@
         mContext.registerReceiver(mBaseBroadcastReceiver, internalFilter, PERMISSION_SELF, null,
                 Context.RECEIVER_EXPORTED_UNAUDITED);
 
-        mCurrentUserId = ActivityManager.getCurrentUser(); // in case we reg'd receiver too late
+        mCurrentUserId = mUserTracker.getUserId(); // in case we reg'd receiver too late
         updateCurrentProfilesCache();
 
         mSettingsObserver.onChange(false);  // set up
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java
index 815b86e..d7eddf5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java
@@ -71,6 +71,7 @@
     private int[] mTmp = new int[2];
     private boolean mHideBackground;
     private int mStatusBarHeight;
+    private boolean mEnableNotificationClipping;
     private AmbientState mAmbientState;
     private NotificationStackScrollLayoutController mHostLayoutController;
     private int mPaddingBetweenElements;
@@ -117,7 +118,7 @@
         // Setting this to first in section to get the clipping to the top roundness correct. This
         // value determines the way we are clipping to the top roundness of the overall shade
         setFirstInSection(true);
-        initDimens();
+        updateResources();
     }
 
     public void bind(AmbientState ambientState,
@@ -126,14 +127,17 @@
         mHostLayoutController = hostLayoutController;
     }
 
-    private void initDimens() {
+    private void updateResources() {
         Resources res = getResources();
         mStatusBarHeight = SystemBarUtils.getStatusBarHeight(mContext);
         mPaddingBetweenElements = res.getDimensionPixelSize(R.dimen.notification_divider_height);
 
         ViewGroup.LayoutParams layoutParams = getLayoutParams();
-        layoutParams.height = res.getDimensionPixelOffset(R.dimen.notification_shelf_height);
-        setLayoutParams(layoutParams);
+        final int newShelfHeight = res.getDimensionPixelOffset(R.dimen.notification_shelf_height);
+        if (newShelfHeight != layoutParams.height) {
+            layoutParams.height = newShelfHeight;
+            setLayoutParams(layoutParams);
+        }
 
         final int padding = res.getDimensionPixelOffset(R.dimen.shelf_icon_container_padding);
         mShelfIcons.setPadding(padding, 0, padding, 0);
@@ -141,6 +145,7 @@
         mShowNotificationShelf = res.getBoolean(R.bool.config_showNotificationShelf);
         mCornerAnimationDistance = res.getDimensionPixelSize(
                 R.dimen.notification_corner_animation_distance);
+        mEnableNotificationClipping = res.getBoolean(R.bool.notification_enable_clipping);
 
         mShelfIcons.setInNotificationIconShelf(true);
         if (!mShowNotificationShelf) {
@@ -151,7 +156,7 @@
     @Override
     protected void onConfigurationChanged(Configuration newConfig) {
         super.onConfigurationChanged(newConfig);
-        initDimens();
+        updateResources();
     }
 
     @Override
@@ -636,7 +641,8 @@
         }
         if (!isPinned) {
             if (viewEnd > notificationClipEnd && !shouldClipOwnTop) {
-                int clipBottomAmount = (int) (viewEnd - notificationClipEnd);
+                int clipBottomAmount =
+                        mEnableNotificationClipping ? (int) (viewEnd - notificationClipEnd) : 0;
                 view.setClipBottomAmount(clipBottomAmount);
             } else {
                 view.setClipBottomAmount(0);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/NetworkControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/NetworkControllerImpl.java
index 73d6483..99ff06a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/NetworkControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/NetworkControllerImpl.java
@@ -39,6 +39,7 @@
 import android.os.AsyncTask;
 import android.os.Bundle;
 import android.os.Handler;
+import android.os.HandlerExecutor;
 import android.os.Looper;
 import android.provider.Settings;
 import android.telephony.CarrierConfigManager;
@@ -76,7 +77,7 @@
 import com.android.systemui.plugins.log.LogBuffer;
 import com.android.systemui.plugins.log.LogLevel;
 import com.android.systemui.qs.tiles.dialog.InternetDialogFactory;
-import com.android.systemui.settings.CurrentUserTracker;
+import com.android.systemui.settings.UserTracker;
 import com.android.systemui.statusbar.policy.ConfigurationController;
 import com.android.systemui.statusbar.policy.DataSaverController;
 import com.android.systemui.statusbar.policy.DataSaverControllerImpl;
@@ -128,7 +129,7 @@
     private final boolean mHasMobileDataFeature;
     private final SubscriptionDefaults mSubDefaults;
     private final DataSaverController mDataSaverController;
-    private final CurrentUserTracker mUserTracker;
+    private final UserTracker mUserTracker;
     private final BroadcastDispatcher mBroadcastDispatcher;
     private final DemoModeController mDemoModeController;
     private final Object mLock = new Object();
@@ -212,6 +213,14 @@
                 }
             };
 
+    private final UserTracker.Callback mUserChangedCallback =
+            new UserTracker.Callback() {
+                @Override
+                public void onUserChanged(int newUser, @NonNull Context userContext) {
+                    NetworkControllerImpl.this.onUserSwitched(newUser);
+                }
+            };
+
     /**
      * Construct this controller object and register for updates.
      */
@@ -224,6 +233,7 @@
             CallbackHandler callbackHandler,
             DeviceProvisionedController deviceProvisionedController,
             BroadcastDispatcher broadcastDispatcher,
+            UserTracker userTracker,
             ConnectivityManager connectivityManager,
             TelephonyManager telephonyManager,
             TelephonyListenerManager telephonyListenerManager,
@@ -251,6 +261,7 @@
                 new SubscriptionDefaults(),
                 deviceProvisionedController,
                 broadcastDispatcher,
+                userTracker,
                 demoModeController,
                 carrierConfigTracker,
                 trackerFactory,
@@ -277,6 +288,7 @@
             SubscriptionDefaults defaultsHandler,
             DeviceProvisionedController deviceProvisionedController,
             BroadcastDispatcher broadcastDispatcher,
+            UserTracker userTracker,
             DemoModeController demoModeController,
             CarrierConfigTracker carrierConfigTracker,
             WifiStatusTrackerFactory trackerFactory,
@@ -333,13 +345,9 @@
 
         // AIRPLANE_MODE_CHANGED is sent at boot; we've probably already missed it
         updateAirplaneMode(true /* force callback */);
-        mUserTracker = new CurrentUserTracker(broadcastDispatcher) {
-            @Override
-            public void onUserSwitched(int newUserId) {
-                NetworkControllerImpl.this.onUserSwitched(newUserId);
-            }
-        };
-        mUserTracker.startTracking();
+        mUserTracker = userTracker;
+        mUserTracker.addCallback(mUserChangedCallback, new HandlerExecutor(mMainHandler));
+
         deviceProvisionedController.addCallback(new DeviceProvisionedListener() {
             @Override
             public void onUserSetupChanged() {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceController.kt
index 5873837..6bd9502 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceController.kt
@@ -235,19 +235,24 @@
 
         ssView.setIntentStarter(object : BcSmartspaceDataPlugin.IntentStarter {
             override fun startIntent(view: View, intent: Intent, showOnLockscreen: Boolean) {
-                activityStarter.startActivity(
-                    intent,
-                    true, /* dismissShade */
-                    null, /* launch animator - looks bad with the transparent smartspace bg */
-                    showOnLockscreen
-                )
+                if (showOnLockscreen) {
+                    activityStarter.startActivity(
+                            intent,
+                            true, /* dismissShade */
+                            // launch animator - looks bad with the transparent smartspace bg
+                            null,
+                            true
+                    )
+                } else {
+                    activityStarter.postStartActivityDismissingKeyguard(intent, 0)
+                }
             }
 
             override fun startPendingIntent(pi: PendingIntent, showOnLockscreen: Boolean) {
                 if (showOnLockscreen) {
                     pi.send()
                 } else {
-                    activityStarter.startPendingIntentDismissingKeyguard(pi)
+                    activityStarter.postStartActivityDismissingKeyguard(pi)
                 }
             }
         })
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotifPipelineFlags.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotifPipelineFlags.kt
index 2734511..7eb8906 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotifPipelineFlags.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotifPipelineFlags.kt
@@ -40,4 +40,8 @@
     val isSemiStableSortEnabled: Boolean by lazy {
         featureFlags.isEnabled(Flags.SEMI_STABLE_SORT)
     }
+
+    val shouldFilterUnseenNotifsOnKeyguard: Boolean by lazy {
+        featureFlags.isEnabled(Flags.FILTER_UNSEEN_NOTIFS_ON_KEYGUARD)
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinator.java
deleted file mode 100644
index e3d71c8..0000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinator.java
+++ /dev/null
@@ -1,88 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.statusbar.notification.collection.coordinator;
-
-import androidx.annotation.NonNull;
-
-import com.android.systemui.plugins.statusbar.StatusBarStateController;
-import com.android.systemui.statusbar.StatusBarState;
-import com.android.systemui.statusbar.notification.collection.NotifPipeline;
-import com.android.systemui.statusbar.notification.collection.NotificationEntry;
-import com.android.systemui.statusbar.notification.collection.coordinator.dagger.CoordinatorScope;
-import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifFilter;
-import com.android.systemui.statusbar.notification.collection.provider.SectionHeaderVisibilityProvider;
-import com.android.systemui.statusbar.notification.interruption.KeyguardNotificationVisibilityProvider;
-
-import javax.inject.Inject;
-
-/**
- * Filters low priority and privacy-sensitive notifications from the lockscreen, and hides section
- * headers on the lockscreen.
- */
-@CoordinatorScope
-public class KeyguardCoordinator implements Coordinator {
-    private static final String TAG = "KeyguardCoordinator";
-    private final KeyguardNotificationVisibilityProvider mKeyguardNotificationVisibilityProvider;
-    private final SectionHeaderVisibilityProvider mSectionHeaderVisibilityProvider;
-    private final StatusBarStateController mStatusBarStateController;
-
-    @Inject
-    public KeyguardCoordinator(
-            KeyguardNotificationVisibilityProvider keyguardNotificationVisibilityProvider,
-            SectionHeaderVisibilityProvider sectionHeaderVisibilityProvider,
-            StatusBarStateController statusBarStateController) {
-        mKeyguardNotificationVisibilityProvider = keyguardNotificationVisibilityProvider;
-        mSectionHeaderVisibilityProvider = sectionHeaderVisibilityProvider;
-        mStatusBarStateController = statusBarStateController;
-    }
-
-    @Override
-    public void attach(NotifPipeline pipeline) {
-
-        setupInvalidateNotifListCallbacks();
-        // Filter at the "finalize" stage so that views remain bound by PreparationCoordinator
-        pipeline.addFinalizeFilter(mNotifFilter);
-        mKeyguardNotificationVisibilityProvider
-                .addOnStateChangedListener(this::invalidateListFromFilter);
-        updateSectionHeadersVisibility();
-    }
-
-    private final NotifFilter mNotifFilter = new NotifFilter(TAG) {
-        @Override
-        public boolean shouldFilterOut(@NonNull NotificationEntry entry, long now) {
-            return mKeyguardNotificationVisibilityProvider.shouldHideNotification(entry);
-        }
-    };
-
-    // TODO(b/206118999): merge this class with SensitiveContentCoordinator which also depends on
-    // these same updates
-    private void setupInvalidateNotifListCallbacks() {
-
-    }
-
-    private void invalidateListFromFilter(String reason) {
-        updateSectionHeadersVisibility();
-        mNotifFilter.invalidateList(reason);
-    }
-
-    private void updateSectionHeadersVisibility() {
-        boolean onKeyguard = mStatusBarStateController.getState() == StatusBarState.KEYGUARD;
-        boolean neverShowSections = mSectionHeaderVisibilityProvider.getNeverShowSectionHeaders();
-        boolean showSections = !onKeyguard && !neverShowSections;
-        mSectionHeaderVisibilityProvider.setSectionHeadersVisible(showSections);
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinator.kt
new file mode 100644
index 0000000..6e5fceb
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinator.kt
@@ -0,0 +1,147 @@
+/*
+ * 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.statusbar.notification.collection.coordinator
+
+import androidx.annotation.VisibleForTesting
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.keyguard.data.repository.KeyguardRepository
+import com.android.systemui.plugins.statusbar.StatusBarStateController
+import com.android.systemui.statusbar.StatusBarState
+import com.android.systemui.statusbar.notification.NotifPipelineFlags
+import com.android.systemui.statusbar.notification.collection.NotifPipeline
+import com.android.systemui.statusbar.notification.collection.NotificationEntry
+import com.android.systemui.statusbar.notification.collection.coordinator.dagger.CoordinatorScope
+import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifFilter
+import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener
+import com.android.systemui.statusbar.notification.collection.provider.SectionHeaderVisibilityProvider
+import com.android.systemui.statusbar.notification.interruption.KeyguardNotificationVisibilityProvider
+import javax.inject.Inject
+import kotlin.time.Duration.Companion.seconds
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.delay
+import kotlinx.coroutines.flow.collectLatest
+import kotlinx.coroutines.launch
+
+/**
+ * Filters low priority and privacy-sensitive notifications from the lockscreen, and hides section
+ * headers on the lockscreen.
+ */
+@CoordinatorScope
+class KeyguardCoordinator
+@Inject
+constructor(
+    private val keyguardNotificationVisibilityProvider: KeyguardNotificationVisibilityProvider,
+    private val keyguardRepository: KeyguardRepository,
+    private val notifPipelineFlags: NotifPipelineFlags,
+    @Application private val scope: CoroutineScope,
+    private val sectionHeaderVisibilityProvider: SectionHeaderVisibilityProvider,
+    private val statusBarStateController: StatusBarStateController,
+) : Coordinator {
+
+    private val unseenNotifications = mutableSetOf<NotificationEntry>()
+
+    override fun attach(pipeline: NotifPipeline) {
+        setupInvalidateNotifListCallbacks()
+        // Filter at the "finalize" stage so that views remain bound by PreparationCoordinator
+        pipeline.addFinalizeFilter(notifFilter)
+        keyguardNotificationVisibilityProvider.addOnStateChangedListener(::invalidateListFromFilter)
+        updateSectionHeadersVisibility()
+        if (notifPipelineFlags.shouldFilterUnseenNotifsOnKeyguard) {
+            attachUnseenFilter(pipeline)
+        }
+    }
+
+    private fun attachUnseenFilter(pipeline: NotifPipeline) {
+        pipeline.addFinalizeFilter(unseenNotifFilter)
+        pipeline.addCollectionListener(collectionListener)
+        scope.launch { clearUnseenWhenKeyguardIsDismissed() }
+    }
+
+    private suspend fun clearUnseenWhenKeyguardIsDismissed() {
+        // Use collectLatest so that the suspending block is cancelled if isKeyguardShowing changes
+        // during the timeout period
+        keyguardRepository.isKeyguardShowing.collectLatest { isKeyguardShowing ->
+            if (!isKeyguardShowing) {
+                unseenNotifFilter.invalidateList("keyguard no longer showing")
+                delay(SEEN_TIMEOUT)
+                unseenNotifications.clear()
+            }
+        }
+    }
+
+    private val collectionListener =
+        object : NotifCollectionListener {
+            override fun onEntryAdded(entry: NotificationEntry) {
+                if (keyguardRepository.isKeyguardShowing()) {
+                    unseenNotifications.add(entry)
+                }
+            }
+
+            override fun onEntryUpdated(entry: NotificationEntry) {
+                if (keyguardRepository.isKeyguardShowing()) {
+                    unseenNotifications.add(entry)
+                }
+            }
+
+            override fun onEntryRemoved(entry: NotificationEntry, reason: Int) {
+                unseenNotifications.remove(entry)
+            }
+        }
+
+    @VisibleForTesting
+    internal val unseenNotifFilter =
+        object : NotifFilter("$TAG-unseen") {
+            override fun shouldFilterOut(entry: NotificationEntry, now: Long): Boolean =
+                when {
+                    // Don't apply filter if the keyguard isn't currently showing
+                    !keyguardRepository.isKeyguardShowing() -> false
+                    // Don't apply the filter if the notification is unseen
+                    unseenNotifications.contains(entry) -> false
+                    // Don't apply the filter to (non-promoted) group summaries
+                    //  - summary will be pruned if necessary, depending on if children are filtered
+                    entry.parent?.summary == entry -> false
+                    else -> true
+                }
+        }
+
+    private val notifFilter: NotifFilter =
+        object : NotifFilter(TAG) {
+            override fun shouldFilterOut(entry: NotificationEntry, now: Long): Boolean =
+                keyguardNotificationVisibilityProvider.shouldHideNotification(entry)
+        }
+
+    // TODO(b/206118999): merge this class with SensitiveContentCoordinator which also depends on
+    //  these same updates
+    private fun setupInvalidateNotifListCallbacks() {}
+
+    private fun invalidateListFromFilter(reason: String) {
+        updateSectionHeadersVisibility()
+        notifFilter.invalidateList(reason)
+    }
+
+    private fun updateSectionHeadersVisibility() {
+        val onKeyguard = statusBarStateController.state == StatusBarState.KEYGUARD
+        val neverShowSections = sectionHeaderVisibilityProvider.neverShowSectionHeaders
+        val showSections = !onKeyguard && !neverShowSections
+        sectionHeaderVisibilityProvider.sectionHeadersVisible = showSections
+    }
+
+    companion object {
+        private const val TAG = "KeyguardCoordinator"
+        private val SEEN_TIMEOUT = 5.seconds
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinator.java
index 3002a68..a2379b2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinator.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinator.java
@@ -29,6 +29,7 @@
 import com.android.systemui.keyguard.WakefulnessLifecycle;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.shade.ShadeStateEvents;
+import com.android.systemui.statusbar.notification.VisibilityLocationProvider;
 import com.android.systemui.statusbar.notification.collection.GroupEntry;
 import com.android.systemui.statusbar.notification.collection.ListEntry;
 import com.android.systemui.statusbar.notification.collection.NotifPipeline;
@@ -62,6 +63,7 @@
     private final HeadsUpManager mHeadsUpManager;
     private final ShadeStateEvents mShadeStateEvents;
     private final StatusBarStateController mStatusBarStateController;
+    private final VisibilityLocationProvider mVisibilityLocationProvider;
     private final VisualStabilityProvider mVisualStabilityProvider;
     private final WakefulnessLifecycle mWakefulnessLifecycle;
 
@@ -94,9 +96,11 @@
             HeadsUpManager headsUpManager,
             ShadeStateEvents shadeStateEvents,
             StatusBarStateController statusBarStateController,
+            VisibilityLocationProvider visibilityLocationProvider,
             VisualStabilityProvider visualStabilityProvider,
             WakefulnessLifecycle wakefulnessLifecycle) {
         mHeadsUpManager = headsUpManager;
+        mVisibilityLocationProvider = visibilityLocationProvider;
         mVisualStabilityProvider = visualStabilityProvider;
         mWakefulnessLifecycle = wakefulnessLifecycle;
         mStatusBarStateController = statusBarStateController;
@@ -123,6 +127,11 @@
     //  HUNs to the top of the shade
     private final NotifStabilityManager mNotifStabilityManager =
             new NotifStabilityManager("VisualStabilityCoordinator") {
+                private boolean canMoveForHeadsUp(NotificationEntry entry) {
+                    return entry != null && mHeadsUpManager.isAlerting(entry.getKey())
+                            && !mVisibilityLocationProvider.isInVisibleLocation(entry);
+                }
+
                 @Override
                 public void onBeginRun() {
                     mIsSuppressingPipelineRun = false;
@@ -140,7 +149,7 @@
                 @Override
                 public boolean isGroupChangeAllowed(@NonNull NotificationEntry entry) {
                     final boolean isGroupChangeAllowedForEntry =
-                            mReorderingAllowed || mHeadsUpManager.isAlerting(entry.getKey());
+                            mReorderingAllowed || canMoveForHeadsUp(entry);
                     mIsSuppressingGroupChange |= !isGroupChangeAllowedForEntry;
                     return isGroupChangeAllowedForEntry;
                 }
@@ -156,7 +165,7 @@
                 public boolean isSectionChangeAllowed(@NonNull NotificationEntry entry) {
                     final boolean isSectionChangeAllowedForEntry =
                             mReorderingAllowed
-                                    || mHeadsUpManager.isAlerting(entry.getKey())
+                                    || canMoveForHeadsUp(entry)
                                     || mEntriesThatCanChangeSection.containsKey(entry.getKey());
                     if (!isSectionChangeAllowedForEntry) {
                         mEntriesWithSuppressedSectionChange.add(entry.getKey());
@@ -165,8 +174,8 @@
                 }
 
                 @Override
-                public boolean isEntryReorderingAllowed(@NonNull ListEntry section) {
-                    return mReorderingAllowed;
+                public boolean isEntryReorderingAllowed(@NonNull ListEntry entry) {
+                    return mReorderingAllowed || canMoveForHeadsUp(entry.getRepresentativeEntry());
                 }
 
                 @Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/provider/VisibilityLocationProviderDelegator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/provider/VisibilityLocationProviderDelegator.kt
new file mode 100644
index 0000000..4bc4ecf
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/provider/VisibilityLocationProviderDelegator.kt
@@ -0,0 +1,38 @@
+/*
+ * 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.statusbar.notification.collection.provider
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.statusbar.notification.VisibilityLocationProvider
+import com.android.systemui.statusbar.notification.collection.NotificationEntry
+import javax.inject.Inject
+
+/**
+ * An injectable component which delegates the visibility location computation to a delegate which
+ * can be initialized after the initial injection, generally because it's provided by a view.
+ */
+@SysUISingleton
+class VisibilityLocationProviderDelegator @Inject constructor() : VisibilityLocationProvider {
+    private var delegate: VisibilityLocationProvider? = null
+
+    fun setDelegate(provider: VisibilityLocationProvider) {
+        delegate = provider
+    }
+
+    override fun isInVisibleLocation(entry: NotificationEntry): Boolean =
+        requireNotNull(this.delegate) { "delegate not initialized" }.isInVisibleLocation(entry)
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java
index df2de56..a7b7a23 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java
@@ -37,6 +37,7 @@
 import com.android.systemui.shade.ShadeExpansionStateManager;
 import com.android.systemui.statusbar.NotificationListener;
 import com.android.systemui.statusbar.notification.AssistantFeedbackController;
+import com.android.systemui.statusbar.notification.VisibilityLocationProvider;
 import com.android.systemui.statusbar.notification.collection.NotifInflaterImpl;
 import com.android.systemui.statusbar.notification.collection.NotifLiveDataStore;
 import com.android.systemui.statusbar.notification.collection.NotifLiveDataStoreImpl;
@@ -51,6 +52,7 @@
 import com.android.systemui.statusbar.notification.collection.notifcollection.CommonNotifCollection;
 import com.android.systemui.statusbar.notification.collection.provider.HighPriorityProvider;
 import com.android.systemui.statusbar.notification.collection.provider.NotificationVisibilityProviderImpl;
+import com.android.systemui.statusbar.notification.collection.provider.VisibilityLocationProviderDelegator;
 import com.android.systemui.statusbar.notification.collection.render.GroupExpansionManager;
 import com.android.systemui.statusbar.notification.collection.render.GroupExpansionManagerImpl;
 import com.android.systemui.statusbar.notification.collection.render.GroupMembershipManager;
@@ -151,6 +153,11 @@
     @Binds
     NotifGutsViewManager bindNotifGutsViewManager(NotificationGutsManager notificationGutsManager);
 
+    /** Provides an instance of {@link VisibilityLocationProvider} */
+    @Binds
+    VisibilityLocationProvider bindVisibilityLocationProvider(
+            VisibilityLocationProviderDelegator visibilityLocationProviderDelegator);
+
     /** Provides an instance of {@link NotificationLogger} */
     @SysUISingleton
     @Provides
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationLogger.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationLogger.java
index 58f59be..5f6a5cb 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationLogger.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationLogger.java
@@ -53,6 +53,7 @@
 import java.util.Collections;
 import java.util.List;
 import java.util.Map;
+import java.util.Objects;
 import java.util.concurrent.Executor;
 
 import javax.inject.Inject;
@@ -534,7 +535,7 @@
                 return;
             }
             if (loggedExpansionState != null
-                    && state.mIsExpanded == loggedExpansionState) {
+                    && Objects.equals(state.mIsExpanded, loggedExpansionState)) {
                 return;
             }
             mLoggedExpansionState.put(key, state.mIsExpanded);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
index e1337826..0240bbc 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
@@ -89,6 +89,7 @@
 import com.android.systemui.statusbar.notification.collection.PipelineDumper;
 import com.android.systemui.statusbar.notification.collection.notifcollection.DismissedByUserStats;
 import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener;
+import com.android.systemui.statusbar.notification.collection.provider.VisibilityLocationProviderDelegator;
 import com.android.systemui.statusbar.notification.collection.render.GroupExpansionManager;
 import com.android.systemui.statusbar.notification.collection.render.NotifStackController;
 import com.android.systemui.statusbar.notification.collection.render.NotifStats;
@@ -157,6 +158,7 @@
     private final NotifCollection mNotifCollection;
     private final UiEventLogger mUiEventLogger;
     private final NotificationRemoteInputManager mRemoteInputManager;
+    private final VisibilityLocationProviderDelegator mVisibilityLocationProviderDelegator;
     private final ShadeController mShadeController;
     private final KeyguardMediaController mKeyguardMediaController;
     private final SysuiStatusBarStateController mStatusBarStateController;
@@ -638,6 +640,7 @@
             ShadeTransitionController shadeTransitionController,
             UiEventLogger uiEventLogger,
             NotificationRemoteInputManager remoteInputManager,
+            VisibilityLocationProviderDelegator visibilityLocationProviderDelegator,
             ShadeController shadeController,
             InteractionJankMonitor jankMonitor,
             StackStateLogger stackLogger,
@@ -679,6 +682,7 @@
         mNotifCollection = notifCollection;
         mUiEventLogger = uiEventLogger;
         mRemoteInputManager = remoteInputManager;
+        mVisibilityLocationProviderDelegator = visibilityLocationProviderDelegator;
         mShadeController = shadeController;
         mFeatureFlags = featureFlags;
         mNotificationTargetsHelper = notificationTargetsHelper;
@@ -750,6 +754,8 @@
         mNotificationRoundnessManager.setOnRoundingChangedCallback(mView::invalidate);
         mView.addOnExpandedHeightChangedListener(mNotificationRoundnessManager::setExpanded);
 
+        mVisibilityLocationProviderDelegator.setDelegate(this::isInVisibleLocation);
+
         mTunerService.addTunable(
                 (key, newValue) -> {
                     switch (key) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java
index eea1d911..62f57b8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java
@@ -57,6 +57,7 @@
     private float mGapHeight;
     private float mGapHeightOnLockscreen;
     private int mCollapsedSize;
+    private boolean mEnableNotificationClipping;
 
     private StackScrollAlgorithmState mTempAlgorithmState = new StackScrollAlgorithmState();
     private boolean mIsExpanded;
@@ -85,6 +86,7 @@
         mPaddingBetweenElements = res.getDimensionPixelSize(
                 R.dimen.notification_divider_height);
         mCollapsedSize = res.getDimensionPixelSize(R.dimen.notification_min_height);
+        mEnableNotificationClipping = res.getBoolean(R.bool.notification_enable_clipping);
         mClipNotificationScrollToTop = res.getBoolean(R.bool.config_clipNotificationScrollToTop);
         int statusBarHeight = SystemBarUtils.getStatusBarHeight(context);
         mHeadsUpInset = statusBarHeight + res.getDimensionPixelSize(
@@ -289,7 +291,7 @@
                 // The bottom of this view is peeking out from under the previous view.
                 // Clip the part that is peeking out.
                 float overlapAmount = newNotificationEnd - firstHeadsUpEnd;
-                state.clipBottomAmount = (int) overlapAmount;
+                state.clipBottomAmount = mEnableNotificationClipping ? (int) overlapAmount : 0;
             } else {
                 state.clipBottomAmount = 0;
             }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java
index 1ab9be7..be08183 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java
@@ -54,7 +54,6 @@
 import com.android.systemui.statusbar.GestureRecorder;
 import com.android.systemui.statusbar.LightRevealScrim;
 import com.android.systemui.statusbar.NotificationPresenter;
-import com.android.systemui.statusbar.notification.row.NotificationGutsManager;
 
 import java.io.PrintWriter;
 
@@ -254,8 +253,6 @@
 
     boolean isWakeUpComingFromTouch();
 
-    boolean isFalsingThresholdNeeded();
-
     void onKeyguardViewManagerStatesUpdated();
 
     ViewGroup getNotificationScrollLayout();
@@ -413,12 +410,6 @@
 
     void onClosingFinished();
 
-    void onUnlockHintStarted();
-
-    void onHintFinished();
-
-    void onTrackingStopped(boolean expand);
-
     // TODO: Figure out way to remove these.
     NavigationBarView getNavigationBarView();
 
@@ -500,8 +491,6 @@
 
     boolean isKeyguardSecure();
 
-    NotificationGutsManager getGutsManager();
-
     void updateNotificationPanelTouchState();
 
     void makeExpandedVisible(boolean force);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
index d772378..4562e69 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
@@ -279,6 +279,7 @@
     // 1020-1040 reserved for BaseStatusBar
 
     /**
+     * TODO(b/249277686) delete this
      * The delay to reset the hint text when the hint animation is finished running.
      */
     private static final int HINT_RESET_DELAY_MS = 1200;
@@ -1131,7 +1132,6 @@
         // into fragments, but the rest here, it leaves some awkward lifecycle and whatnot.
         mNotificationIconAreaController.setupShelf(mNotificationShelfController);
         mShadeExpansionStateManager.addExpansionListener(mWakeUpCoordinator);
-        mUserSwitcherController.init(mNotificationShadeWindowView);
 
         // Allow plugins to reference DarkIconDispatcher and StatusBarStateController
         mPluginDependencyProvider.allowPluginDependency(DarkIconDispatcher.class);
@@ -1785,11 +1785,6 @@
         return mWakeUpComingFromTouch;
     }
 
-    @Override
-    public boolean isFalsingThresholdNeeded() {
-        return true;
-    }
-
     /**
      * To be called when there's a state change in StatusBarKeyguardViewManager.
      */
@@ -3393,22 +3388,6 @@
         }
     }
 
-    @Override
-    public void onUnlockHintStarted() {
-        mFalsingCollector.onUnlockHintStarted();
-        mKeyguardIndicationController.showActionToUnlock();
-    }
-
-    @Override
-    public void onHintFinished() {
-        // Delay the reset a bit so the user can read the text.
-        mKeyguardIndicationController.hideTransientIndicationDelayed(HINT_RESET_DELAY_MS);
-    }
-
-    @Override
-    public void onTrackingStopped(boolean expand) {
-    }
-
     // TODO: Figure out way to remove these.
     @Override
     public NavigationBarView getNavigationBarView() {
@@ -4139,11 +4118,6 @@
 
     // End Extra BaseStatusBarMethods.
 
-    @Override
-    public NotificationGutsManager getGutsManager() {
-        return mGutsManager;
-    }
-
     boolean isTransientShown() {
         return mTransientShown;
     }
@@ -4266,7 +4240,6 @@
             }
             // TODO: Bring these out of CentralSurfaces.
             mUserInfoControllerImpl.onDensityOrFontScaleChanged();
-            mUserSwitcherController.onDensityOrFontScaleChanged();
             mNotificationIconAreaController.onDensityOrFontScaleChanged(mContext);
             mHeadsUpManager.onDensityOrFontScaleChanged();
         }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ConfigurationControllerImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ConfigurationControllerImpl.kt
index 34cd1ce..7dcdc0b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ConfigurationControllerImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ConfigurationControllerImpl.kt
@@ -33,7 +33,7 @@
     private val lastConfig = Configuration()
     private var density: Int = 0
     private var smallestScreenWidth: Int = 0
-    private var maxBounds: Rect? = null
+    private var maxBounds = Rect()
     private var fontScale: Float = 0.toFloat()
     private val inCarMode: Boolean
     private var uiMode: Int = 0
@@ -47,6 +47,7 @@
         fontScale = currentConfig.fontScale
         density = currentConfig.densityDpi
         smallestScreenWidth = currentConfig.smallestScreenWidthDp
+        maxBounds.set(currentConfig.windowConfiguration.maxBounds)
         inCarMode = currentConfig.uiMode and Configuration.UI_MODE_TYPE_MASK ==
                 Configuration.UI_MODE_TYPE_CAR
         uiMode = currentConfig.uiMode and Configuration.UI_MODE_NIGHT_MASK
@@ -92,7 +93,11 @@
 
         val maxBounds = newConfig.windowConfiguration.maxBounds
         if (maxBounds != this.maxBounds) {
-            this.maxBounds = maxBounds
+            // Update our internal rect to have the same bounds, instead of using
+            // `this.maxBounds = maxBounds` directly. Setting it directly means that `maxBounds`
+            // would be a direct reference to windowConfiguration.maxBounds, so the if statement
+            // above would always fail. See b/245799099 for more information.
+            this.maxBounds.set(maxBounds)
             listeners.filterForEach({ this.listeners.contains(it) }) {
                 it.onMaxBoundsChanged()
             }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarTransitionsController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarTransitionsController.java
index 16fddb42..6bf5443 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarTransitionsController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarTransitionsController.java
@@ -33,6 +33,7 @@
 import com.android.systemui.statusbar.policy.KeyguardStateController;
 
 import java.io.PrintWriter;
+import java.lang.ref.WeakReference;
 
 import dagger.assisted.Assisted;
 import dagger.assisted.AssistedFactory;
@@ -41,12 +42,54 @@
 /**
  * Class to control all aspects about light bar changes.
  */
-public class LightBarTransitionsController implements Dumpable, Callbacks,
-        StatusBarStateController.StateListener {
+public class LightBarTransitionsController implements Dumpable {
 
     public static final int DEFAULT_TINT_ANIMATION_DURATION = 120;
     private static final String EXTRA_DARK_INTENSITY = "dark_intensity";
 
+    private static class Callback implements Callbacks, StatusBarStateController.StateListener {
+        private final WeakReference<LightBarTransitionsController> mSelf;
+
+        Callback(LightBarTransitionsController self) {
+            mSelf = new WeakReference<>(self);
+        }
+
+        @Override
+        public void appTransitionPending(int displayId, boolean forced) {
+            LightBarTransitionsController self = mSelf.get();
+            if (self != null) {
+                self.appTransitionPending(displayId, forced);
+            }
+        }
+
+        @Override
+        public void appTransitionCancelled(int displayId) {
+            LightBarTransitionsController self = mSelf.get();
+            if (self != null) {
+                self.appTransitionCancelled(displayId);
+            }
+        }
+
+        @Override
+        public void appTransitionStarting(int displayId, long startTime, long duration,
+                boolean forced) {
+            LightBarTransitionsController self = mSelf.get();
+            if (self != null) {
+                self.appTransitionStarting(displayId, startTime, duration, forced);
+            }
+        }
+
+        @Override
+        public void onDozeAmountChanged(float linear, float eased) {
+            LightBarTransitionsController self = mSelf.get();
+            if (self != null) {
+                self.onDozeAmountChanged(linear, eased);
+            }
+        }
+    }
+
+    private final Callback mCallback;
+
     private final Handler mHandler;
     private final DarkIntensityApplier mApplier;
     private final KeyguardStateController mKeyguardStateController;
@@ -86,8 +129,9 @@
         mKeyguardStateController = keyguardStateController;
         mStatusBarStateController = statusBarStateController;
         mCommandQueue = commandQueue;
-        mCommandQueue.addCallback(this);
-        mStatusBarStateController.addCallback(this);
+        mCallback = new Callback(this);
+        mCommandQueue.addCallback(mCallback);
+        mStatusBarStateController.addCallback(mCallback);
         mDozeAmount = mStatusBarStateController.getDozeAmount();
         mContext = context;
         mDisplayId = mContext.getDisplayId();
@@ -95,8 +139,8 @@
 
     /** Call to cleanup the LightBarTransitionsController when done with it. */
     public void destroy() {
-        mCommandQueue.removeCallback(this);
-        mStatusBarStateController.removeCallback(this);
+        mCommandQueue.removeCallback(mCallback);
+        mStatusBarStateController.removeCallback(mCallback);
     }
 
     public void saveState(Bundle outState) {
@@ -110,16 +154,14 @@
         mNextDarkIntensity = mDarkIntensity;
     }
 
-    @Override
-    public void appTransitionPending(int displayId, boolean forced) {
+    private void appTransitionPending(int displayId, boolean forced) {
         if (mDisplayId != displayId || mKeyguardStateController.isKeyguardGoingAway() && !forced) {
             return;
         }
         mTransitionPending = true;
     }
 
-    @Override
-    public void appTransitionCancelled(int displayId) {
+    private void appTransitionCancelled(int displayId) {
         if (mDisplayId != displayId) {
             return;
         }
@@ -131,9 +173,7 @@
         mTransitionPending = false;
     }
 
-    @Override
-    public void appTransitionStarting(int displayId, long startTime, long duration,
-            boolean forced) {
+    private void appTransitionStarting(int displayId, long startTime, long duration, boolean forced) {
         if (mDisplayId != displayId || mKeyguardStateController.isKeyguardGoingAway() && !forced) {
             return;
         }
@@ -230,10 +270,6 @@
         pw.print(" mNextDarkIntensity="); pw.println(mNextDarkIntensity);
     }
 
-    @Override
-    public void onStateChanged(int newState) { }
-
-    @Override
     public void onDozeAmountChanged(float linear, float eased) {
         mDozeAmount = eased;
         dispatchDark();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockscreenWallpaper.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockscreenWallpaper.java
index 8793a57..1d7dfe1 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockscreenWallpaper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockscreenWallpaper.java
@@ -17,7 +17,6 @@
 package com.android.systemui.statusbar.phone;
 
 import android.annotation.Nullable;
-import android.app.ActivityManager;
 import android.app.IWallpaperManager;
 import android.app.IWallpaperManagerCallback;
 import android.app.WallpaperColors;
@@ -45,6 +44,7 @@
 import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.dump.DumpManager;
+import com.android.systemui.settings.UserTracker;
 import com.android.systemui.statusbar.NotificationMediaManager;
 
 import libcore.io.IoUtils;
@@ -82,10 +82,11 @@
             KeyguardUpdateMonitor keyguardUpdateMonitor,
             DumpManager dumpManager,
             NotificationMediaManager mediaManager,
-            @Main Handler mainHandler) {
+            @Main Handler mainHandler,
+            UserTracker userTracker) {
         dumpManager.registerDumpable(getClass().getSimpleName(), this);
         mWallpaperManager = wallpaperManager;
-        mCurrentUserId = ActivityManager.getCurrentUser();
+        mCurrentUserId = userTracker.getUserId();
         mUpdateMonitor = keyguardUpdateMonitor;
         mMediaManager = mediaManager;
         mH = mainHandler;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ManagedProfileControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ManagedProfileControllerImpl.java
index 94d1bf4..26e6db6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ManagedProfileControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ManagedProfileControllerImpl.java
@@ -14,7 +14,6 @@
 
 package com.android.systemui.statusbar.phone;
 
-import android.app.ActivityManager;
 import android.app.StatusBarManager;
 import android.content.BroadcastReceiver;
 import android.content.Context;
@@ -28,6 +27,7 @@
 
 import com.android.systemui.broadcast.BroadcastDispatcher;
 import com.android.systemui.dagger.SysUISingleton;
+import com.android.systemui.settings.UserTracker;
 
 import java.util.ArrayList;
 import java.util.LinkedList;
@@ -44,6 +44,7 @@
 
     private final Context mContext;
     private final UserManager mUserManager;
+    private final UserTracker mUserTracker;
     private final BroadcastDispatcher mBroadcastDispatcher;
     private final LinkedList<UserInfo> mProfiles;
     private boolean mListening;
@@ -52,9 +53,11 @@
     /**
      */
     @Inject
-    public ManagedProfileControllerImpl(Context context, BroadcastDispatcher broadcastDispatcher) {
+    public ManagedProfileControllerImpl(Context context, UserTracker userTracker,
+            BroadcastDispatcher broadcastDispatcher) {
         mContext = context;
         mUserManager = UserManager.get(mContext);
+        mUserTracker = userTracker;
         mBroadcastDispatcher = broadcastDispatcher;
         mProfiles = new LinkedList<UserInfo>();
     }
@@ -90,7 +93,7 @@
     private void reloadManagedProfiles() {
         synchronized (mProfiles) {
             boolean hadProfile = mProfiles.size() > 0;
-            int user = ActivityManager.getCurrentUser();
+            int user = mUserTracker.getUserId();
             mProfiles.clear();
 
             for (UserInfo ui : mUserManager.getEnabledProfiles(user)) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarViewModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarViewModule.java
index 2d580ab..344d233 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarViewModule.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarViewModule.java
@@ -28,13 +28,13 @@
 import com.android.systemui.battery.BatteryMeterView;
 import com.android.systemui.battery.BatteryMeterViewController;
 import com.android.systemui.biometrics.AuthRippleView;
-import com.android.systemui.broadcast.BroadcastDispatcher;
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.dump.DumpManager;
 import com.android.systemui.flags.FeatureFlags;
 import com.android.systemui.flags.Flags;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.privacy.OngoingPrivacyChip;
+import com.android.systemui.settings.UserTracker;
 import com.android.systemui.shade.CombinedShadeHeadersConstraintManager;
 import com.android.systemui.shade.CombinedShadeHeadersConstraintManagerImpl;
 import com.android.systemui.shade.NotificationPanelView;
@@ -220,9 +220,9 @@
     @Named(LARGE_SCREEN_BATTERY_CONTROLLER)
     static BatteryMeterViewController getBatteryMeterViewController(
             @Named(SPLIT_SHADE_BATTERY_VIEW) BatteryMeterView batteryMeterView,
+            UserTracker userTracker,
             ConfigurationController configurationController,
             TunerService tunerService,
-            BroadcastDispatcher broadcastDispatcher,
             @Main Handler mainHandler,
             ContentResolver contentResolver,
             FeatureFlags featureFlags,
@@ -230,9 +230,9 @@
     ) {
         return new BatteryMeterViewController(
                 batteryMeterView,
+                userTracker,
                 configurationController,
                 tunerService,
-                broadcastDispatcher,
                 mainHandler,
                 contentResolver,
                 featureFlags,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BaseUserSwitcherAdapter.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BaseUserSwitcherAdapter.kt
index cf4106c..68d30d3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BaseUserSwitcherAdapter.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BaseUserSwitcherAdapter.kt
@@ -21,7 +21,6 @@
 import android.graphics.ColorMatrix
 import android.graphics.ColorMatrixColorFilter
 import android.graphics.drawable.Drawable
-import android.os.UserHandle
 import android.widget.BaseAdapter
 import com.android.systemui.qs.user.UserSwitchDialogController.DialogShower
 import com.android.systemui.user.data.source.UserRecord
@@ -84,7 +83,7 @@
     }
 
     fun refresh() {
-        controller.refreshUsers(UserHandle.USER_NULL)
+        controller.refreshUsers()
     }
 
     companion object {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryControllerImpl.java
index 3c2ac7b..2ee5232 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryControllerImpl.java
@@ -156,6 +156,7 @@
         pw.print("  mPluggedIn="); pw.println(mPluggedIn);
         pw.print("  mCharging="); pw.println(mCharging);
         pw.print("  mCharged="); pw.println(mCharged);
+        pw.print("  mIsOverheated="); pw.println(mIsOverheated);
         pw.print("  mPowerSave="); pw.println(mPowerSave);
         pw.print("  mStateUnknown="); pw.println(mStateUnknown);
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BluetoothControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BluetoothControllerImpl.java
index aae0f93..38b3769 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BluetoothControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BluetoothControllerImpl.java
@@ -17,7 +17,6 @@
 package com.android.systemui.statusbar.policy;
 
 import android.annotation.Nullable;
-import android.app.ActivityManager;
 import android.bluetooth.BluetoothAdapter;
 import android.bluetooth.BluetoothDevice;
 import android.bluetooth.BluetoothProfile;
@@ -41,6 +40,7 @@
 import com.android.systemui.dagger.qualifiers.Background;
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.dump.DumpManager;
+import com.android.systemui.settings.UserTracker;
 
 import java.io.PrintWriter;
 import java.lang.ref.WeakReference;
@@ -83,6 +83,7 @@
     @Inject
     public BluetoothControllerImpl(
             Context context,
+            UserTracker userTracker,
             DumpManager dumpManager,
             BluetoothLogger logger,
             @Background Looper bgLooper,
@@ -100,7 +101,7 @@
                     mLocalBluetoothManager.getBluetoothAdapter().getBluetoothState());
         }
         mUserManager = (UserManager) context.getSystemService(Context.USER_SERVICE);
-        mCurrentUser = ActivityManager.getCurrentUser();
+        mCurrentUser = userTracker.getUserId();
         mDumpManager.registerDumpable(TAG, this);
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/Clock.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/Clock.java
index 576962d..d84cbcc 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/Clock.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/Clock.java
@@ -16,6 +16,7 @@
 
 package com.android.systemui.statusbar.policy;
 
+import android.annotation.NonNull;
 import android.app.StatusBarManager;
 import android.content.BroadcastReceiver;
 import android.content.Context;
@@ -49,7 +50,7 @@
 import com.android.systemui.demomode.DemoModeCommandReceiver;
 import com.android.systemui.plugins.DarkIconDispatcher;
 import com.android.systemui.plugins.DarkIconDispatcher.DarkReceiver;
-import com.android.systemui.settings.CurrentUserTracker;
+import com.android.systemui.settings.UserTracker;
 import com.android.systemui.statusbar.CommandQueue;
 import com.android.systemui.statusbar.phone.StatusBarIconController;
 import com.android.systemui.statusbar.policy.ConfigurationController.ConfigurationListener;
@@ -79,7 +80,7 @@
     private static final String SHOW_SECONDS = "show_seconds";
     private static final String VISIBILITY = "visibility";
 
-    private final CurrentUserTracker mCurrentUserTracker;
+    private final UserTracker mUserTracker;
     private final CommandQueue mCommandQueue;
     private int mCurrentUserId;
 
@@ -114,6 +115,14 @@
 
     private final BroadcastDispatcher mBroadcastDispatcher;
 
+    private final UserTracker.Callback mUserChangedCallback =
+            new UserTracker.Callback() {
+                @Override
+                public void onUserChanged(int newUser, @NonNull Context userContext) {
+                    mCurrentUserId = newUser;
+                }
+            };
+
     public Clock(Context context, AttributeSet attrs) {
         this(context, attrs, 0);
     }
@@ -132,12 +141,7 @@
             a.recycle();
         }
         mBroadcastDispatcher = Dependency.get(BroadcastDispatcher.class);
-        mCurrentUserTracker = new CurrentUserTracker(mBroadcastDispatcher) {
-            @Override
-            public void onUserSwitched(int newUserId) {
-                mCurrentUserId = newUserId;
-            }
-        };
+        mUserTracker = Dependency.get(UserTracker.class);
     }
 
     @Override
@@ -196,8 +200,8 @@
             Dependency.get(TunerService.class).addTunable(this, CLOCK_SECONDS,
                     StatusBarIconController.ICON_HIDE_LIST);
             mCommandQueue.addCallback(this);
-            mCurrentUserTracker.startTracking();
-            mCurrentUserId = mCurrentUserTracker.getCurrentUserId();
+            mUserTracker.addCallback(mUserChangedCallback, mContext.getMainExecutor());
+            mCurrentUserId = mUserTracker.getUserId();
         }
 
         // The time zone may have changed while the receiver wasn't registered, so update the Time
@@ -227,7 +231,7 @@
             mAttached = false;
             Dependency.get(TunerService.class).removeTunable(this);
             mCommandQueue.removeCallback(this);
-            mCurrentUserTracker.stopTracking();
+            mUserTracker.removeCallback(mUserChangedCallback);
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HotspotControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HotspotControllerImpl.java
index 69b55c8..a4821e0 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HotspotControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HotspotControllerImpl.java
@@ -18,7 +18,6 @@
 
 import static android.net.TetheringManager.TETHERING_WIFI;
 
-import android.app.ActivityManager;
 import android.content.Context;
 import android.net.ConnectivityManager;
 import android.net.TetheringManager;
@@ -38,6 +37,7 @@
 import com.android.systemui.dagger.qualifiers.Background;
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.dump.DumpManager;
+import com.android.systemui.settings.UserTracker;
 
 import java.io.PrintWriter;
 import java.util.ArrayList;
@@ -59,6 +59,7 @@
     private final WifiManager mWifiManager;
     private final Handler mMainHandler;
     private final Context mContext;
+    private final UserTracker mUserTracker;
 
     private int mHotspotState;
     private volatile int mNumConnectedDevices;
@@ -95,10 +96,12 @@
     @Inject
     public HotspotControllerImpl(
             Context context,
+            UserTracker userTracker,
             @Main Handler mainHandler,
             @Background Handler backgroundHandler,
             DumpManager dumpManager) {
         mContext = context;
+        mUserTracker = userTracker;
         mTetheringManager = context.getSystemService(TetheringManager.class);
         mWifiManager = (WifiManager) context.getSystemService(Context.WIFI_SERVICE);
         mMainHandler = mainHandler;
@@ -125,7 +128,7 @@
     @Override
     public boolean isHotspotSupported() {
         return mIsTetheringSupportedConfig && mIsTetheringSupported && mHasTetherableWifiRegexs
-                && UserManager.get(mContext).isUserAdmin(ActivityManager.getCurrentUser());
+                && UserManager.get(mContext).isUserAdmin(mUserTracker.getUserId());
     }
 
     public void dump(PrintWriter pw, String[] args) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SecurityControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SecurityControllerImpl.java
index cc241d9..ba94714 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SecurityControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SecurityControllerImpl.java
@@ -16,7 +16,6 @@
 package com.android.systemui.statusbar.policy;
 
 import android.annotation.Nullable;
-import android.app.ActivityManager;
 import android.app.admin.DeviceAdminInfo;
 import android.app.admin.DevicePolicyManager;
 import android.app.admin.DevicePolicyManager.DeviceOwnerType;
@@ -55,8 +54,9 @@
 import com.android.systemui.broadcast.BroadcastDispatcher;
 import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dagger.qualifiers.Background;
+import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.dump.DumpManager;
-import com.android.systemui.settings.CurrentUserTracker;
+import com.android.systemui.settings.UserTracker;
 
 import org.xmlpull.v1.XmlPullParserException;
 
@@ -70,7 +70,7 @@
 /**
  */
 @SysUISingleton
-public class SecurityControllerImpl extends CurrentUserTracker implements SecurityController {
+public class SecurityControllerImpl implements SecurityController {
 
     private static final String TAG = "SecurityController";
     private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
@@ -84,11 +84,13 @@
     private static final int CA_CERT_LOADING_RETRY_TIME_IN_MS = 30_000;
 
     private final Context mContext;
+    private final UserTracker mUserTracker;
     private final ConnectivityManager mConnectivityManager;
     private final VpnManager mVpnManager;
     private final DevicePolicyManager mDevicePolicyManager;
     private final PackageManager mPackageManager;
     private final UserManager mUserManager;
+    private final Executor mMainExecutor;
     private final Executor mBgExecutor;
 
     @GuardedBy("mCallbacks")
@@ -102,18 +104,28 @@
     // Needs to be cached here since the query has to be asynchronous
     private ArrayMap<Integer, Boolean> mHasCACerts = new ArrayMap<Integer, Boolean>();
 
+    private final UserTracker.Callback mUserChangedCallback =
+            new UserTracker.Callback() {
+                @Override
+                public void onUserChanged(int newUser, @NonNull Context userContext) {
+                    onUserSwitched(newUser);
+                }
+            };
+
     /**
      */
     @Inject
     public SecurityControllerImpl(
             Context context,
+            UserTracker userTracker,
             @Background Handler bgHandler,
             BroadcastDispatcher broadcastDispatcher,
+            @Main Executor mainExecutor,
             @Background Executor bgExecutor,
             DumpManager dumpManager
     ) {
-        super(broadcastDispatcher);
         mContext = context;
+        mUserTracker = userTracker;
         mDevicePolicyManager = (DevicePolicyManager)
                 context.getSystemService(Context.DEVICE_POLICY_SERVICE);
         mConnectivityManager = (ConnectivityManager)
@@ -121,6 +133,7 @@
         mVpnManager = context.getSystemService(VpnManager.class);
         mPackageManager = context.getPackageManager();
         mUserManager = (UserManager) context.getSystemService(Context.USER_SERVICE);
+        mMainExecutor = mainExecutor;
         mBgExecutor = bgExecutor;
 
         dumpManager.registerDumpable(getClass().getSimpleName(), this);
@@ -133,8 +146,8 @@
 
         // TODO: re-register network callback on user change.
         mConnectivityManager.registerNetworkCallback(REQUEST, mNetworkCallback);
-        onUserSwitched(ActivityManager.getCurrentUser());
-        startTracking();
+        onUserSwitched(mUserTracker.getUserId());
+        mUserTracker.addCallback(mUserChangedCallback, mMainExecutor);
     }
 
     public void dump(PrintWriter pw, String[] args) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserInfoControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserInfoControllerImpl.java
index 29285f8..a593d51 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserInfoControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserInfoControllerImpl.java
@@ -16,7 +16,6 @@
 
 package com.android.systemui.statusbar.policy;
 
-import android.app.ActivityManager;
 import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.Intent;
@@ -28,7 +27,6 @@
 import android.graphics.Bitmap;
 import android.graphics.drawable.Drawable;
 import android.os.AsyncTask;
-import android.os.RemoteException;
 import android.os.UserHandle;
 import android.os.UserManager;
 import android.provider.ContactsContract;
@@ -40,8 +38,11 @@
 import com.android.settingslib.drawable.UserIconDrawable;
 import com.android.systemui.R;
 import com.android.systemui.dagger.SysUISingleton;
+import com.android.systemui.dagger.qualifiers.Main;
+import com.android.systemui.settings.UserTracker;
 
 import java.util.ArrayList;
+import java.util.concurrent.Executor;
 
 import javax.inject.Inject;
 
@@ -53,6 +54,7 @@
     private static final String TAG = "UserInfoController";
 
     private final Context mContext;
+    private final UserTracker mUserTracker;
     private final ArrayList<OnUserInfoChangedListener> mCallbacks =
             new ArrayList<OnUserInfoChangedListener>();
     private AsyncTask<Void, Void, UserInfoQueryResult> mUserInfoTask;
@@ -64,11 +66,11 @@
     /**
      */
     @Inject
-    public UserInfoControllerImpl(Context context) {
+    public UserInfoControllerImpl(Context context, @Main Executor mainExecutor,
+            UserTracker userTracker) {
         mContext = context;
-        IntentFilter filter = new IntentFilter();
-        filter.addAction(Intent.ACTION_USER_SWITCHED);
-        mContext.registerReceiver(mReceiver, filter);
+        mUserTracker = userTracker;
+        mUserTracker.addCallback(mUserChangedCallback, mainExecutor);
 
         IntentFilter profileFilter = new IntentFilter();
         profileFilter.addAction(ContactsContract.Intents.ACTION_PROFILE_CHANGED);
@@ -88,15 +90,13 @@
         mCallbacks.remove(callback);
     }
 
-    private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
-        @Override
-        public void onReceive(Context context, Intent intent) {
-            final String action = intent.getAction();
-            if (Intent.ACTION_USER_SWITCHED.equals(action)) {
-                reloadUserInfo();
-            }
-        }
-    };
+    private final UserTracker.Callback mUserChangedCallback =
+            new UserTracker.Callback() {
+                @Override
+                public void onUserChanged(int newUser, @NonNull Context userContext) {
+                    reloadUserInfo();
+                }
+            };
 
     private final BroadcastReceiver mProfileReceiver = new BroadcastReceiver() {
         @Override
@@ -104,15 +104,11 @@
             final String action = intent.getAction();
             if (ContactsContract.Intents.ACTION_PROFILE_CHANGED.equals(action) ||
                     Intent.ACTION_USER_INFO_CHANGED.equals(action)) {
-                try {
-                    final int currentUser = ActivityManager.getService().getCurrentUser().id;
-                    final int changedUser =
-                            intent.getIntExtra(Intent.EXTRA_USER_HANDLE, getSendingUserId());
-                    if (changedUser == currentUser) {
-                        reloadUserInfo();
-                    }
-                } catch (RemoteException e) {
-                    Log.e(TAG, "Couldn't get current user id for profile change", e);
+                final int currentUser = mUserTracker.getUserId();
+                final int changedUser =
+                        intent.getIntExtra(Intent.EXTRA_USER_HANDLE, getSendingUserId());
+                if (changedUser == currentUser) {
+                    reloadUserInfo();
                 }
             }
         }
@@ -130,15 +126,12 @@
         Context currentUserContext;
         UserInfo userInfo;
         try {
-            userInfo = ActivityManager.getService().getCurrentUser();
+            userInfo = mUserTracker.getUserInfo();
             currentUserContext = mContext.createPackageContextAsUser("android", 0,
                     new UserHandle(userInfo.id));
         } catch (PackageManager.NameNotFoundException e) {
             Log.e(TAG, "Couldn't create user context", e);
             throw new RuntimeException(e);
-        } catch (RemoteException e) {
-            Log.e(TAG, "Couldn't get user info", e);
-            throw new RuntimeException(e);
         }
         final int userId = userInfo.id;
         final boolean isGuest = userInfo.isGuest();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.kt
index 146b222..bdb656b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.kt
@@ -14,35 +14,74 @@
  * limitations under the License.
  *
  */
+
 package com.android.systemui.statusbar.policy
 
-import android.annotation.UserIdInt
+import android.content.Context
 import android.content.Intent
 import android.view.View
-import com.android.systemui.Dumpable
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
+import com.android.systemui.plugins.ActivityStarter
 import com.android.systemui.qs.user.UserSwitchDialogController.DialogShower
 import com.android.systemui.user.data.source.UserRecord
+import com.android.systemui.user.domain.interactor.GuestUserInteractor
+import com.android.systemui.user.domain.interactor.UserInteractor
 import com.android.systemui.user.legacyhelper.ui.LegacyUserUiHelper
+import dagger.Lazy
+import java.io.PrintWriter
 import java.lang.ref.WeakReference
-import kotlinx.coroutines.flow.Flow
+import javax.inject.Inject
 
-/** Defines interface for a class that provides user switching functionality and state. */
-interface UserSwitcherController : Dumpable {
+/** Access point into multi-user switching logic. */
+@Deprecated("Use UserInteractor or GuestUserInteractor instead.")
+@SysUISingleton
+class UserSwitcherController
+@Inject
+constructor(
+    @Application private val applicationContext: Context,
+    private val userInteractorLazy: Lazy<UserInteractor>,
+    private val guestUserInteractorLazy: Lazy<GuestUserInteractor>,
+    private val keyguardInteractorLazy: Lazy<KeyguardInteractor>,
+    private val activityStarter: ActivityStarter,
+) {
+
+    /** Defines interface for classes that can be called back when the user is switched. */
+    fun interface UserSwitchCallback {
+        /** Notifies that the user has switched. */
+        fun onUserSwitched()
+    }
+
+    private val userInteractor: UserInteractor by lazy { userInteractorLazy.get() }
+    private val guestUserInteractor: GuestUserInteractor by lazy { guestUserInteractorLazy.get() }
+    private val keyguardInteractor: KeyguardInteractor by lazy { keyguardInteractorLazy.get() }
+
+    private val callbackCompatMap = mutableMapOf<UserSwitchCallback, UserInteractor.UserCallback>()
 
     /** The current list of [UserRecord]. */
     val users: ArrayList<UserRecord>
+        get() = userInteractor.userRecords.value
 
     /** Whether the user switcher experience should use the simple experience. */
     val isSimpleUserSwitcher: Boolean
-
-    /** Require a view for jank detection */
-    fun init(view: View)
+        get() = userInteractor.isSimpleUserSwitcher
 
     /** The [UserRecord] of the current user or `null` when none. */
     val currentUserRecord: UserRecord?
+        get() = userInteractor.selectedUserRecord.value
 
     /** The name of the current user of the device or `null`, when none is selected. */
     val currentUserName: String?
+        get() =
+            currentUserRecord?.let {
+                LegacyUserUiHelper.getUserRecordName(
+                    context = applicationContext,
+                    record = it,
+                    isGuestUserAutoCreated = userInteractor.isGuestUserAutoCreated,
+                    isGuestUserResetting = userInteractor.isGuestUserResetting,
+                )
+            }
 
     /**
      * Notifies that a user has been selected.
@@ -55,34 +94,40 @@
      * @param userId The ID of the user to switch to.
      * @param dialogShower An optional [DialogShower] in case we need to show dialogs.
      */
-    fun onUserSelected(userId: Int, dialogShower: DialogShower?)
-
-    /** Whether it is allowed to add users while the device is locked. */
-    val isAddUsersFromLockScreenEnabled: Flow<Boolean>
+    fun onUserSelected(userId: Int, dialogShower: DialogShower?) {
+        userInteractor.selectUser(userId, dialogShower)
+    }
 
     /** Whether the guest user is configured to always be present on the device. */
     val isGuestUserAutoCreated: Boolean
+        get() = userInteractor.isGuestUserAutoCreated
 
     /** Whether the guest user is currently being reset. */
     val isGuestUserResetting: Boolean
-
-    /** Creates and switches to the guest user. */
-    fun createAndSwitchToGuestUser(dialogShower: DialogShower?)
-
-    /** Shows the add user dialog. */
-    fun showAddUserDialog(dialogShower: DialogShower?)
-
-    /** Starts an activity to add a supervised user to the device. */
-    fun startSupervisedUserActivity()
-
-    /** Notifies when the display density or font scale has changed. */
-    fun onDensityOrFontScaleChanged()
+        get() = userInteractor.isGuestUserResetting
 
     /** Registers an adapter to notify when the users change. */
-    fun addAdapter(adapter: WeakReference<BaseUserSwitcherAdapter>)
+    fun addAdapter(adapter: WeakReference<BaseUserSwitcherAdapter>) {
+        userInteractor.addCallback(
+            object : UserInteractor.UserCallback {
+                override fun isEvictable(): Boolean {
+                    return adapter.get() == null
+                }
+
+                override fun onUserStateChanged() {
+                    adapter.get()?.notifyDataSetChanged()
+                }
+            }
+        )
+    }
 
     /** Notifies the item for a user has been clicked. */
-    fun onUserListItemClicked(record: UserRecord, dialogShower: DialogShower?)
+    fun onUserListItemClicked(
+        record: UserRecord,
+        dialogShower: DialogShower?,
+    ) {
+        userInteractor.onRecordSelected(record, dialogShower)
+    }
 
     /**
      * Removes guest user and switches to target user. The guest must be the current user and its id
@@ -103,7 +148,12 @@
      * @param targetUserId id of the user to switch to after guest is removed. If
      * `UserHandle.USER_NULL`, then switch immediately to the newly created guest user.
      */
-    fun removeGuestUser(@UserIdInt guestUserId: Int, @UserIdInt targetUserId: Int)
+    fun removeGuestUser(guestUserId: Int, targetUserId: Int) {
+        userInteractor.removeGuestUser(
+            guestUserId = guestUserId,
+            targetUserId = targetUserId,
+        )
+    }
 
     /**
      * Exits guest user and switches to previous non-guest user. The guest must be the current user.
@@ -114,43 +164,58 @@
      * @param forceRemoveGuestOnExit true: remove guest before switching user, false: remove guest
      * only if its ephemeral, else keep guest
      */
-    fun exitGuestUser(
-        @UserIdInt guestUserId: Int,
-        @UserIdInt targetUserId: Int,
-        forceRemoveGuestOnExit: Boolean
-    )
+    fun exitGuestUser(guestUserId: Int, targetUserId: Int, forceRemoveGuestOnExit: Boolean) {
+        userInteractor.exitGuestUser(guestUserId, targetUserId, forceRemoveGuestOnExit)
+    }
 
     /**
      * Guarantee guest is present only if the device is provisioned. Otherwise, create a content
      * observer to wait until the device is provisioned, then schedule the guest creation.
      */
-    fun schedulePostBootGuestCreation()
+    fun schedulePostBootGuestCreation() {
+        guestUserInteractor.onDeviceBootCompleted()
+    }
 
     /** Whether keyguard is showing. */
     val isKeyguardShowing: Boolean
+        get() = keyguardInteractor.isKeyguardShowing()
 
     /** Starts an activity with the given [Intent]. */
-    fun startActivity(intent: Intent)
+    fun startActivity(intent: Intent) {
+        activityStarter.startActivity(intent, /* dismissShade= */ true)
+    }
 
     /**
      * Refreshes users from UserManager.
      *
      * The pictures are only loaded if they have not been loaded yet.
-     *
-     * @param forcePictureLoadForId forces the picture of the given user to be reloaded.
      */
-    fun refreshUsers(forcePictureLoadForId: Int)
+    fun refreshUsers() {
+        userInteractor.refreshUsers()
+    }
 
     /** Adds a subscriber to when user switches. */
-    fun addUserSwitchCallback(callback: UserSwitchCallback)
+    fun addUserSwitchCallback(callback: UserSwitchCallback) {
+        val interactorCallback =
+            object : UserInteractor.UserCallback {
+                override fun onUserStateChanged() {
+                    callback.onUserSwitched()
+                }
+            }
+        callbackCompatMap[callback] = interactorCallback
+        userInteractor.addCallback(interactorCallback)
+    }
 
     /** Removes a previously-added subscriber. */
-    fun removeUserSwitchCallback(callback: UserSwitchCallback)
+    fun removeUserSwitchCallback(callback: UserSwitchCallback) {
+        val interactorCallback = callbackCompatMap.remove(callback)
+        if (interactorCallback != null) {
+            userInteractor.removeCallback(interactorCallback)
+        }
+    }
 
-    /** Defines interface for classes that can be called back when the user is switched. */
-    fun interface UserSwitchCallback {
-        /** Notifies that the user has switched. */
-        fun onUserSwitched()
+    fun dump(pw: PrintWriter, args: Array<out String>) {
+        userInteractor.dump(pw)
     }
 
     companion object {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherControllerImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherControllerImpl.kt
deleted file mode 100644
index 935fc7f..0000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherControllerImpl.kt
+++ /dev/null
@@ -1,299 +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.statusbar.policy
-
-import android.content.Context
-import android.content.Intent
-import android.view.View
-import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.dagger.qualifiers.Application
-import com.android.systemui.flags.FeatureFlags
-import com.android.systemui.flags.Flags
-import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
-import com.android.systemui.plugins.ActivityStarter
-import com.android.systemui.qs.user.UserSwitchDialogController
-import com.android.systemui.user.data.source.UserRecord
-import com.android.systemui.user.domain.interactor.GuestUserInteractor
-import com.android.systemui.user.domain.interactor.UserInteractor
-import com.android.systemui.user.legacyhelper.ui.LegacyUserUiHelper
-import dagger.Lazy
-import java.io.PrintWriter
-import java.lang.ref.WeakReference
-import javax.inject.Inject
-import kotlinx.coroutines.flow.Flow
-
-/** Implementation of [UserSwitcherController]. */
-@SysUISingleton
-class UserSwitcherControllerImpl
-@Inject
-constructor(
-    @Application private val applicationContext: Context,
-    flags: FeatureFlags,
-    @Suppress("DEPRECATION") private val oldImpl: Lazy<UserSwitcherControllerOldImpl>,
-    private val userInteractorLazy: Lazy<UserInteractor>,
-    private val guestUserInteractorLazy: Lazy<GuestUserInteractor>,
-    private val keyguardInteractorLazy: Lazy<KeyguardInteractor>,
-    private val activityStarter: ActivityStarter,
-) : UserSwitcherController {
-
-    private val useInteractor: Boolean =
-        flags.isEnabled(Flags.USER_CONTROLLER_USES_INTERACTOR) &&
-            !flags.isEnabled(Flags.USER_INTERACTOR_AND_REPO_USE_CONTROLLER)
-    private val _oldImpl: UserSwitcherControllerOldImpl
-        get() = oldImpl.get()
-    private val userInteractor: UserInteractor by lazy { userInteractorLazy.get() }
-    private val guestUserInteractor: GuestUserInteractor by lazy { guestUserInteractorLazy.get() }
-    private val keyguardInteractor: KeyguardInteractor by lazy { keyguardInteractorLazy.get() }
-
-    private val callbackCompatMap =
-        mutableMapOf<UserSwitcherController.UserSwitchCallback, UserInteractor.UserCallback>()
-
-    private fun notSupported(): Nothing {
-        error("Not supported in the new implementation!")
-    }
-
-    override val users: ArrayList<UserRecord>
-        get() =
-            if (useInteractor) {
-                userInteractor.userRecords.value
-            } else {
-                _oldImpl.users
-            }
-
-    override val isSimpleUserSwitcher: Boolean
-        get() =
-            if (useInteractor) {
-                userInteractor.isSimpleUserSwitcher
-            } else {
-                _oldImpl.isSimpleUserSwitcher
-            }
-
-    override fun init(view: View) {
-        if (!useInteractor) {
-            _oldImpl.init(view)
-        }
-    }
-
-    override val currentUserRecord: UserRecord?
-        get() =
-            if (useInteractor) {
-                userInteractor.selectedUserRecord.value
-            } else {
-                _oldImpl.currentUserRecord
-            }
-
-    override val currentUserName: String?
-        get() =
-            if (useInteractor) {
-                currentUserRecord?.let {
-                    LegacyUserUiHelper.getUserRecordName(
-                        context = applicationContext,
-                        record = it,
-                        isGuestUserAutoCreated = userInteractor.isGuestUserAutoCreated,
-                        isGuestUserResetting = userInteractor.isGuestUserResetting,
-                    )
-                }
-            } else {
-                _oldImpl.currentUserName
-            }
-
-    override fun onUserSelected(
-        userId: Int,
-        dialogShower: UserSwitchDialogController.DialogShower?
-    ) {
-        if (useInteractor) {
-            userInteractor.selectUser(userId, dialogShower)
-        } else {
-            _oldImpl.onUserSelected(userId, dialogShower)
-        }
-    }
-
-    override val isAddUsersFromLockScreenEnabled: Flow<Boolean>
-        get() =
-            if (useInteractor) {
-                notSupported()
-            } else {
-                _oldImpl.isAddUsersFromLockScreenEnabled
-            }
-
-    override val isGuestUserAutoCreated: Boolean
-        get() =
-            if (useInteractor) {
-                userInteractor.isGuestUserAutoCreated
-            } else {
-                _oldImpl.isGuestUserAutoCreated
-            }
-
-    override val isGuestUserResetting: Boolean
-        get() =
-            if (useInteractor) {
-                userInteractor.isGuestUserResetting
-            } else {
-                _oldImpl.isGuestUserResetting
-            }
-
-    override fun createAndSwitchToGuestUser(
-        dialogShower: UserSwitchDialogController.DialogShower?,
-    ) {
-        if (useInteractor) {
-            notSupported()
-        } else {
-            _oldImpl.createAndSwitchToGuestUser(dialogShower)
-        }
-    }
-
-    override fun showAddUserDialog(dialogShower: UserSwitchDialogController.DialogShower?) {
-        if (useInteractor) {
-            notSupported()
-        } else {
-            _oldImpl.showAddUserDialog(dialogShower)
-        }
-    }
-
-    override fun startSupervisedUserActivity() {
-        if (useInteractor) {
-            notSupported()
-        } else {
-            _oldImpl.startSupervisedUserActivity()
-        }
-    }
-
-    override fun onDensityOrFontScaleChanged() {
-        if (!useInteractor) {
-            _oldImpl.onDensityOrFontScaleChanged()
-        }
-    }
-
-    override fun addAdapter(adapter: WeakReference<BaseUserSwitcherAdapter>) {
-        if (useInteractor) {
-            userInteractor.addCallback(
-                object : UserInteractor.UserCallback {
-                    override fun isEvictable(): Boolean {
-                        return adapter.get() == null
-                    }
-
-                    override fun onUserStateChanged() {
-                        adapter.get()?.notifyDataSetChanged()
-                    }
-                }
-            )
-        } else {
-            _oldImpl.addAdapter(adapter)
-        }
-    }
-
-    override fun onUserListItemClicked(
-        record: UserRecord,
-        dialogShower: UserSwitchDialogController.DialogShower?,
-    ) {
-        if (useInteractor) {
-            userInteractor.onRecordSelected(record, dialogShower)
-        } else {
-            _oldImpl.onUserListItemClicked(record, dialogShower)
-        }
-    }
-
-    override fun removeGuestUser(guestUserId: Int, targetUserId: Int) {
-        if (useInteractor) {
-            userInteractor.removeGuestUser(
-                guestUserId = guestUserId,
-                targetUserId = targetUserId,
-            )
-        } else {
-            _oldImpl.removeGuestUser(guestUserId, targetUserId)
-        }
-    }
-
-    override fun exitGuestUser(
-        guestUserId: Int,
-        targetUserId: Int,
-        forceRemoveGuestOnExit: Boolean
-    ) {
-        if (useInteractor) {
-            userInteractor.exitGuestUser(guestUserId, targetUserId, forceRemoveGuestOnExit)
-        } else {
-            _oldImpl.exitGuestUser(guestUserId, targetUserId, forceRemoveGuestOnExit)
-        }
-    }
-
-    override fun schedulePostBootGuestCreation() {
-        if (useInteractor) {
-            guestUserInteractor.onDeviceBootCompleted()
-        } else {
-            _oldImpl.schedulePostBootGuestCreation()
-        }
-    }
-
-    override val isKeyguardShowing: Boolean
-        get() =
-            if (useInteractor) {
-                keyguardInteractor.isKeyguardShowing()
-            } else {
-                _oldImpl.isKeyguardShowing
-            }
-
-    override fun startActivity(intent: Intent) {
-        if (useInteractor) {
-            activityStarter.startActivity(intent, /* dismissShade= */ true)
-        } else {
-            _oldImpl.startActivity(intent)
-        }
-    }
-
-    override fun refreshUsers(forcePictureLoadForId: Int) {
-        if (useInteractor) {
-            userInteractor.refreshUsers()
-        } else {
-            _oldImpl.refreshUsers(forcePictureLoadForId)
-        }
-    }
-
-    override fun addUserSwitchCallback(callback: UserSwitcherController.UserSwitchCallback) {
-        if (useInteractor) {
-            val interactorCallback =
-                object : UserInteractor.UserCallback {
-                    override fun onUserStateChanged() {
-                        callback.onUserSwitched()
-                    }
-                }
-            callbackCompatMap[callback] = interactorCallback
-            userInteractor.addCallback(interactorCallback)
-        } else {
-            _oldImpl.addUserSwitchCallback(callback)
-        }
-    }
-
-    override fun removeUserSwitchCallback(callback: UserSwitcherController.UserSwitchCallback) {
-        if (useInteractor) {
-            val interactorCallback = callbackCompatMap.remove(callback)
-            if (interactorCallback != null) {
-                userInteractor.removeCallback(interactorCallback)
-            }
-        } else {
-            _oldImpl.removeUserSwitchCallback(callback)
-        }
-    }
-
-    override fun dump(pw: PrintWriter, args: Array<out String>) {
-        if (useInteractor) {
-            userInteractor.dump(pw)
-        } else {
-            _oldImpl.dump(pw, args)
-        }
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherControllerOldImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherControllerOldImpl.java
deleted file mode 100644
index c294c37..0000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherControllerOldImpl.java
+++ /dev/null
@@ -1,1063 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.systemui.statusbar.policy;
-
-import static android.os.UserManager.SWITCHABILITY_STATUS_OK;
-
-import android.annotation.UserIdInt;
-import android.app.AlertDialog;
-import android.app.Dialog;
-import android.app.IActivityManager;
-import android.app.admin.DevicePolicyManager;
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.content.pm.UserInfo;
-import android.database.ContentObserver;
-import android.graphics.Bitmap;
-import android.os.Handler;
-import android.os.RemoteException;
-import android.os.UserHandle;
-import android.os.UserManager;
-import android.provider.Settings;
-import android.telephony.TelephonyCallback;
-import android.text.TextUtils;
-import android.util.Log;
-import android.util.SparseArray;
-import android.util.SparseBooleanArray;
-import android.view.View;
-import android.view.WindowManagerGlobal;
-import android.widget.Toast;
-
-import androidx.annotation.Nullable;
-
-import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.jank.InteractionJankMonitor;
-import com.android.internal.logging.UiEventLogger;
-import com.android.internal.util.LatencyTracker;
-import com.android.keyguard.KeyguardUpdateMonitor;
-import com.android.settingslib.users.UserCreatingDialog;
-import com.android.systemui.GuestResetOrExitSessionReceiver;
-import com.android.systemui.GuestResumeSessionReceiver;
-import com.android.systemui.SystemUISecondaryUserService;
-import com.android.systemui.animation.DialogCuj;
-import com.android.systemui.animation.DialogLaunchAnimator;
-import com.android.systemui.broadcast.BroadcastDispatcher;
-import com.android.systemui.broadcast.BroadcastSender;
-import com.android.systemui.dagger.SysUISingleton;
-import com.android.systemui.dagger.qualifiers.Background;
-import com.android.systemui.dagger.qualifiers.LongRunning;
-import com.android.systemui.dagger.qualifiers.Main;
-import com.android.systemui.dump.DumpManager;
-import com.android.systemui.plugins.ActivityStarter;
-import com.android.systemui.plugins.FalsingManager;
-import com.android.systemui.qs.QSUserSwitcherEvent;
-import com.android.systemui.qs.user.UserSwitchDialogController.DialogShower;
-import com.android.systemui.settings.UserTracker;
-import com.android.systemui.telephony.TelephonyListenerManager;
-import com.android.systemui.user.data.source.UserRecord;
-import com.android.systemui.user.legacyhelper.data.LegacyUserDataHelper;
-import com.android.systemui.user.shared.model.UserActionModel;
-import com.android.systemui.user.ui.dialog.AddUserDialog;
-import com.android.systemui.user.ui.dialog.ExitGuestDialog;
-import com.android.systemui.util.settings.GlobalSettings;
-import com.android.systemui.util.settings.SecureSettings;
-
-import java.io.PrintWriter;
-import java.lang.ref.WeakReference;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.List;
-import java.util.concurrent.Executor;
-import java.util.concurrent.atomic.AtomicBoolean;
-import java.util.function.Consumer;
-
-import javax.inject.Inject;
-
-import kotlinx.coroutines.flow.Flow;
-import kotlinx.coroutines.flow.MutableStateFlow;
-import kotlinx.coroutines.flow.StateFlowKt;
-
-/**
- * Old implementation. Keeps a list of all users on the device for user switching.
- *
- * @deprecated This is the old implementation. Please depend on {@link UserSwitcherController}
- * instead.
- */
-@Deprecated
-@SysUISingleton
-public class UserSwitcherControllerOldImpl implements UserSwitcherController {
-
-    private static final String TAG = "UserSwitcherController";
-    private static final boolean DEBUG = false;
-    private static final String SIMPLE_USER_SWITCHER_GLOBAL_SETTING =
-            "lockscreenSimpleUserSwitcher";
-    private static final int PAUSE_REFRESH_USERS_TIMEOUT_MS = 3000;
-
-    private static final String PERMISSION_SELF = "com.android.systemui.permission.SELF";
-    private static final long MULTI_USER_JOURNEY_TIMEOUT = 20000L;
-
-    private static final String INTERACTION_JANK_ADD_NEW_USER_TAG = "add_new_user";
-    private static final String INTERACTION_JANK_EXIT_GUEST_MODE_TAG = "exit_guest_mode";
-
-    protected final Context mContext;
-    protected final UserTracker mUserTracker;
-    protected final UserManager mUserManager;
-    private final ContentObserver mSettingsObserver;
-    private final ArrayList<WeakReference<BaseUserSwitcherAdapter>> mAdapters = new ArrayList<>();
-    @VisibleForTesting
-    final GuestResumeSessionReceiver mGuestResumeSessionReceiver;
-    @VisibleForTesting
-    final GuestResetOrExitSessionReceiver mGuestResetOrExitSessionReceiver;
-    private final KeyguardStateController mKeyguardStateController;
-    private final DeviceProvisionedController mDeviceProvisionedController;
-    private final DevicePolicyManager mDevicePolicyManager;
-    protected final Handler mHandler;
-    private final ActivityStarter mActivityStarter;
-    private final BroadcastDispatcher mBroadcastDispatcher;
-    private final BroadcastSender mBroadcastSender;
-    private final TelephonyListenerManager mTelephonyListenerManager;
-    private final InteractionJankMonitor mInteractionJankMonitor;
-    private final LatencyTracker mLatencyTracker;
-    private final DialogLaunchAnimator mDialogLaunchAnimator;
-
-    private ArrayList<UserRecord> mUsers = new ArrayList<>();
-    @VisibleForTesting
-    AlertDialog mExitGuestDialog;
-    @VisibleForTesting
-    Dialog mAddUserDialog;
-    private int mLastNonGuestUser = UserHandle.USER_SYSTEM;
-    private boolean mSimpleUserSwitcher;
-    // When false, there won't be any visual affordance to add a new user from the keyguard even if
-    // the user is unlocked
-    private final MutableStateFlow<Boolean> mAddUsersFromLockScreen =
-            StateFlowKt.MutableStateFlow(false);
-    private boolean mUserSwitcherEnabled;
-    @VisibleForTesting
-    boolean mPauseRefreshUsers;
-    private int mSecondaryUser = UserHandle.USER_NULL;
-    private Intent mSecondaryUserServiceIntent;
-    private SparseBooleanArray mForcePictureLoadForUserId = new SparseBooleanArray(2);
-    private final UiEventLogger mUiEventLogger;
-    private final IActivityManager mActivityManager;
-    private final Executor mBgExecutor;
-    private final Executor mUiExecutor;
-    private final Executor mLongRunningExecutor;
-    private final boolean mGuestUserAutoCreated;
-    private final AtomicBoolean mGuestIsResetting;
-    private final AtomicBoolean mGuestCreationScheduled;
-    private FalsingManager mFalsingManager;
-    @Nullable
-    private View mView;
-    private String mCreateSupervisedUserPackage;
-    private GlobalSettings mGlobalSettings;
-    private List<UserSwitchCallback> mUserSwitchCallbacks =
-            Collections.synchronizedList(new ArrayList<>());
-
-    @Inject
-    public UserSwitcherControllerOldImpl(
-            Context context,
-            IActivityManager activityManager,
-            UserManager userManager,
-            UserTracker userTracker,
-            KeyguardStateController keyguardStateController,
-            DeviceProvisionedController deviceProvisionedController,
-            DevicePolicyManager devicePolicyManager,
-            @Main Handler handler,
-            ActivityStarter activityStarter,
-            BroadcastDispatcher broadcastDispatcher,
-            BroadcastSender broadcastSender,
-            UiEventLogger uiEventLogger,
-            FalsingManager falsingManager,
-            TelephonyListenerManager telephonyListenerManager,
-            SecureSettings secureSettings,
-            GlobalSettings globalSettings,
-            @Background Executor bgExecutor,
-            @LongRunning Executor longRunningExecutor,
-            @Main Executor uiExecutor,
-            InteractionJankMonitor interactionJankMonitor,
-            LatencyTracker latencyTracker,
-            DumpManager dumpManager,
-            DialogLaunchAnimator dialogLaunchAnimator,
-            GuestResumeSessionReceiver guestResumeSessionReceiver,
-            GuestResetOrExitSessionReceiver guestResetOrExitSessionReceiver) {
-        mContext = context;
-        mActivityManager = activityManager;
-        mUserTracker = userTracker;
-        mBroadcastDispatcher = broadcastDispatcher;
-        mBroadcastSender = broadcastSender;
-        mTelephonyListenerManager = telephonyListenerManager;
-        mUiEventLogger = uiEventLogger;
-        mFalsingManager = falsingManager;
-        mInteractionJankMonitor = interactionJankMonitor;
-        mLatencyTracker = latencyTracker;
-        mGlobalSettings = globalSettings;
-        mGuestResumeSessionReceiver = guestResumeSessionReceiver;
-        mGuestResetOrExitSessionReceiver = guestResetOrExitSessionReceiver;
-        mBgExecutor = bgExecutor;
-        mLongRunningExecutor = longRunningExecutor;
-        mUiExecutor = uiExecutor;
-        mGuestResumeSessionReceiver.register();
-        mGuestResetOrExitSessionReceiver.register();
-        mGuestUserAutoCreated = mContext.getResources().getBoolean(
-                com.android.internal.R.bool.config_guestUserAutoCreated);
-        mGuestIsResetting = new AtomicBoolean();
-        mGuestCreationScheduled = new AtomicBoolean();
-        mKeyguardStateController = keyguardStateController;
-        mDeviceProvisionedController = deviceProvisionedController;
-        mDevicePolicyManager = devicePolicyManager;
-        mHandler = handler;
-        mActivityStarter = activityStarter;
-        mUserManager = userManager;
-        mDialogLaunchAnimator = dialogLaunchAnimator;
-
-        IntentFilter filter = new IntentFilter();
-        filter.addAction(Intent.ACTION_USER_ADDED);
-        filter.addAction(Intent.ACTION_USER_REMOVED);
-        filter.addAction(Intent.ACTION_USER_INFO_CHANGED);
-        filter.addAction(Intent.ACTION_USER_SWITCHED);
-        filter.addAction(Intent.ACTION_USER_STOPPED);
-        filter.addAction(Intent.ACTION_USER_UNLOCKED);
-        mBroadcastDispatcher.registerReceiver(
-                mReceiver, filter, null /* executor */,
-                UserHandle.SYSTEM, Context.RECEIVER_EXPORTED, null /* permission */);
-
-        mSimpleUserSwitcher = shouldUseSimpleUserSwitcher();
-
-        mSecondaryUserServiceIntent = new Intent(context, SystemUISecondaryUserService.class);
-
-        filter = new IntentFilter();
-        mContext.registerReceiverAsUser(mReceiver, UserHandle.SYSTEM, filter,
-                PERMISSION_SELF, null /* scheduler */,
-                Context.RECEIVER_EXPORTED_UNAUDITED);
-
-        mSettingsObserver = new ContentObserver(mHandler) {
-            @Override
-            public void onChange(boolean selfChange) {
-                mSimpleUserSwitcher = shouldUseSimpleUserSwitcher();
-                mAddUsersFromLockScreen.setValue(
-                        mGlobalSettings.getIntForUser(
-                                Settings.Global.ADD_USERS_WHEN_LOCKED,
-                                0,
-                                UserHandle.USER_SYSTEM) != 0);
-                mUserSwitcherEnabled = mGlobalSettings.getIntForUser(
-                        Settings.Global.USER_SWITCHER_ENABLED, 0, UserHandle.USER_SYSTEM) != 0;
-                refreshUsers(UserHandle.USER_NULL);
-            };
-        };
-        mContext.getContentResolver().registerContentObserver(
-                Settings.Global.getUriFor(SIMPLE_USER_SWITCHER_GLOBAL_SETTING), true,
-                mSettingsObserver);
-        mContext.getContentResolver().registerContentObserver(
-                Settings.Global.getUriFor(Settings.Global.USER_SWITCHER_ENABLED), true,
-                mSettingsObserver);
-        mContext.getContentResolver().registerContentObserver(
-                Settings.Global.getUriFor(Settings.Global.ADD_USERS_WHEN_LOCKED), true,
-                mSettingsObserver);
-        mContext.getContentResolver().registerContentObserver(
-                Settings.Global.getUriFor(
-                        Settings.Global.ALLOW_USER_SWITCHING_WHEN_SYSTEM_USER_LOCKED),
-                true, mSettingsObserver);
-        // Fetch initial values.
-        mSettingsObserver.onChange(false);
-
-        keyguardStateController.addCallback(mCallback);
-        listenForCallState();
-
-        mCreateSupervisedUserPackage = mContext.getString(
-                com.android.internal.R.string.config_supervisedUserCreationPackage);
-
-        dumpManager.registerDumpable(getClass().getSimpleName(), this);
-
-        refreshUsers(UserHandle.USER_NULL);
-    }
-
-    @Override
-    @SuppressWarnings("unchecked")
-    public void refreshUsers(int forcePictureLoadForId) {
-        if (DEBUG) Log.d(TAG, "refreshUsers(forcePictureLoadForId=" + forcePictureLoadForId + ")");
-        if (forcePictureLoadForId != UserHandle.USER_NULL) {
-            mForcePictureLoadForUserId.put(forcePictureLoadForId, true);
-        }
-
-        if (mPauseRefreshUsers) {
-            return;
-        }
-
-        boolean forceAllUsers = mForcePictureLoadForUserId.get(UserHandle.USER_ALL);
-        SparseArray<Bitmap> bitmaps = new SparseArray<>(mUsers.size());
-        final int userCount = mUsers.size();
-        for (int i = 0; i < userCount; i++) {
-            UserRecord r = mUsers.get(i);
-            if (r == null || r.picture == null || r.info == null || forceAllUsers
-                    || mForcePictureLoadForUserId.get(r.info.id)) {
-                continue;
-            }
-            bitmaps.put(r.info.id, r.picture);
-        }
-        mForcePictureLoadForUserId.clear();
-
-        mBgExecutor.execute(() ->  {
-            List<UserInfo> infos = mUserManager.getAliveUsers();
-            if (infos == null) {
-                return;
-            }
-            ArrayList<UserRecord> records = new ArrayList<>(infos.size());
-            int currentId = mUserTracker.getUserId();
-            // Check user switchability of the foreground user since SystemUI is running in
-            // User 0
-            boolean canSwitchUsers = mUserManager.getUserSwitchability(
-                    UserHandle.of(mUserTracker.getUserId())) == SWITCHABILITY_STATUS_OK;
-            UserRecord guestRecord = null;
-
-            for (UserInfo info : infos) {
-                boolean isCurrent = currentId == info.id;
-                if (!mUserSwitcherEnabled && !info.isPrimary()) {
-                    continue;
-                }
-
-                if (info.isEnabled()) {
-                    if (info.isGuest()) {
-                        // Tapping guest icon triggers remove and a user switch therefore
-                        // the icon shouldn't be enabled even if the user is current
-                        guestRecord = LegacyUserDataHelper.createRecord(
-                                mContext,
-                                mUserManager,
-                                null /* picture */,
-                                info,
-                                isCurrent,
-                                canSwitchUsers);
-                    } else if (info.supportsSwitchToByUser()) {
-                        records.add(
-                                LegacyUserDataHelper.createRecord(
-                                        mContext,
-                                        mUserManager,
-                                        bitmaps.get(info.id),
-                                        info,
-                                        isCurrent,
-                                        canSwitchUsers));
-                    }
-                }
-            }
-
-            if (guestRecord == null) {
-                if (mGuestUserAutoCreated) {
-                    // If mGuestIsResetting=true, the switch should be disabled since
-                    // we will just use it as an indicator for "Resetting guest...".
-                    // Otherwise, default to canSwitchUsers.
-                    boolean isSwitchToGuestEnabled = !mGuestIsResetting.get() && canSwitchUsers;
-                    guestRecord = LegacyUserDataHelper.createRecord(
-                            mContext,
-                            currentId,
-                            UserActionModel.ENTER_GUEST_MODE,
-                            false /* isRestricted */,
-                            isSwitchToGuestEnabled);
-                    records.add(guestRecord);
-                } else if (canCreateGuest(guestRecord != null)) {
-                    guestRecord = LegacyUserDataHelper.createRecord(
-                            mContext,
-                            currentId,
-                            UserActionModel.ENTER_GUEST_MODE,
-                            false /* isRestricted */,
-                            canSwitchUsers);
-                    records.add(guestRecord);
-                }
-            } else {
-                records.add(guestRecord);
-            }
-
-            if (canCreateUser()) {
-                final UserRecord userRecord = LegacyUserDataHelper.createRecord(
-                        mContext,
-                        currentId,
-                        UserActionModel.ADD_USER,
-                        createIsRestricted(),
-                        canSwitchUsers);
-                records.add(userRecord);
-            }
-
-            if (canCreateSupervisedUser()) {
-                final UserRecord userRecord = LegacyUserDataHelper.createRecord(
-                        mContext,
-                        currentId,
-                        UserActionModel.ADD_SUPERVISED_USER,
-                        createIsRestricted(),
-                        canSwitchUsers);
-                records.add(userRecord);
-            }
-
-            if (canManageUsers()) {
-                records.add(LegacyUserDataHelper.createRecord(
-                        mContext,
-                        KeyguardUpdateMonitor.getCurrentUser(),
-                        UserActionModel.NAVIGATE_TO_USER_MANAGEMENT,
-                        /* isRestricted= */ false,
-                        /* isSwitchToEnabled= */ true
-                ));
-            }
-
-            mUiExecutor.execute(() -> {
-                if (records != null) {
-                    mUsers = records;
-                    notifyAdapters();
-                }
-            });
-        });
-    }
-
-    private boolean systemCanCreateUsers() {
-        return !mUserManager.hasBaseUserRestriction(
-                UserManager.DISALLOW_ADD_USER, UserHandle.SYSTEM);
-    }
-
-    private boolean currentUserCanCreateUsers() {
-        UserInfo currentUser = mUserTracker.getUserInfo();
-        return currentUser != null
-                && (currentUser.isAdmin() || mUserTracker.getUserId() == UserHandle.USER_SYSTEM)
-                && systemCanCreateUsers();
-    }
-
-    private boolean anyoneCanCreateUsers() {
-        return systemCanCreateUsers() && mAddUsersFromLockScreen.getValue();
-    }
-
-    @VisibleForTesting
-    boolean canCreateGuest(boolean hasExistingGuest) {
-        return mUserSwitcherEnabled
-                && (currentUserCanCreateUsers() || anyoneCanCreateUsers())
-                && !hasExistingGuest;
-    }
-
-    @VisibleForTesting
-    boolean canCreateUser() {
-        return mUserSwitcherEnabled
-                && (currentUserCanCreateUsers() || anyoneCanCreateUsers())
-                && mUserManager.canAddMoreUsers(UserManager.USER_TYPE_FULL_SECONDARY);
-    }
-
-    @VisibleForTesting
-    boolean canManageUsers() {
-        UserInfo currentUser = mUserTracker.getUserInfo();
-        return mUserSwitcherEnabled
-                && ((currentUser != null && currentUser.isAdmin())
-                || mAddUsersFromLockScreen.getValue());
-    }
-
-    private boolean createIsRestricted() {
-        return !mAddUsersFromLockScreen.getValue();
-    }
-
-    @VisibleForTesting
-    boolean canCreateSupervisedUser() {
-        return !TextUtils.isEmpty(mCreateSupervisedUserPackage) && canCreateUser();
-    }
-
-    private void pauseRefreshUsers() {
-        if (!mPauseRefreshUsers) {
-            mHandler.postDelayed(mUnpauseRefreshUsers, PAUSE_REFRESH_USERS_TIMEOUT_MS);
-            mPauseRefreshUsers = true;
-        }
-    }
-
-    private void notifyAdapters() {
-        for (int i = mAdapters.size() - 1; i >= 0; i--) {
-            BaseUserSwitcherAdapter adapter = mAdapters.get(i).get();
-            if (adapter != null) {
-                adapter.notifyDataSetChanged();
-            } else {
-                mAdapters.remove(i);
-            }
-        }
-    }
-
-    @Override
-    public boolean isSimpleUserSwitcher() {
-        return mSimpleUserSwitcher;
-    }
-
-    /**
-     * Returns whether the current user is a system user.
-     */
-    @VisibleForTesting
-    boolean isSystemUser() {
-        return mUserTracker.getUserId() == UserHandle.USER_SYSTEM;
-    }
-
-    @Override
-    public @Nullable UserRecord getCurrentUserRecord() {
-        for (int i = 0; i < mUsers.size(); ++i) {
-            UserRecord userRecord = mUsers.get(i);
-            if (userRecord.isCurrent) {
-                return userRecord;
-            }
-        }
-        return null;
-    }
-
-    @Override
-    public void onUserSelected(int userId, @Nullable DialogShower dialogShower) {
-        UserRecord userRecord = mUsers.stream()
-                .filter(x -> x.resolveId() == userId)
-                .findFirst()
-                .orElse(null);
-        if (userRecord == null) {
-            return;
-        }
-
-        onUserListItemClicked(userRecord, dialogShower);
-    }
-
-    @Override
-    public Flow<Boolean> isAddUsersFromLockScreenEnabled() {
-        return mAddUsersFromLockScreen;
-    }
-
-    @Override
-    public boolean isGuestUserAutoCreated() {
-        return mGuestUserAutoCreated;
-    }
-
-    @Override
-    public boolean isGuestUserResetting() {
-        return mGuestIsResetting.get();
-    }
-
-    @Override
-    public void onUserListItemClicked(UserRecord record, DialogShower dialogShower) {
-        if (record.isGuest && record.info == null) {
-            createAndSwitchToGuestUser(dialogShower);
-        } else if (record.isAddUser) {
-            showAddUserDialog(dialogShower);
-        } else if (record.isAddSupervisedUser) {
-            startSupervisedUserActivity();
-        } else if (record.isManageUsers) {
-            startActivity(new Intent(Settings.ACTION_USER_SETTINGS));
-        } else {
-            onUserListItemClicked(record.info.id, record, dialogShower);
-        }
-    }
-
-    private void onUserListItemClicked(int id, UserRecord record, DialogShower dialogShower) {
-        int currUserId = mUserTracker.getUserId();
-        // If switching from guest and guest is ephemeral, then follow the flow
-        // of showExitGuestDialog to remove current guest,
-        // and switch to selected user
-        UserInfo currUserInfo = mUserTracker.getUserInfo();
-        if (currUserId == id) {
-            if (record.isGuest) {
-                showExitGuestDialog(id, currUserInfo.isEphemeral(), dialogShower);
-            }
-            return;
-        }
-
-        if (currUserInfo != null && currUserInfo.isGuest()) {
-            showExitGuestDialog(currUserId, currUserInfo.isEphemeral(),
-                    record.resolveId(), dialogShower);
-            return;
-        }
-
-        if (dialogShower != null) {
-            // If we haven't morphed into another dialog, it means we have just switched users.
-            // Then, dismiss the dialog.
-            dialogShower.dismiss();
-        }
-        switchToUserId(id);
-    }
-
-    private void switchToUserId(int id) {
-        try {
-            if (mView != null) {
-                mInteractionJankMonitor.begin(InteractionJankMonitor.Configuration.Builder
-                        .withView(InteractionJankMonitor.CUJ_USER_SWITCH, mView)
-                        .setTimeout(MULTI_USER_JOURNEY_TIMEOUT));
-            }
-            mLatencyTracker.onActionStart(LatencyTracker.ACTION_USER_SWITCH);
-            pauseRefreshUsers();
-            mActivityManager.switchUser(id);
-        } catch (RemoteException e) {
-            Log.e(TAG, "Couldn't switch user.", e);
-        }
-    }
-
-    private void showExitGuestDialog(int id, boolean isGuestEphemeral, DialogShower dialogShower) {
-        int newId = UserHandle.USER_SYSTEM;
-        if (mLastNonGuestUser != UserHandle.USER_SYSTEM) {
-            UserInfo info = mUserManager.getUserInfo(mLastNonGuestUser);
-            if (info != null && info.isEnabled() && info.supportsSwitchToByUser()) {
-                newId = info.id;
-            }
-        }
-        showExitGuestDialog(id, isGuestEphemeral, newId, dialogShower);
-    }
-
-    private void showExitGuestDialog(
-            int id,
-            boolean isGuestEphemeral,
-            int targetId,
-            DialogShower dialogShower) {
-        if (mExitGuestDialog != null && mExitGuestDialog.isShowing()) {
-            mExitGuestDialog.cancel();
-        }
-        mExitGuestDialog = new ExitGuestDialog(
-                mContext,
-                id,
-                isGuestEphemeral,
-                targetId,
-                mKeyguardStateController.isShowing(),
-                mFalsingManager,
-                mDialogLaunchAnimator,
-                this::exitGuestUser);
-        if (dialogShower != null) {
-            dialogShower.showDialog(mExitGuestDialog, new DialogCuj(
-                    InteractionJankMonitor.CUJ_USER_DIALOG_OPEN,
-                    INTERACTION_JANK_EXIT_GUEST_MODE_TAG));
-        } else {
-            mExitGuestDialog.show();
-        }
-    }
-
-    @Override
-    public void createAndSwitchToGuestUser(@Nullable DialogShower dialogShower) {
-        createGuestAsync(guestId -> {
-            // guestId may be USER_NULL if we haven't reloaded the user list yet.
-            if (guestId != UserHandle.USER_NULL) {
-                mUiEventLogger.log(QSUserSwitcherEvent.QS_USER_GUEST_ADD);
-                onUserListItemClicked(guestId, UserRecord.createForGuest(), dialogShower);
-            }
-        });
-    }
-
-    @Override
-    public void showAddUserDialog(@Nullable DialogShower dialogShower) {
-        if (mAddUserDialog != null && mAddUserDialog.isShowing()) {
-            mAddUserDialog.cancel();
-        }
-        final UserInfo currentUser = mUserTracker.getUserInfo();
-        mAddUserDialog = new AddUserDialog(
-                mContext,
-                currentUser.getUserHandle(),
-                mKeyguardStateController.isShowing(),
-                /* showEphemeralMessage= */currentUser.isGuest() && currentUser.isEphemeral(),
-                mFalsingManager,
-                mBroadcastSender,
-                mDialogLaunchAnimator);
-        if (dialogShower != null) {
-            dialogShower.showDialog(mAddUserDialog,
-                    new DialogCuj(
-                            InteractionJankMonitor.CUJ_USER_DIALOG_OPEN,
-                            INTERACTION_JANK_ADD_NEW_USER_TAG
-                    ));
-        } else {
-            mAddUserDialog.show();
-        }
-    }
-
-    @Override
-    public void startSupervisedUserActivity() {
-        final Intent intent = new Intent()
-                .setAction(UserManager.ACTION_CREATE_SUPERVISED_USER)
-                .setPackage(mCreateSupervisedUserPackage)
-                .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
-
-        mContext.startActivity(intent);
-    }
-
-    private void listenForCallState() {
-        mTelephonyListenerManager.addCallStateListener(mPhoneStateListener);
-    }
-
-    private final TelephonyCallback.CallStateListener mPhoneStateListener =
-            new TelephonyCallback.CallStateListener() {
-        private int mCallState;
-
-        @Override
-        public void onCallStateChanged(int state) {
-            if (mCallState == state) return;
-            if (DEBUG) Log.v(TAG, "Call state changed: " + state);
-            mCallState = state;
-            refreshUsers(UserHandle.USER_NULL);
-        }
-    };
-
-    private BroadcastReceiver mReceiver = new BroadcastReceiver() {
-        @Override
-        public void onReceive(Context context, Intent intent) {
-            if (DEBUG) {
-                Log.v(TAG, "Broadcast: a=" + intent.getAction()
-                        + " user=" + intent.getIntExtra(Intent.EXTRA_USER_HANDLE, -1));
-            }
-
-            boolean unpauseRefreshUsers = false;
-            int forcePictureLoadForId = UserHandle.USER_NULL;
-
-            if (Intent.ACTION_USER_SWITCHED.equals(intent.getAction())) {
-                if (mExitGuestDialog != null && mExitGuestDialog.isShowing()) {
-                    mExitGuestDialog.cancel();
-                    mExitGuestDialog = null;
-                }
-
-                final int currentId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, -1);
-                final UserInfo userInfo = mUserManager.getUserInfo(currentId);
-                final int userCount = mUsers.size();
-                for (int i = 0; i < userCount; i++) {
-                    UserRecord record = mUsers.get(i);
-                    if (record.info == null) continue;
-                    boolean shouldBeCurrent = record.info.id == currentId;
-                    if (record.isCurrent != shouldBeCurrent) {
-                        mUsers.set(i, record.copyWithIsCurrent(shouldBeCurrent));
-                    }
-                    if (shouldBeCurrent && !record.isGuest) {
-                        mLastNonGuestUser = record.info.id;
-                    }
-                    if ((userInfo == null || !userInfo.isAdmin()) && record.isRestricted) {
-                        // Immediately remove restricted records in case the AsyncTask is too slow.
-                        mUsers.remove(i);
-                        i--;
-                    }
-                }
-                notifyUserSwitchCallbacks();
-                notifyAdapters();
-
-                // Disconnect from the old secondary user's service
-                if (mSecondaryUser != UserHandle.USER_NULL) {
-                    context.stopServiceAsUser(mSecondaryUserServiceIntent,
-                            UserHandle.of(mSecondaryUser));
-                    mSecondaryUser = UserHandle.USER_NULL;
-                }
-                // Connect to the new secondary user's service (purely to ensure that a persistent
-                // SystemUI application is created for that user)
-                if (userInfo != null && userInfo.id != UserHandle.USER_SYSTEM) {
-                    context.startServiceAsUser(mSecondaryUserServiceIntent,
-                            UserHandle.of(userInfo.id));
-                    mSecondaryUser = userInfo.id;
-                }
-                unpauseRefreshUsers = true;
-                if (mGuestUserAutoCreated) {
-                    // Guest user must be scheduled for creation AFTER switching to the target user.
-                    // This avoids lock contention which will produce UX bugs on the keyguard
-                    // (b/193933686).
-                    // TODO(b/191067027): Move guest user recreation to system_server
-                    guaranteeGuestPresent();
-                }
-            } else if (Intent.ACTION_USER_INFO_CHANGED.equals(intent.getAction())) {
-                forcePictureLoadForId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE,
-                        UserHandle.USER_NULL);
-            } else if (Intent.ACTION_USER_UNLOCKED.equals(intent.getAction())) {
-                // Unlocking the system user may require a refresh
-                int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, UserHandle.USER_NULL);
-                if (userId != UserHandle.USER_SYSTEM) {
-                    return;
-                }
-            }
-            refreshUsers(forcePictureLoadForId);
-            if (unpauseRefreshUsers) {
-                mUnpauseRefreshUsers.run();
-            }
-        }
-    };
-
-    private final Runnable mUnpauseRefreshUsers = new Runnable() {
-        @Override
-        public void run() {
-            mHandler.removeCallbacks(this);
-            mPauseRefreshUsers = false;
-            refreshUsers(UserHandle.USER_NULL);
-        }
-    };
-
-    @Override
-    public void dump(PrintWriter pw, String[] args) {
-        pw.println("UserSwitcherController state:");
-        pw.println("  mLastNonGuestUser=" + mLastNonGuestUser);
-        pw.print("  mUsers.size="); pw.println(mUsers.size());
-        for (int i = 0; i < mUsers.size(); i++) {
-            final UserRecord u = mUsers.get(i);
-            pw.print("    "); pw.println(u.toString());
-        }
-        pw.println("mSimpleUserSwitcher=" + mSimpleUserSwitcher);
-        pw.println("mGuestUserAutoCreated=" + mGuestUserAutoCreated);
-    }
-
-    @Override
-    public String getCurrentUserName() {
-        if (mUsers.isEmpty()) return null;
-        UserRecord item = mUsers.stream().filter(x -> x.isCurrent).findFirst().orElse(null);
-        if (item == null || item.info == null) return null;
-        if (item.isGuest) return mContext.getString(com.android.internal.R.string.guest_name);
-        return item.info.name;
-    }
-
-    @Override
-    public void onDensityOrFontScaleChanged() {
-        refreshUsers(UserHandle.USER_ALL);
-    }
-
-    @Override
-    public void addAdapter(WeakReference<BaseUserSwitcherAdapter> adapter) {
-        mAdapters.add(adapter);
-    }
-
-    @Override
-    public ArrayList<UserRecord> getUsers() {
-        return mUsers;
-    }
-
-    @Override
-    public void removeGuestUser(@UserIdInt int guestUserId, @UserIdInt int targetUserId) {
-        UserInfo currentUser = mUserTracker.getUserInfo();
-        if (currentUser.id != guestUserId) {
-            Log.w(TAG, "User requesting to start a new session (" + guestUserId + ")"
-                    + " is not current user (" + currentUser.id + ")");
-            return;
-        }
-        if (!currentUser.isGuest()) {
-            Log.w(TAG, "User requesting to start a new session (" + guestUserId + ")"
-                    + " is not a guest");
-            return;
-        }
-
-        boolean marked = mUserManager.markGuestForDeletion(currentUser.id);
-        if (!marked) {
-            Log.w(TAG, "Couldn't mark the guest for deletion for user " + guestUserId);
-            return;
-        }
-
-        if (targetUserId == UserHandle.USER_NULL) {
-            // Create a new guest in the foreground, and then immediately switch to it
-            createGuestAsync(newGuestId -> {
-                if (newGuestId == UserHandle.USER_NULL) {
-                    Log.e(TAG, "Could not create new guest, switching back to system user");
-                    switchToUserId(UserHandle.USER_SYSTEM);
-                    mUserManager.removeUser(currentUser.id);
-                    try {
-                        WindowManagerGlobal.getWindowManagerService().lockNow(/* options= */ null);
-                    } catch (RemoteException e) {
-                        Log.e(TAG, "Couldn't remove guest because ActivityManager "
-                                + "or WindowManager is dead");
-                    }
-                    return;
-                }
-                switchToUserId(newGuestId);
-                mUserManager.removeUser(currentUser.id);
-            });
-        } else {
-            if (mGuestUserAutoCreated) {
-                mGuestIsResetting.set(true);
-            }
-            switchToUserId(targetUserId);
-            mUserManager.removeUser(currentUser.id);
-        }
-    }
-
-    @Override
-    public void exitGuestUser(@UserIdInt int guestUserId, @UserIdInt int targetUserId,
-            boolean forceRemoveGuestOnExit) {
-        UserInfo currentUser = mUserTracker.getUserInfo();
-        if (currentUser.id != guestUserId) {
-            Log.w(TAG, "User requesting to start a new session (" + guestUserId + ")"
-                    + " is not current user (" + currentUser.id + ")");
-            return;
-        }
-        if (!currentUser.isGuest()) {
-            Log.w(TAG, "User requesting to start a new session (" + guestUserId + ")"
-                    + " is not a guest");
-            return;
-        }
-
-        int newUserId = UserHandle.USER_SYSTEM;
-        if (targetUserId == UserHandle.USER_NULL) {
-            // when target user is not specified switch to last non guest user
-            if (mLastNonGuestUser != UserHandle.USER_SYSTEM) {
-                UserInfo info = mUserManager.getUserInfo(mLastNonGuestUser);
-                if (info != null && info.isEnabled() && info.supportsSwitchToByUser()) {
-                    newUserId = info.id;
-                }
-            }
-        } else {
-            newUserId = targetUserId;
-        }
-
-        if (currentUser.isEphemeral() || forceRemoveGuestOnExit) {
-            mUiEventLogger.log(QSUserSwitcherEvent.QS_USER_GUEST_REMOVE);
-            removeGuestUser(currentUser.id, newUserId);
-        } else {
-            mUiEventLogger.log(QSUserSwitcherEvent.QS_USER_SWITCH);
-            switchToUserId(newUserId);
-        }
-    }
-
-    private void scheduleGuestCreation() {
-        if (!mGuestCreationScheduled.compareAndSet(false, true)) {
-            return;
-        }
-
-        mLongRunningExecutor.execute(() -> {
-            int newGuestId = createGuest();
-            mGuestCreationScheduled.set(false);
-            mGuestIsResetting.set(false);
-            if (newGuestId == UserHandle.USER_NULL) {
-                Log.w(TAG, "Could not create new guest while exiting existing guest");
-                // Refresh users so that we still display "Guest" if
-                // config_guestUserAutoCreated=true
-                refreshUsers(UserHandle.USER_NULL);
-            }
-        });
-
-    }
-
-    @Override
-    public void schedulePostBootGuestCreation() {
-        if (isDeviceAllowedToAddGuest()) {
-            guaranteeGuestPresent();
-        } else {
-            mDeviceProvisionedController.addCallback(mGuaranteeGuestPresentAfterProvisioned);
-        }
-    }
-
-    private boolean isDeviceAllowedToAddGuest() {
-        return mDeviceProvisionedController.isDeviceProvisioned()
-                && !mDevicePolicyManager.isDeviceManaged();
-    }
-
-    /**
-     * If there is no guest on the device, schedule creation of a new guest user in the background.
-     */
-    private void guaranteeGuestPresent() {
-        if (isDeviceAllowedToAddGuest() && mUserManager.findCurrentGuestUser() == null) {
-            scheduleGuestCreation();
-        }
-    }
-
-    private void createGuestAsync(Consumer<Integer> callback) {
-        final Dialog guestCreationProgressDialog =
-                new UserCreatingDialog(mContext, /* isGuest= */true);
-        guestCreationProgressDialog.show();
-
-        // userManager.createGuest will block the thread so post is needed for the dialog to show
-        mBgExecutor.execute(() -> {
-            final int guestId = createGuest();
-            mUiExecutor.execute(() -> {
-                guestCreationProgressDialog.dismiss();
-                if (guestId == UserHandle.USER_NULL) {
-                    Toast.makeText(mContext,
-                            com.android.settingslib.R.string.add_guest_failed,
-                            Toast.LENGTH_SHORT).show();
-                }
-                callback.accept(guestId);
-            });
-        });
-    }
-
-    /**
-     * Creates a guest user and return its multi-user user ID.
-     *
-     * This method does not check if a guest already exists before it makes a call to
-     * {@link UserManager} to create a new one.
-     *
-     * @return The multi-user user ID of the newly created guest user, or
-     * {@link UserHandle#USER_NULL} if the guest couldn't be created.
-     */
-    private @UserIdInt int createGuest() {
-        UserInfo guest;
-        try {
-            guest = mUserManager.createGuest(mContext);
-        } catch (UserManager.UserOperationException e) {
-            Log.e(TAG, "Couldn't create guest user", e);
-            return UserHandle.USER_NULL;
-        }
-        if (guest == null) {
-            Log.e(TAG, "Couldn't create guest, most likely because there already exists one");
-            return UserHandle.USER_NULL;
-        }
-        return guest.id;
-    }
-
-    @Override
-    public void init(View view) {
-        mView = view;
-    }
-
-    @Override
-    public boolean isKeyguardShowing() {
-        return mKeyguardStateController.isShowing();
-    }
-
-    private boolean shouldUseSimpleUserSwitcher() {
-        int defaultSimpleUserSwitcher = mContext.getResources().getBoolean(
-                com.android.internal.R.bool.config_expandLockScreenUserSwitcher) ? 1 : 0;
-        return mGlobalSettings.getIntForUser(SIMPLE_USER_SWITCHER_GLOBAL_SETTING,
-                defaultSimpleUserSwitcher, UserHandle.USER_SYSTEM) != 0;
-    }
-
-    @Override
-    public void startActivity(Intent intent) {
-        mActivityStarter.startActivity(intent, /* dismissShade= */ true);
-    }
-
-    @Override
-    public void addUserSwitchCallback(UserSwitchCallback callback) {
-        mUserSwitchCallbacks.add(callback);
-    }
-
-    @Override
-    public void removeUserSwitchCallback(UserSwitchCallback callback) {
-        mUserSwitchCallbacks.remove(callback);
-    }
-
-    /**
-     *  Notify user switch callbacks that user has switched.
-     */
-    private void notifyUserSwitchCallbacks() {
-        List<UserSwitchCallback> temp;
-        synchronized (mUserSwitchCallbacks) {
-            temp = new ArrayList<>(mUserSwitchCallbacks);
-        }
-        for (UserSwitchCallback callback : temp) {
-            callback.onUserSwitched();
-        }
-    }
-
-    private final KeyguardStateController.Callback mCallback =
-            new KeyguardStateController.Callback() {
-                @Override
-                public void onKeyguardShowingChanged() {
-
-                    // When Keyguard is going away, we don't need to update our items immediately
-                    // which
-                    // helps making the transition faster.
-                    if (!mKeyguardStateController.isShowing()) {
-                        mHandler.post(UserSwitcherControllerOldImpl.this::notifyAdapters);
-                    } else {
-                        notifyAdapters();
-                    }
-                }
-            };
-
-    private final DeviceProvisionedController.DeviceProvisionedListener
-            mGuaranteeGuestPresentAfterProvisioned =
-            new DeviceProvisionedController.DeviceProvisionedListener() {
-                @Override
-                public void onDeviceProvisionedChanged() {
-                    if (isDeviceAllowedToAddGuest()) {
-                        mBgExecutor.execute(
-                                () -> mDeviceProvisionedController.removeCallback(
-                                        mGuaranteeGuestPresentAfterProvisioned));
-                        guaranteeGuestPresent();
-                    }
-                }
-            };
-}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/ZenModeControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ZenModeControllerImpl.java
index 9866389..b135d0d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/ZenModeControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ZenModeControllerImpl.java
@@ -16,7 +16,6 @@
 
 package com.android.systemui.statusbar.policy;
 
-import android.app.ActivityManager;
 import android.app.AlarmManager;
 import android.app.NotificationManager;
 import android.content.BroadcastReceiver;
@@ -28,6 +27,7 @@
 import android.database.ContentObserver;
 import android.net.Uri;
 import android.os.Handler;
+import android.os.HandlerExecutor;
 import android.os.UserHandle;
 import android.os.UserManager;
 import android.provider.Settings.Global;
@@ -46,7 +46,7 @@
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.dump.DumpManager;
 import com.android.systemui.qs.SettingObserver;
-import com.android.systemui.settings.CurrentUserTracker;
+import com.android.systemui.settings.UserTracker;
 import com.android.systemui.util.Utils;
 import com.android.systemui.util.settings.GlobalSettings;
 
@@ -58,14 +58,15 @@
 
 /** Platform implementation of the zen mode controller. **/
 @SysUISingleton
-public class ZenModeControllerImpl extends CurrentUserTracker
-        implements ZenModeController, Dumpable {
+public class ZenModeControllerImpl implements ZenModeController, Dumpable {
     private static final String TAG = "ZenModeController";
     private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
 
     private final ArrayList<Callback> mCallbacks = new ArrayList<>();
     private final Object mCallbacksLock = new Object();
     private final Context mContext;
+    private final UserTracker mUserTracker;
+    private final BroadcastDispatcher mBroadcastDispatcher;
     private final SettingObserver mModeSetting;
     private final SettingObserver mConfigSetting;
     private final NotificationManager mNoMan;
@@ -80,23 +81,45 @@
     private long mZenUpdateTime;
     private NotificationManager.Policy mConsolidatedNotificationPolicy;
 
+    private final UserTracker.Callback mUserChangedCallback =
+            new UserTracker.Callback() {
+                @Override
+                public void onUserChanged(int newUser, Context userContext) {
+                    mUserId = newUser;
+                    if (mRegistered) {
+                        mBroadcastDispatcher.unregisterReceiver(mReceiver);
+                    }
+                    final IntentFilter filter = new IntentFilter(
+                            AlarmManager.ACTION_NEXT_ALARM_CLOCK_CHANGED);
+                    filter.addAction(NotificationManager.ACTION_EFFECTS_SUPPRESSOR_CHANGED);
+                    mBroadcastDispatcher.registerReceiver(mReceiver, filter, null,
+                            UserHandle.of(mUserId));
+                    mRegistered = true;
+                    mSetupObserver.register();
+                }
+            };
+
     @Inject
     public ZenModeControllerImpl(
             Context context,
             @Main Handler handler,
             BroadcastDispatcher broadcastDispatcher,
             DumpManager dumpManager,
-            GlobalSettings globalSettings) {
-        super(broadcastDispatcher);
+            GlobalSettings globalSettings,
+            UserTracker userTracker) {
         mContext = context;
-        mModeSetting = new SettingObserver(globalSettings, handler, Global.ZEN_MODE) {
+        mBroadcastDispatcher = broadcastDispatcher;
+        mUserTracker = userTracker;
+        mModeSetting = new SettingObserver(globalSettings, handler, Global.ZEN_MODE,
+                userTracker.getUserId()) {
             @Override
             protected void handleValueChanged(int value, boolean observedChange) {
                 updateZenMode(value);
                 fireZenChanged(value);
             }
         };
-        mConfigSetting = new SettingObserver(globalSettings, handler, Global.ZEN_MODE_CONFIG_ETAG) {
+        mConfigSetting = new SettingObserver(globalSettings, handler, Global.ZEN_MODE_CONFIG_ETAG,
+                userTracker.getUserId()) {
             @Override
             protected void handleValueChanged(int value, boolean observedChange) {
                 updateZenModeConfig();
@@ -112,7 +135,7 @@
         mSetupObserver = new SetupObserver(handler);
         mSetupObserver.register();
         mUserManager = context.getSystemService(UserManager.class);
-        startTracking();
+        mUserTracker.addCallback(mUserChangedCallback, new HandlerExecutor(handler));
 
         dumpManager.registerDumpable(getClass().getSimpleName(), this);
     }
@@ -120,7 +143,7 @@
     @Override
     public boolean isVolumeRestricted() {
         return mUserManager.hasUserRestriction(UserManager.DISALLOW_ADJUST_VOLUME,
-                new UserHandle(mUserId));
+                UserHandle.of(mUserId));
     }
 
     @Override
@@ -183,19 +206,6 @@
     }
 
     @Override
-    public void onUserSwitched(int userId) {
-        mUserId = userId;
-        if (mRegistered) {
-            mContext.unregisterReceiver(mReceiver);
-        }
-        final IntentFilter filter = new IntentFilter(AlarmManager.ACTION_NEXT_ALARM_CLOCK_CHANGED);
-        filter.addAction(NotificationManager.ACTION_EFFECTS_SUPPRESSOR_CHANGED);
-        mContext.registerReceiverAsUser(mReceiver, new UserHandle(mUserId), filter, null, null);
-        mRegistered = true;
-        mSetupObserver.register();
-    }
-
-    @Override
     public ComponentName getEffectsSuppressor() {
         return NotificationManager.from(mContext).getEffectsSuppressor();
     }
@@ -208,7 +218,7 @@
 
     @Override
     public int getCurrentUser() {
-        return ActivityManager.getCurrentUser();
+        return mUserTracker.getUserId();
     }
 
     private void fireNextAlarmChanged() {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/dagger/StatusBarPolicyModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/dagger/StatusBarPolicyModule.java
index b1b45b5..1b73539 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/dagger/StatusBarPolicyModule.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/dagger/StatusBarPolicyModule.java
@@ -58,8 +58,6 @@
 import com.android.systemui.statusbar.policy.SecurityControllerImpl;
 import com.android.systemui.statusbar.policy.UserInfoController;
 import com.android.systemui.statusbar.policy.UserInfoControllerImpl;
-import com.android.systemui.statusbar.policy.UserSwitcherController;
-import com.android.systemui.statusbar.policy.UserSwitcherControllerImpl;
 import com.android.systemui.statusbar.policy.WalletController;
 import com.android.systemui.statusbar.policy.WalletControllerImpl;
 import com.android.systemui.statusbar.policy.ZenModeController;
@@ -198,8 +196,4 @@
     static DataSaverController provideDataSaverController(NetworkController networkController) {
         return networkController.getDataSaverController();
     }
-
-    /** Binds {@link UserSwitcherController} to its implementation. */
-    @Binds
-    UserSwitcherController bindUserSwitcherController(UserSwitcherControllerImpl impl);
 }
diff --git a/packages/SystemUI/src/com/android/systemui/user/data/repository/UserRepository.kt b/packages/SystemUI/src/com/android/systemui/user/data/repository/UserRepository.kt
index ffaf524..ed53de7 100644
--- a/packages/SystemUI/src/com/android/systemui/user/data/repository/UserRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/user/data/repository/UserRepository.kt
@@ -19,31 +19,18 @@
 
 import android.content.Context
 import android.content.pm.UserInfo
-import android.graphics.drawable.BitmapDrawable
-import android.graphics.drawable.Drawable
 import android.os.UserHandle
 import android.os.UserManager
 import android.provider.Settings
 import androidx.annotation.VisibleForTesting
-import androidx.appcompat.content.res.AppCompatResources
-import com.android.internal.util.UserIcons
-import com.android.systemui.R
 import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging
 import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
-import com.android.systemui.common.shared.model.Text
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.dagger.qualifiers.Background
 import com.android.systemui.dagger.qualifiers.Main
-import com.android.systemui.flags.FeatureFlags
-import com.android.systemui.flags.Flags
 import com.android.systemui.settings.UserTracker
-import com.android.systemui.statusbar.policy.UserSwitcherController
 import com.android.systemui.user.data.model.UserSwitcherSettingsModel
-import com.android.systemui.user.data.source.UserRecord
-import com.android.systemui.user.legacyhelper.ui.LegacyUserUiHelper
-import com.android.systemui.user.shared.model.UserActionModel
-import com.android.systemui.user.shared.model.UserModel
 import com.android.systemui.util.settings.GlobalSettings
 import com.android.systemui.util.settings.SettingsProxyExt.observerFlow
 import java.util.concurrent.atomic.AtomicBoolean
@@ -55,7 +42,6 @@
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.asStateFlow
-import kotlinx.coroutines.flow.emptyFlow
 import kotlinx.coroutines.flow.filterNotNull
 import kotlinx.coroutines.flow.launchIn
 import kotlinx.coroutines.flow.map
@@ -72,15 +58,6 @@
  * upstream changes.
  */
 interface UserRepository {
-    /** List of all users on the device. */
-    val users: Flow<List<UserModel>>
-
-    /** The currently-selected user. */
-    val selectedUser: Flow<UserModel>
-
-    /** List of available user-related actions. */
-    val actions: Flow<List<UserActionModel>>
-
     /** User switcher related settings. */
     val userSwitcherSettings: Flow<UserSwitcherSettingsModel>
 
@@ -93,9 +70,6 @@
     /** User ID of the last non-guest selected user. */
     val lastSelectedNonGuestUserId: Int
 
-    /** Whether actions are available even when locked. */
-    val isActionableWhenLocked: Flow<Boolean>
-
     /** Whether the device is configured to always have a guest user available. */
     val isGuestUserAutoCreated: Boolean
 
@@ -125,18 +99,13 @@
 constructor(
     @Application private val appContext: Context,
     private val manager: UserManager,
-    private val controller: UserSwitcherController,
     @Application private val applicationScope: CoroutineScope,
     @Main private val mainDispatcher: CoroutineDispatcher,
     @Background private val backgroundDispatcher: CoroutineDispatcher,
     private val globalSettings: GlobalSettings,
     private val tracker: UserTracker,
-    private val featureFlags: FeatureFlags,
 ) : UserRepository {
 
-    private val isNewImpl: Boolean
-        get() = !featureFlags.isEnabled(Flags.USER_INTERACTOR_AND_REPO_USE_CONTROLLER)
-
     private val _userSwitcherSettings = MutableStateFlow(runBlocking { getSettings() })
     override val userSwitcherSettings: Flow<UserSwitcherSettingsModel> =
         _userSwitcherSettings.asStateFlow().filterNotNull()
@@ -150,58 +119,11 @@
     override var lastSelectedNonGuestUserId: Int = UserHandle.USER_SYSTEM
         private set
 
-    private val userRecords: Flow<List<UserRecord>> = conflatedCallbackFlow {
-        fun send() {
-            trySendWithFailureLogging(
-                controller.users,
-                TAG,
-            )
-        }
-
-        val callback = UserSwitcherController.UserSwitchCallback { send() }
-
-        controller.addUserSwitchCallback(callback)
-        send()
-
-        awaitClose { controller.removeUserSwitchCallback(callback) }
-    }
-
-    override val users: Flow<List<UserModel>> =
-        userRecords.map { records -> records.filter { it.isUser() }.map { it.toUserModel() } }
-
-    override val selectedUser: Flow<UserModel> =
-        users.map { users -> users.first { user -> user.isSelected } }
-
-    override val actions: Flow<List<UserActionModel>> =
-        userRecords.map { records -> records.filter { it.isNotUser() }.map { it.toActionModel() } }
-
-    override val isActionableWhenLocked: Flow<Boolean> =
-        if (isNewImpl) {
-            emptyFlow()
-        } else {
-            controller.isAddUsersFromLockScreenEnabled
-        }
-
     override val isGuestUserAutoCreated: Boolean =
-        if (isNewImpl) {
-            appContext.resources.getBoolean(com.android.internal.R.bool.config_guestUserAutoCreated)
-        } else {
-            controller.isGuestUserAutoCreated
-        }
+        appContext.resources.getBoolean(com.android.internal.R.bool.config_guestUserAutoCreated)
 
     private var _isGuestUserResetting: Boolean = false
-    override var isGuestUserResetting: Boolean =
-        if (isNewImpl) {
-            _isGuestUserResetting
-        } else {
-            controller.isGuestUserResetting
-        }
-        set(value) =
-            if (isNewImpl) {
-                _isGuestUserResetting = value
-            } else {
-                error("Not supported in the old implementation!")
-            }
+    override var isGuestUserResetting: Boolean = _isGuestUserResetting
 
     override val isGuestUserCreationScheduled = AtomicBoolean()
 
@@ -210,10 +132,8 @@
     override var isRefreshUsersPaused: Boolean = false
 
     init {
-        if (isNewImpl) {
-            observeSelectedUser()
-            observeUserSettings()
-        }
+        observeSelectedUser()
+        observeUserSettings()
     }
 
     override fun refreshUsers() {
@@ -327,64 +247,6 @@
         }
     }
 
-    private fun UserRecord.isUser(): Boolean {
-        return when {
-            isAddUser -> false
-            isAddSupervisedUser -> false
-            isManageUsers -> false
-            isGuest -> info != null
-            else -> true
-        }
-    }
-
-    private fun UserRecord.isNotUser(): Boolean {
-        return !isUser()
-    }
-
-    private fun UserRecord.toUserModel(): UserModel {
-        return UserModel(
-            id = resolveId(),
-            name = getUserName(this),
-            image = getUserImage(this),
-            isSelected = isCurrent,
-            isSelectable = isSwitchToEnabled || isGuest,
-            isGuest = isGuest,
-        )
-    }
-
-    private fun UserRecord.toActionModel(): UserActionModel {
-        return when {
-            isAddUser -> UserActionModel.ADD_USER
-            isAddSupervisedUser -> UserActionModel.ADD_SUPERVISED_USER
-            isGuest -> UserActionModel.ENTER_GUEST_MODE
-            isManageUsers -> UserActionModel.NAVIGATE_TO_USER_MANAGEMENT
-            else -> error("Don't know how to convert to UserActionModel: $this")
-        }
-    }
-
-    private fun getUserName(record: UserRecord): Text {
-        val resourceId: Int? = LegacyUserUiHelper.getGuestUserRecordNameResourceId(record)
-        return if (resourceId != null) {
-            Text.Resource(resourceId)
-        } else {
-            Text.Loaded(checkNotNull(record.info).name)
-        }
-    }
-
-    private fun getUserImage(record: UserRecord): Drawable {
-        if (record.isGuest) {
-            return checkNotNull(
-                AppCompatResources.getDrawable(appContext, R.drawable.ic_account_circle)
-            )
-        }
-
-        val userId = checkNotNull(record.info?.id)
-        return manager.getUserIcon(userId)?.let { userSelectedIcon ->
-            BitmapDrawable(userSelectedIcon)
-        }
-            ?: UserIcons.getDefaultUserIcon(appContext.resources, userId, /* light= */ false)
-    }
-
     companion object {
         private const val TAG = "UserRepository"
         @VisibleForTesting const val SETTING_SIMPLE_USER_SWITCHER = "lockscreenSimpleUserSwitcher"
diff --git a/packages/SystemUI/src/com/android/systemui/user/domain/interactor/UserInteractor.kt b/packages/SystemUI/src/com/android/systemui/user/domain/interactor/UserInteractor.kt
index dda78aa..516c650 100644
--- a/packages/SystemUI/src/com/android/systemui/user/domain/interactor/UserInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/user/domain/interactor/UserInteractor.kt
@@ -39,13 +39,11 @@
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.dagger.qualifiers.Background
-import com.android.systemui.flags.FeatureFlags
-import com.android.systemui.flags.Flags
 import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
 import com.android.systemui.plugins.ActivityStarter
 import com.android.systemui.qs.user.UserSwitchDialogController
-import com.android.systemui.statusbar.policy.UserSwitcherController
 import com.android.systemui.telephony.domain.interactor.TelephonyInteractor
+import com.android.systemui.user.data.model.UserSwitcherSettingsModel
 import com.android.systemui.user.data.repository.UserRepository
 import com.android.systemui.user.data.source.UserRecord
 import com.android.systemui.user.domain.model.ShowDialogRequestModel
@@ -64,8 +62,7 @@
 import kotlinx.coroutines.flow.asStateFlow
 import kotlinx.coroutines.flow.combine
 import kotlinx.coroutines.flow.distinctUntilChanged
-import kotlinx.coroutines.flow.flatMapLatest
-import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.flow.filter
 import kotlinx.coroutines.flow.launchIn
 import kotlinx.coroutines.flow.map
 import kotlinx.coroutines.flow.onEach
@@ -82,10 +79,8 @@
 constructor(
     @Application private val applicationContext: Context,
     private val repository: UserRepository,
-    private val controller: UserSwitcherController,
     private val activityStarter: ActivityStarter,
     private val keyguardInteractor: KeyguardInteractor,
-    private val featureFlags: FeatureFlags,
     private val manager: UserManager,
     @Application private val applicationScope: CoroutineScope,
     telephonyInteractor: TelephonyInteractor,
@@ -107,9 +102,6 @@
         fun onUserStateChanged()
     }
 
-    private val isNewImpl: Boolean
-        get() = !featureFlags.isEnabled(Flags.USER_INTERACTOR_AND_REPO_USE_CONTROLLER)
-
     private val supervisedUserPackageName: String?
         get() =
             applicationContext.getString(
@@ -118,185 +110,145 @@
 
     private val callbackMutex = Mutex()
     private val callbacks = mutableSetOf<UserCallback>()
+    private val userInfos =
+        combine(repository.userSwitcherSettings, repository.userInfos) { settings, userInfos ->
+            userInfos.filter { !it.isGuest || canCreateGuestUser(settings) }
+        }
 
     /** List of current on-device users to select from. */
     val users: Flow<List<UserModel>>
         get() =
-            if (isNewImpl) {
-                combine(
-                    repository.userInfos,
-                    repository.selectedUserInfo,
-                    repository.userSwitcherSettings,
-                ) { userInfos, selectedUserInfo, settings ->
-                    toUserModels(
-                        userInfos = userInfos,
-                        selectedUserId = selectedUserInfo.id,
-                        isUserSwitcherEnabled = settings.isUserSwitcherEnabled,
-                    )
-                }
-            } else {
-                repository.users
+            combine(
+                userInfos,
+                repository.selectedUserInfo,
+                repository.userSwitcherSettings,
+            ) { userInfos, selectedUserInfo, settings ->
+                toUserModels(
+                    userInfos = userInfos,
+                    selectedUserId = selectedUserInfo.id,
+                    isUserSwitcherEnabled = settings.isUserSwitcherEnabled,
+                )
             }
 
     /** The currently-selected user. */
     val selectedUser: Flow<UserModel>
         get() =
-            if (isNewImpl) {
-                combine(
-                    repository.selectedUserInfo,
-                    repository.userSwitcherSettings,
-                ) { selectedUserInfo, settings ->
-                    val selectedUserId = selectedUserInfo.id
-                    checkNotNull(
-                        toUserModel(
-                            userInfo = selectedUserInfo,
-                            selectedUserId = selectedUserId,
-                            canSwitchUsers = canSwitchUsers(selectedUserId),
-                            isUserSwitcherEnabled = settings.isUserSwitcherEnabled,
-                        )
+            combine(
+                repository.selectedUserInfo,
+                repository.userSwitcherSettings,
+            ) { selectedUserInfo, settings ->
+                val selectedUserId = selectedUserInfo.id
+                checkNotNull(
+                    toUserModel(
+                        userInfo = selectedUserInfo,
+                        selectedUserId = selectedUserId,
+                        canSwitchUsers = canSwitchUsers(selectedUserId),
+                        isUserSwitcherEnabled = settings.isUserSwitcherEnabled,
                     )
-                }
-            } else {
-                repository.selectedUser
+                )
             }
 
     /** List of user-switcher related actions that are available. */
     val actions: Flow<List<UserActionModel>>
         get() =
-            if (isNewImpl) {
-                combine(
-                    repository.selectedUserInfo,
-                    repository.userInfos,
-                    repository.userSwitcherSettings,
-                    keyguardInteractor.isKeyguardShowing,
-                ) { _, userInfos, settings, isDeviceLocked ->
-                    buildList {
-                        val hasGuestUser = userInfos.any { it.isGuest }
-                        if (
-                            !hasGuestUser &&
-                                (guestUserInteractor.isGuestUserAutoCreated ||
-                                    UserActionsUtil.canCreateGuest(
-                                        manager,
-                                        repository,
-                                        settings.isUserSwitcherEnabled,
-                                        settings.isAddUsersFromLockscreen,
-                                    ))
-                        ) {
-                            add(UserActionModel.ENTER_GUEST_MODE)
-                        }
+            combine(
+                repository.selectedUserInfo,
+                userInfos,
+                repository.userSwitcherSettings,
+                keyguardInteractor.isKeyguardShowing,
+            ) { _, userInfos, settings, isDeviceLocked ->
+                buildList {
+                    val hasGuestUser = userInfos.any { it.isGuest }
+                    if (!hasGuestUser && canCreateGuestUser(settings)) {
+                        add(UserActionModel.ENTER_GUEST_MODE)
+                    }
 
-                        if (!isDeviceLocked || settings.isAddUsersFromLockscreen) {
-                            // The device is locked and our setting to allow actions that add users
-                            // from the lock-screen is not enabled. The guest action from above is
-                            // always allowed, even when the device is locked, but the various "add
-                            // user" actions below are not. We can finish building the list here.
+                    if (!isDeviceLocked || settings.isAddUsersFromLockscreen) {
+                        // The device is locked and our setting to allow actions that add users
+                        // from the lock-screen is not enabled. The guest action from above is
+                        // always allowed, even when the device is locked, but the various "add
+                        // user" actions below are not. We can finish building the list here.
 
-                            val canCreateUsers =
-                                UserActionsUtil.canCreateUser(
-                                    manager,
-                                    repository,
-                                    settings.isUserSwitcherEnabled,
-                                    settings.isAddUsersFromLockscreen,
-                                )
-
-                            if (canCreateUsers) {
-                                add(UserActionModel.ADD_USER)
-                            }
-
-                            if (
-                                UserActionsUtil.canCreateSupervisedUser(
-                                    manager,
-                                    repository,
-                                    settings.isUserSwitcherEnabled,
-                                    settings.isAddUsersFromLockscreen,
-                                    supervisedUserPackageName,
-                                )
-                            ) {
-                                add(UserActionModel.ADD_SUPERVISED_USER)
-                            }
-                        }
-
-                        if (
-                            UserActionsUtil.canManageUsers(
+                        val canCreateUsers =
+                            UserActionsUtil.canCreateUser(
+                                manager,
                                 repository,
                                 settings.isUserSwitcherEnabled,
                                 settings.isAddUsersFromLockscreen,
                             )
-                        ) {
-                            add(UserActionModel.NAVIGATE_TO_USER_MANAGEMENT)
+
+                        if (canCreateUsers) {
+                            add(UserActionModel.ADD_USER)
                         }
+
+                        if (
+                            UserActionsUtil.canCreateSupervisedUser(
+                                manager,
+                                repository,
+                                settings.isUserSwitcherEnabled,
+                                settings.isAddUsersFromLockscreen,
+                                supervisedUserPackageName,
+                            )
+                        ) {
+                            add(UserActionModel.ADD_SUPERVISED_USER)
+                        }
+                    }
+
+                    if (
+                        UserActionsUtil.canManageUsers(
+                            repository,
+                            settings.isUserSwitcherEnabled,
+                            settings.isAddUsersFromLockscreen,
+                        )
+                    ) {
+                        add(UserActionModel.NAVIGATE_TO_USER_MANAGEMENT)
                     }
                 }
-            } else {
-                combine(
-                        repository.isActionableWhenLocked,
-                        keyguardInteractor.isKeyguardShowing,
-                    ) { isActionableWhenLocked, isLocked ->
-                        isActionableWhenLocked || !isLocked
-                    }
-                    .flatMapLatest { isActionable ->
-                        if (isActionable) {
-                            repository.actions
-                        } else {
-                            // If not actionable it means that we're not allowed to show actions
-                            // when
-                            // locked and we are locked. Therefore, we should show no actions.
-                            flowOf(emptyList())
-                        }
-                    }
             }
 
     val userRecords: StateFlow<ArrayList<UserRecord>> =
-        if (isNewImpl) {
-            combine(
-                    repository.userInfos,
-                    repository.selectedUserInfo,
-                    actions,
-                    repository.userSwitcherSettings,
-                ) { userInfos, selectedUserInfo, actionModels, settings ->
-                    ArrayList(
-                        userInfos.map {
+        combine(
+                userInfos,
+                repository.selectedUserInfo,
+                actions,
+                repository.userSwitcherSettings,
+            ) { userInfos, selectedUserInfo, actionModels, settings ->
+                ArrayList(
+                    userInfos.map {
+                        toRecord(
+                            userInfo = it,
+                            selectedUserId = selectedUserInfo.id,
+                        )
+                    } +
+                        actionModels.map {
                             toRecord(
-                                userInfo = it,
+                                action = it,
                                 selectedUserId = selectedUserInfo.id,
+                                isRestricted =
+                                    it != UserActionModel.ENTER_GUEST_MODE &&
+                                        it != UserActionModel.NAVIGATE_TO_USER_MANAGEMENT &&
+                                        !settings.isAddUsersFromLockscreen,
                             )
-                        } +
-                            actionModels.map {
-                                toRecord(
-                                    action = it,
-                                    selectedUserId = selectedUserInfo.id,
-                                    isRestricted =
-                                        it != UserActionModel.ENTER_GUEST_MODE &&
-                                            it != UserActionModel.NAVIGATE_TO_USER_MANAGEMENT &&
-                                            !settings.isAddUsersFromLockscreen,
-                                )
-                            }
-                    )
-                }
-                .onEach { notifyCallbacks() }
-                .stateIn(
-                    scope = applicationScope,
-                    started = SharingStarted.Eagerly,
-                    initialValue = ArrayList(),
+                        }
                 )
-        } else {
-            MutableStateFlow(ArrayList())
-        }
+            }
+            .onEach { notifyCallbacks() }
+            .stateIn(
+                scope = applicationScope,
+                started = SharingStarted.Eagerly,
+                initialValue = ArrayList(),
+            )
 
     val selectedUserRecord: StateFlow<UserRecord?> =
-        if (isNewImpl) {
-            repository.selectedUserInfo
-                .map { selectedUserInfo ->
-                    toRecord(userInfo = selectedUserInfo, selectedUserId = selectedUserInfo.id)
-                }
-                .stateIn(
-                    scope = applicationScope,
-                    started = SharingStarted.Eagerly,
-                    initialValue = null,
-                )
-        } else {
-            MutableStateFlow(null)
-        }
+        repository.selectedUserInfo
+            .map { selectedUserInfo ->
+                toRecord(userInfo = selectedUserInfo, selectedUserId = selectedUserInfo.id)
+            }
+            .stateIn(
+                scope = applicationScope,
+                started = SharingStarted.Eagerly,
+                initialValue = null,
+            )
 
     /** Whether the device is configured to always have a guest user available. */
     val isGuestUserAutoCreated: Boolean = guestUserInteractor.isGuestUserAutoCreated
@@ -311,44 +263,37 @@
     val dialogDismissRequests: Flow<Unit?> = _dialogDismissRequests.asStateFlow()
 
     val isSimpleUserSwitcher: Boolean
-        get() =
-            if (isNewImpl) {
-                repository.isSimpleUserSwitcher()
-            } else {
-                error("Not supported in the old implementation!")
-            }
+        get() = repository.isSimpleUserSwitcher()
 
     init {
-        if (isNewImpl) {
-            refreshUsersScheduler.refreshIfNotPaused()
-            telephonyInteractor.callState
-                .distinctUntilChanged()
-                .onEach { refreshUsersScheduler.refreshIfNotPaused() }
-                .launchIn(applicationScope)
+        refreshUsersScheduler.refreshIfNotPaused()
+        telephonyInteractor.callState
+            .distinctUntilChanged()
+            .onEach { refreshUsersScheduler.refreshIfNotPaused() }
+            .launchIn(applicationScope)
 
-            combine(
-                    broadcastDispatcher.broadcastFlow(
-                        filter =
-                            IntentFilter().apply {
-                                addAction(Intent.ACTION_USER_ADDED)
-                                addAction(Intent.ACTION_USER_REMOVED)
-                                addAction(Intent.ACTION_USER_INFO_CHANGED)
-                                addAction(Intent.ACTION_USER_SWITCHED)
-                                addAction(Intent.ACTION_USER_STOPPED)
-                                addAction(Intent.ACTION_USER_UNLOCKED)
-                            },
-                        user = UserHandle.SYSTEM,
-                        map = { intent, _ -> intent },
-                    ),
-                    repository.selectedUserInfo.pairwise(null),
-                ) { intent, selectedUserChange ->
-                    Pair(intent, selectedUserChange.previousValue)
-                }
-                .onEach { (intent, previousSelectedUser) ->
-                    onBroadcastReceived(intent, previousSelectedUser)
-                }
-                .launchIn(applicationScope)
-        }
+        combine(
+                broadcastDispatcher.broadcastFlow(
+                    filter =
+                        IntentFilter().apply {
+                            addAction(Intent.ACTION_USER_ADDED)
+                            addAction(Intent.ACTION_USER_REMOVED)
+                            addAction(Intent.ACTION_USER_INFO_CHANGED)
+                            addAction(Intent.ACTION_USER_SWITCHED)
+                            addAction(Intent.ACTION_USER_STOPPED)
+                            addAction(Intent.ACTION_USER_UNLOCKED)
+                        },
+                    user = UserHandle.SYSTEM,
+                    map = { intent, _ -> intent },
+                ),
+                repository.selectedUserInfo.pairwise(null),
+            ) { intent, selectedUserChange ->
+                Pair(intent, selectedUserChange.previousValue)
+            }
+            .onEach { (intent, previousSelectedUser) ->
+                onBroadcastReceived(intent, previousSelectedUser)
+            }
+            .launchIn(applicationScope)
     }
 
     fun addCallback(callback: UserCallback) {
@@ -414,48 +359,43 @@
         newlySelectedUserId: Int,
         dialogShower: UserSwitchDialogController.DialogShower? = null,
     ) {
-        if (isNewImpl) {
-            val currentlySelectedUserInfo = repository.getSelectedUserInfo()
-            if (
-                newlySelectedUserId == currentlySelectedUserInfo.id &&
-                    currentlySelectedUserInfo.isGuest
-            ) {
-                // Here when clicking on the currently-selected guest user to leave guest mode
-                // and return to the previously-selected non-guest user.
-                showDialog(
-                    ShowDialogRequestModel.ShowExitGuestDialog(
-                        guestUserId = currentlySelectedUserInfo.id,
-                        targetUserId = repository.lastSelectedNonGuestUserId,
-                        isGuestEphemeral = currentlySelectedUserInfo.isEphemeral,
-                        isKeyguardShowing = keyguardInteractor.isKeyguardShowing(),
-                        onExitGuestUser = this::exitGuestUser,
-                        dialogShower = dialogShower,
-                    )
+        val currentlySelectedUserInfo = repository.getSelectedUserInfo()
+        if (
+            newlySelectedUserId == currentlySelectedUserInfo.id && currentlySelectedUserInfo.isGuest
+        ) {
+            // Here when clicking on the currently-selected guest user to leave guest mode
+            // and return to the previously-selected non-guest user.
+            showDialog(
+                ShowDialogRequestModel.ShowExitGuestDialog(
+                    guestUserId = currentlySelectedUserInfo.id,
+                    targetUserId = repository.lastSelectedNonGuestUserId,
+                    isGuestEphemeral = currentlySelectedUserInfo.isEphemeral,
+                    isKeyguardShowing = keyguardInteractor.isKeyguardShowing(),
+                    onExitGuestUser = this::exitGuestUser,
+                    dialogShower = dialogShower,
                 )
-                return
-            }
-
-            if (currentlySelectedUserInfo.isGuest) {
-                // Here when switching from guest to a non-guest user.
-                showDialog(
-                    ShowDialogRequestModel.ShowExitGuestDialog(
-                        guestUserId = currentlySelectedUserInfo.id,
-                        targetUserId = newlySelectedUserId,
-                        isGuestEphemeral = currentlySelectedUserInfo.isEphemeral,
-                        isKeyguardShowing = keyguardInteractor.isKeyguardShowing(),
-                        onExitGuestUser = this::exitGuestUser,
-                        dialogShower = dialogShower,
-                    )
-                )
-                return
-            }
-
-            dialogShower?.dismiss()
-
-            switchUser(newlySelectedUserId)
-        } else {
-            controller.onUserSelected(newlySelectedUserId, dialogShower)
+            )
+            return
         }
+
+        if (currentlySelectedUserInfo.isGuest) {
+            // Here when switching from guest to a non-guest user.
+            showDialog(
+                ShowDialogRequestModel.ShowExitGuestDialog(
+                    guestUserId = currentlySelectedUserInfo.id,
+                    targetUserId = newlySelectedUserId,
+                    isGuestEphemeral = currentlySelectedUserInfo.isEphemeral,
+                    isKeyguardShowing = keyguardInteractor.isKeyguardShowing(),
+                    onExitGuestUser = this::exitGuestUser,
+                    dialogShower = dialogShower,
+                )
+            )
+            return
+        }
+
+        dialogShower?.dismiss()
+
+        switchUser(newlySelectedUserId)
     }
 
     /** Executes the given action. */
@@ -463,51 +403,38 @@
         action: UserActionModel,
         dialogShower: UserSwitchDialogController.DialogShower? = null,
     ) {
-        if (isNewImpl) {
-            when (action) {
-                UserActionModel.ENTER_GUEST_MODE ->
-                    guestUserInteractor.createAndSwitchTo(
-                        this::showDialog,
-                        this::dismissDialog,
-                    ) { userId ->
-                        selectUser(userId, dialogShower)
-                    }
-                UserActionModel.ADD_USER -> {
-                    val currentUser = repository.getSelectedUserInfo()
-                    showDialog(
-                        ShowDialogRequestModel.ShowAddUserDialog(
-                            userHandle = currentUser.userHandle,
-                            isKeyguardShowing = keyguardInteractor.isKeyguardShowing(),
-                            showEphemeralMessage = currentUser.isGuest && currentUser.isEphemeral,
-                            dialogShower = dialogShower,
-                        )
-                    )
+        when (action) {
+            UserActionModel.ENTER_GUEST_MODE ->
+                guestUserInteractor.createAndSwitchTo(
+                    this::showDialog,
+                    this::dismissDialog,
+                ) { userId ->
+                    selectUser(userId, dialogShower)
                 }
-                UserActionModel.ADD_SUPERVISED_USER ->
-                    activityStarter.startActivity(
-                        Intent()
-                            .setAction(UserManager.ACTION_CREATE_SUPERVISED_USER)
-                            .setPackage(supervisedUserPackageName)
-                            .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK),
-                        /* dismissShade= */ true,
+            UserActionModel.ADD_USER -> {
+                val currentUser = repository.getSelectedUserInfo()
+                showDialog(
+                    ShowDialogRequestModel.ShowAddUserDialog(
+                        userHandle = currentUser.userHandle,
+                        isKeyguardShowing = keyguardInteractor.isKeyguardShowing(),
+                        showEphemeralMessage = currentUser.isGuest && currentUser.isEphemeral,
+                        dialogShower = dialogShower,
                     )
-                UserActionModel.NAVIGATE_TO_USER_MANAGEMENT ->
-                    activityStarter.startActivity(
-                        Intent(Settings.ACTION_USER_SETTINGS),
-                        /* dismissShade= */ true,
-                    )
+                )
             }
-        } else {
-            when (action) {
-                UserActionModel.ENTER_GUEST_MODE -> controller.createAndSwitchToGuestUser(null)
-                UserActionModel.ADD_USER -> controller.showAddUserDialog(null)
-                UserActionModel.ADD_SUPERVISED_USER -> controller.startSupervisedUserActivity()
-                UserActionModel.NAVIGATE_TO_USER_MANAGEMENT ->
-                    activityStarter.startActivity(
-                        Intent(Settings.ACTION_USER_SETTINGS),
-                        /* dismissShade= */ false,
-                    )
-            }
+            UserActionModel.ADD_SUPERVISED_USER ->
+                activityStarter.startActivity(
+                    Intent()
+                        .setAction(UserManager.ACTION_CREATE_SUPERVISED_USER)
+                        .setPackage(supervisedUserPackageName)
+                        .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK),
+                    /* dismissShade= */ true,
+                )
+            UserActionModel.NAVIGATE_TO_USER_MANAGEMENT ->
+                activityStarter.startActivity(
+                    Intent(Settings.ACTION_USER_SETTINGS),
+                    /* dismissShade= */ true,
+                )
         }
     }
 
@@ -757,6 +684,16 @@
         )
     }
 
+    private fun canCreateGuestUser(settings: UserSwitcherSettingsModel): Boolean {
+        return guestUserInteractor.isGuestUserAutoCreated ||
+            UserActionsUtil.canCreateGuest(
+                manager,
+                repository,
+                settings.isUserSwitcherEnabled,
+                settings.isAddUsersFromLockscreen,
+            )
+    }
+
     companion object {
         private const val TAG = "UserInteractor"
     }
diff --git a/packages/SystemUI/src/com/android/systemui/user/ui/binder/UserSwitcherViewBinder.kt b/packages/SystemUI/src/com/android/systemui/user/ui/binder/UserSwitcherViewBinder.kt
index ad09ee3..e137107 100644
--- a/packages/SystemUI/src/com/android/systemui/user/ui/binder/UserSwitcherViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/user/ui/binder/UserSwitcherViewBinder.kt
@@ -133,7 +133,9 @@
                 launch {
                     viewModel.users.collect { users ->
                         val viewPool =
-                            view.children.filter { it.tag == USER_VIEW_TAG }.toMutableList()
+                            gridContainerView.children
+                                .filter { it.tag == USER_VIEW_TAG }
+                                .toMutableList()
                         viewPool.forEach {
                             gridContainerView.removeView(it)
                             flowWidget.removeView(it)
diff --git a/packages/SystemUI/src/com/android/systemui/user/ui/dialog/UserSwitcherDialogCoordinator.kt b/packages/SystemUI/src/com/android/systemui/user/ui/dialog/UserSwitcherDialogCoordinator.kt
index e921720..58a4473 100644
--- a/packages/SystemUI/src/com/android/systemui/user/ui/dialog/UserSwitcherDialogCoordinator.kt
+++ b/packages/SystemUI/src/com/android/systemui/user/ui/dialog/UserSwitcherDialogCoordinator.kt
@@ -27,15 +27,12 @@
 import com.android.systemui.broadcast.BroadcastSender
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
-import com.android.systemui.flags.FeatureFlags
-import com.android.systemui.flags.Flags
 import com.android.systemui.plugins.FalsingManager
 import com.android.systemui.user.domain.interactor.UserInteractor
 import com.android.systemui.user.domain.model.ShowDialogRequestModel
 import dagger.Lazy
 import javax.inject.Inject
 import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.flow.collect
 import kotlinx.coroutines.flow.filterNotNull
 import kotlinx.coroutines.launch
 
@@ -50,16 +47,11 @@
     private val broadcastSender: Lazy<BroadcastSender>,
     private val dialogLaunchAnimator: Lazy<DialogLaunchAnimator>,
     private val interactor: Lazy<UserInteractor>,
-    private val featureFlags: Lazy<FeatureFlags>,
 ) : CoreStartable {
 
     private var currentDialog: Dialog? = null
 
     override fun start() {
-        if (featureFlags.get().isEnabled(Flags.USER_INTERACTOR_AND_REPO_USE_CONTROLLER)) {
-            return
-        }
-
         startHandlingDialogShowRequests()
         startHandlingDialogDismissRequests()
     }
diff --git a/packages/SystemUI/src/com/android/systemui/user/ui/viewmodel/UserSwitcherViewModel.kt b/packages/SystemUI/src/com/android/systemui/user/ui/viewmodel/UserSwitcherViewModel.kt
index d857e85..0910ea3 100644
--- a/packages/SystemUI/src/com/android/systemui/user/ui/viewmodel/UserSwitcherViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/user/ui/viewmodel/UserSwitcherViewModel.kt
@@ -20,8 +20,6 @@
 import androidx.lifecycle.ViewModel
 import androidx.lifecycle.ViewModelProvider
 import com.android.systemui.common.ui.drawable.CircularDrawable
-import com.android.systemui.flags.FeatureFlags
-import com.android.systemui.flags.Flags
 import com.android.systemui.power.domain.interactor.PowerInteractor
 import com.android.systemui.user.domain.interactor.GuestUserInteractor
 import com.android.systemui.user.domain.interactor.UserInteractor
@@ -41,12 +39,8 @@
     private val userInteractor: UserInteractor,
     private val guestUserInteractor: GuestUserInteractor,
     private val powerInteractor: PowerInteractor,
-    private val featureFlags: FeatureFlags,
 ) : ViewModel() {
 
-    private val isNewImpl: Boolean
-        get() = !featureFlags.isEnabled(Flags.USER_INTERACTOR_AND_REPO_USE_CONTROLLER)
-
     /** On-device users. */
     val users: Flow<List<UserViewModel>> =
         userInteractor.users.map { models -> models.map { user -> toViewModel(user) } }
@@ -216,7 +210,6 @@
         private val userInteractor: UserInteractor,
         private val guestUserInteractor: GuestUserInteractor,
         private val powerInteractor: PowerInteractor,
-        private val featureFlags: FeatureFlags,
     ) : ViewModelProvider.Factory {
         override fun <T : ViewModel> create(modelClass: Class<T>): T {
             @Suppress("UNCHECKED_CAST")
@@ -224,7 +217,6 @@
                 userInteractor = userInteractor,
                 guestUserInteractor = guestUserInteractor,
                 powerInteractor = powerInteractor,
-                featureFlags = featureFlags,
             )
                 as T
         }
diff --git a/packages/SystemUI/src/com/android/systemui/util/time/DateFormatUtil.java b/packages/SystemUI/src/com/android/systemui/util/time/DateFormatUtil.java
index d7c4e93..3c57081 100644
--- a/packages/SystemUI/src/com/android/systemui/util/time/DateFormatUtil.java
+++ b/packages/SystemUI/src/com/android/systemui/util/time/DateFormatUtil.java
@@ -16,10 +16,11 @@
 
 package com.android.systemui.util.time;
 
-import android.app.ActivityManager;
 import android.content.Context;
 import android.text.format.DateFormat;
 
+import com.android.systemui.settings.UserTracker;
+
 import javax.inject.Inject;
 
 /**
@@ -27,14 +28,16 @@
  */
 public class DateFormatUtil {
     private final Context mContext;
+    private final UserTracker mUserTracker;
 
     @Inject
-    public DateFormatUtil(Context context) {
+    public DateFormatUtil(Context context, UserTracker userTracker) {
         mContext = context;
+        mUserTracker = userTracker;
     }
 
     /** Returns true if the phone is in 24 hour format. */
     public boolean is24HourFormat() {
-        return DateFormat.is24HourFormat(mContext, ActivityManager.getCurrentUser());
+        return DateFormat.is24HourFormat(mContext, mUserTracker.getUserId());
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt
index 52b6b38..e8f8e25 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt
@@ -155,7 +155,8 @@
         verify(configurationController).addCallback(capture(captor))
         captor.value.onDensityOrFontScaleChanged()
 
-        verify(events).onFontSettingChanged()
+        verify(smallClockEvents, times(2)).onFontSettingChanged(anyFloat())
+        verify(largeClockEvents, times(2)).onFontSettingChanged(anyFloat())
     }
 
     @Test
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
index 27094c0..1ec23e9 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
@@ -29,6 +29,7 @@
 import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_BOOT;
 import static com.android.keyguard.FaceAuthApiRequestReason.NOTIFICATION_PANEL_CLICKED;
 import static com.android.keyguard.KeyguardUpdateMonitor.DEFAULT_CANCEL_SIGNAL_TIMEOUT;
+import static com.android.keyguard.KeyguardUpdateMonitor.getCurrentUser;
 
 import static com.google.common.truth.Truth.assertThat;
 
@@ -36,6 +37,7 @@
 import static org.mockito.ArgumentMatchers.anyBoolean;
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.anyLong;
+import static org.mockito.ArgumentMatchers.anyObject;
 import static org.mockito.ArgumentMatchers.anyString;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.atLeastOnce;
@@ -89,6 +91,7 @@
 import android.os.UserHandle;
 import android.os.UserManager;
 import android.service.dreams.IDreamManager;
+import android.service.trust.TrustAgentService;
 import android.telephony.ServiceState;
 import android.telephony.SubscriptionInfo;
 import android.telephony.SubscriptionManager;
@@ -113,6 +116,7 @@
 import com.android.systemui.dump.DumpManager;
 import com.android.systemui.log.SessionTracker;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
+import com.android.systemui.settings.UserTracker;
 import com.android.systemui.statusbar.StatusBarState;
 import com.android.systemui.statusbar.phone.KeyguardBypassController;
 import com.android.systemui.telephony.TelephonyListenerManager;
@@ -157,6 +161,8 @@
     private static final int FINGERPRINT_SENSOR_ID = 1;
 
     @Mock
+    private UserTracker mUserTracker;
+    @Mock
     private DumpManager mDumpManager;
     @Mock
     private KeyguardUpdateMonitor.StrongAuthTracker mStrongAuthTracker;
@@ -306,8 +312,7 @@
         ExtendedMockito.doReturn(SubscriptionManager.INVALID_SUBSCRIPTION_ID)
                 .when(SubscriptionManager::getDefaultSubscriptionId);
         KeyguardUpdateMonitor.setCurrentUser(mCurrentUserId);
-        ExtendedMockito.doReturn(KeyguardUpdateMonitor.getCurrentUser())
-                .when(ActivityManager::getCurrentUser);
+        when(mUserTracker.getUserId()).thenReturn(mCurrentUserId);
         ExtendedMockito.doReturn(mActivityService).when(ActivityManager::getService);
 
         mFaceWakeUpTriggersConfig = new FaceWakeUpTriggersConfig(
@@ -351,7 +356,9 @@
 
     @After
     public void tearDown() {
-        mMockitoSession.finishMocking();
+        if (mMockitoSession != null) {
+            mMockitoSession.finishMocking();
+        }
         cleanupKeyguardUpdateMonitor();
     }
 
@@ -1032,6 +1039,7 @@
     @Test
     public void testSecondaryLockscreenRequirement() {
         KeyguardUpdateMonitor.setCurrentUser(UserHandle.myUserId());
+        when(mUserTracker.getUserId()).thenReturn(UserHandle.myUserId());
         int user = KeyguardUpdateMonitor.getCurrentUser();
         String packageName = "fake.test.package";
         String cls = "FakeService";
@@ -1313,9 +1321,9 @@
                 Arrays.asList("Unlocked by wearable"));
 
         // THEN the showTrustGrantedMessage should be called with the first message
-        verify(mTestCallback).onTrustGrantedWithFlags(
-                eq(0),
-                eq(KeyguardUpdateMonitor.getCurrentUser()),
+        verify(mTestCallback).onTrustGrantedForCurrentUser(
+                anyBoolean(),
+                eq(new TrustGrantFlags(0)),
                 eq("Unlocked by wearable"));
     }
 
@@ -1726,6 +1734,155 @@
         verify(mFaceManager).authenticate(any(), any(), any(), any(), anyInt(), anyBoolean());
     }
 
+
+    @Test
+    public void testOnTrustGrantedForCurrentUser_dismissKeyguardRequested_deviceInteractive() {
+        // GIVEN device is interactive
+        deviceIsInteractive();
+
+        // GIVEN callback is registered
+        KeyguardUpdateMonitorCallback callback = mock(KeyguardUpdateMonitorCallback.class);
+        mKeyguardUpdateMonitor.registerCallback(callback);
+
+        // WHEN onTrustChanged with TRUST_DISMISS_KEYGUARD flag
+        mKeyguardUpdateMonitor.onTrustChanged(
+                true /* enabled */,
+                getCurrentUser() /* userId */,
+                TrustAgentService.FLAG_GRANT_TRUST_DISMISS_KEYGUARD /* flags */,
+                null /* trustGrantedMessages */);
+
+        // THEN onTrustGrantedForCurrentUser callback called
+        verify(callback).onTrustGrantedForCurrentUser(
+                eq(true) /* dismissKeyguard */,
+                eq(new TrustGrantFlags(TrustAgentService.FLAG_GRANT_TRUST_DISMISS_KEYGUARD)),
+                eq(null) /* message */
+        );
+    }
+
+    @Test
+    public void testOnTrustGrantedForCurrentUser_dismissKeyguardRequested_doesNotDismiss() {
+        // GIVEN device is NOT interactive
+
+        // GIVEN callback is registered
+        KeyguardUpdateMonitorCallback callback = mock(KeyguardUpdateMonitorCallback.class);
+        mKeyguardUpdateMonitor.registerCallback(callback);
+
+        // WHEN onTrustChanged with TRUST_DISMISS_KEYGUARD flag
+        mKeyguardUpdateMonitor.onTrustChanged(
+                true /* enabled */,
+                getCurrentUser() /* userId */,
+                TrustAgentService.FLAG_GRANT_TRUST_DISMISS_KEYGUARD /* flags */,
+                null /* trustGrantedMessages */);
+
+        // THEN onTrustGrantedForCurrentUser callback called
+        verify(callback).onTrustGrantedForCurrentUser(
+                eq(false) /* dismissKeyguard */,
+                eq(new TrustGrantFlags(TrustAgentService.FLAG_GRANT_TRUST_DISMISS_KEYGUARD)),
+                eq(null) /* message */
+        );
+    }
+
+    @Test
+    public void testOnTrustGrantedForCurrentUser_dismissKeyguardRequested_temporaryAndRenewable() {
+        // GIVEN device is interactive
+        deviceIsInteractive();
+
+        // GIVEN callback is registered
+        KeyguardUpdateMonitorCallback callback = mock(KeyguardUpdateMonitorCallback.class);
+        mKeyguardUpdateMonitor.registerCallback(callback);
+
+        // WHEN onTrustChanged for a different user
+        mKeyguardUpdateMonitor.onTrustChanged(
+                true /* enabled */,
+                546 /* userId, not the current userId */,
+                0 /* flags */,
+                null /* trustGrantedMessages */);
+
+        // THEN onTrustGrantedForCurrentUser callback called
+        verify(callback, never()).onTrustGrantedForCurrentUser(
+                anyBoolean() /* dismissKeyguard */,
+                anyObject() /* flags */,
+                anyString() /* message */
+        );
+    }
+
+    @Test
+    public void testOnTrustGranted_differentUser_noCallback() {
+        // GIVEN device is interactive
+
+        // GIVEN callback is registered
+        KeyguardUpdateMonitorCallback callback = mock(KeyguardUpdateMonitorCallback.class);
+        mKeyguardUpdateMonitor.registerCallback(callback);
+
+        // WHEN onTrustChanged with TRUST_DISMISS_KEYGUARD AND TRUST_TEMPORARY_AND_RENEWABLE
+        // flags (temporary & rewable is active unlock)
+        mKeyguardUpdateMonitor.onTrustChanged(
+                true /* enabled */,
+                getCurrentUser() /* userId */,
+                TrustAgentService.FLAG_GRANT_TRUST_DISMISS_KEYGUARD
+                        | TrustAgentService.FLAG_GRANT_TRUST_TEMPORARY_AND_RENEWABLE /* flags */,
+                null /* trustGrantedMessages */);
+
+        // THEN onTrustGrantedForCurrentUser callback called
+        verify(callback).onTrustGrantedForCurrentUser(
+                eq(true) /* dismissKeyguard */,
+                eq(new TrustGrantFlags(TrustAgentService.FLAG_GRANT_TRUST_DISMISS_KEYGUARD
+                        | TrustAgentService.FLAG_GRANT_TRUST_TEMPORARY_AND_RENEWABLE)),
+                eq(null) /* message */
+        );
+    }
+
+    @Test
+    public void testOnTrustGrantedForCurrentUser_bouncerShowing_initiatedByUser() {
+        // GIVEN device is interactive & bouncer is showing
+        deviceIsInteractive();
+        bouncerFullyVisible();
+
+        // GIVEN callback is registered
+        KeyguardUpdateMonitorCallback callback = mock(KeyguardUpdateMonitorCallback.class);
+        mKeyguardUpdateMonitor.registerCallback(callback);
+
+        // WHEN onTrustChanged with INITIATED_BY_USER flag
+        mKeyguardUpdateMonitor.onTrustChanged(
+                true /* enabled */,
+                getCurrentUser() /* userId, not the current userId */,
+                TrustAgentService.FLAG_GRANT_TRUST_INITIATED_BY_USER /* flags */,
+                null /* trustGrantedMessages */);
+
+        // THEN onTrustGrantedForCurrentUser callback called
+        verify(callback, never()).onTrustGrantedForCurrentUser(
+                eq(true) /* dismissKeyguard */,
+                eq(new TrustGrantFlags(TrustAgentService.FLAG_GRANT_TRUST_INITIATED_BY_USER)),
+                anyString() /* message */
+        );
+    }
+
+    @Test
+    public void testOnTrustGrantedForCurrentUser_bouncerShowing_temporaryRenewable() {
+        // GIVEN device is NOT interactive & bouncer is showing
+        bouncerFullyVisible();
+
+        // GIVEN callback is registered
+        KeyguardUpdateMonitorCallback callback = mock(KeyguardUpdateMonitorCallback.class);
+        mKeyguardUpdateMonitor.registerCallback(callback);
+
+        // WHEN onTrustChanged with INITIATED_BY_USER flag
+        mKeyguardUpdateMonitor.onTrustChanged(
+                true /* enabled */,
+                getCurrentUser() /* userId, not the current userId */,
+                TrustAgentService.FLAG_GRANT_TRUST_INITIATED_BY_USER
+                        | TrustAgentService.FLAG_GRANT_TRUST_TEMPORARY_AND_RENEWABLE /* flags */,
+                null /* trustGrantedMessages */);
+
+        // THEN onTrustGrantedForCurrentUser callback called
+        verify(callback, never()).onTrustGrantedForCurrentUser(
+                eq(true) /* dismissKeyguard */,
+                eq(new TrustGrantFlags(TrustAgentService.FLAG_GRANT_TRUST_INITIATED_BY_USER
+                        | TrustAgentService.FLAG_GRANT_TRUST_TEMPORARY_AND_RENEWABLE)),
+                anyString() /* message */
+        );
+    }
+
     private void cleanupKeyguardUpdateMonitor() {
         if (mKeyguardUpdateMonitor != null) {
             mKeyguardUpdateMonitor.removeCallback(mTestCallback);
@@ -1886,7 +2043,7 @@
         AtomicBoolean mSimStateChanged = new AtomicBoolean(false);
 
         protected TestableKeyguardUpdateMonitor(Context context) {
-            super(context,
+            super(context, mUserTracker,
                     TestableLooper.get(KeyguardUpdateMonitorTest.this).getLooper(),
                     mBroadcastDispatcher, mSecureSettings, mDumpManager,
                     mBackgroundExecutor, mMainExecutor,
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/clock/ClockManagerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/clock/ClockManagerTest.java
index ff4412e9..27701be 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/clock/ClockManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/clock/ClockManagerTest.java
@@ -17,6 +17,7 @@
 
 import static com.google.common.truth.Truth.assertThat;
 
+import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.Mockito.reset;
 import static org.mockito.Mockito.verify;
@@ -30,15 +31,15 @@
 import android.testing.TestableLooper.RunWithLooper;
 import android.view.LayoutInflater;
 
-import androidx.lifecycle.MutableLiveData;
-
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.colorextraction.SysuiColorExtractor;
 import com.android.systemui.dock.DockManager;
 import com.android.systemui.dock.DockManagerFake;
 import com.android.systemui.plugins.ClockPlugin;
-import com.android.systemui.settings.CurrentUserObservable;
+import com.android.systemui.settings.UserTracker;
 import com.android.systemui.shared.plugins.PluginManager;
+import com.android.systemui.util.concurrency.FakeExecutor;
+import com.android.systemui.util.time.FakeSystemClock;
 
 import org.junit.After;
 import org.junit.Before;
@@ -52,8 +53,7 @@
 
 @SmallTest
 @RunWith(AndroidTestingRunner.class)
-// Need to run tests on main looper because LiveData operations such as setData, observe,
-// removeObserver cannot be invoked on a background thread.
+// Need to run tests on main looper to allow for onClockChanged operation to happen synchronously.
 @RunWithLooper(setAsMainLooper = true)
 public final class ClockManagerTest extends SysuiTestCase {
 
@@ -63,14 +63,16 @@
     private static final int SECONDARY_USER_ID = 11;
     private static final Uri SETTINGS_URI = null;
 
+    private final FakeSystemClock mFakeSystemClock = new FakeSystemClock();
+    private final FakeExecutor mMainExecutor = new FakeExecutor(mFakeSystemClock);
     private ClockManager mClockManager;
     private ContentObserver mContentObserver;
     private DockManagerFake mFakeDockManager;
-    private MutableLiveData<Integer> mCurrentUser;
+    private ArgumentCaptor<UserTracker.Callback> mUserTrackerCallbackCaptor;
     @Mock PluginManager mMockPluginManager;
     @Mock SysuiColorExtractor mMockColorExtractor;
     @Mock ContentResolver mMockContentResolver;
-    @Mock CurrentUserObservable mMockCurrentUserObserable;
+    @Mock UserTracker mUserTracker;
     @Mock SettingsWrapper mMockSettingsWrapper;
     @Mock ClockManager.ClockChangedListener mMockListener1;
     @Mock ClockManager.ClockChangedListener mMockListener2;
@@ -83,18 +85,18 @@
 
         mFakeDockManager = new DockManagerFake();
 
-        mCurrentUser = new MutableLiveData<>();
-        mCurrentUser.setValue(MAIN_USER_ID);
-        when(mMockCurrentUserObserable.getCurrentUser()).thenReturn(mCurrentUser);
+        when(mUserTracker.getUserId()).thenReturn(MAIN_USER_ID);
+        mUserTrackerCallbackCaptor = ArgumentCaptor.forClass(UserTracker.Callback.class);
 
         mClockManager = new ClockManager(getContext(), inflater,
                 mMockPluginManager, mMockColorExtractor, mMockContentResolver,
-                mMockCurrentUserObserable, mMockSettingsWrapper, mFakeDockManager);
+                mUserTracker, mMainExecutor, mMockSettingsWrapper, mFakeDockManager);
 
         mClockManager.addBuiltinClock(() -> new BubbleClockController(
                 getContext().getResources(), inflater, mMockColorExtractor));
         mClockManager.addOnClockChangedListener(mMockListener1);
         mClockManager.addOnClockChangedListener(mMockListener2);
+        verify(mUserTracker).addCallback(mUserTrackerCallbackCaptor.capture(), any());
         reset(mMockListener1, mMockListener2);
 
         mContentObserver = mClockManager.getContentObserver();
@@ -221,7 +223,7 @@
     @Test
     public void onUserChanged_defaultClock() {
         // WHEN the user changes
-        mCurrentUser.setValue(SECONDARY_USER_ID);
+        switchUser(SECONDARY_USER_ID);
         // THEN the plugin is null for the default clock face
         assertThat(mClockManager.getCurrentClock()).isNull();
     }
@@ -232,7 +234,7 @@
         when(mMockSettingsWrapper.getLockScreenCustomClockFace(SECONDARY_USER_ID)).thenReturn(
                 BUBBLE_CLOCK);
         // WHEN the user changes
-        mCurrentUser.setValue(SECONDARY_USER_ID);
+        switchUser(SECONDARY_USER_ID);
         // THEN the plugin is the bubble clock face.
         assertThat(mClockManager.getCurrentClock()).isInstanceOf(BUBBLE_CLOCK_CLASS);
     }
@@ -244,8 +246,13 @@
         // AND the second user as selected the bubble clock for the dock
         when(mMockSettingsWrapper.getDockedClockFace(SECONDARY_USER_ID)).thenReturn(BUBBLE_CLOCK);
         // WHEN the user changes
-        mCurrentUser.setValue(SECONDARY_USER_ID);
+        switchUser(SECONDARY_USER_ID);
         // THEN the plugin is the bubble clock face.
         assertThat(mClockManager.getCurrentClock()).isInstanceOf(BUBBLE_CLOCK_CLASS);
     }
+
+    private void switchUser(int newUser) {
+        when(mUserTracker.getUserId()).thenReturn(newUser);
+        mUserTrackerCallbackCaptor.getValue().onUserChanged(newUser, mContext);
+    }
 }
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 d20eeaf..2d5188f 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
@@ -19,12 +19,22 @@
 import static android.view.View.GONE;
 import static android.view.View.VISIBLE;
 
+import static com.android.internal.accessibility.AccessibilityShortcutController.MAGNIFICATION_COMPONENT_NAME;
 import static com.android.systemui.accessibility.floatingmenu.MenuViewLayer.LayerIndex;
 
 import static com.google.common.truth.Truth.assertThat;
 
 import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
 
+import android.accessibilityservice.AccessibilityServiceInfo;
+import android.content.ComponentName;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.ResolveInfo;
+import android.content.pm.ServiceInfo;
+import android.os.Build;
+import android.os.UserHandle;
+import android.provider.Settings;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
 import android.view.View;
@@ -35,6 +45,7 @@
 
 import com.android.systemui.SysuiTestCase;
 
+import org.junit.After;
 import org.junit.Before;
 import org.junit.Rule;
 import org.junit.Test;
@@ -43,12 +54,23 @@
 import org.mockito.junit.MockitoJUnit;
 import org.mockito.junit.MockitoRule;
 
+import java.util.ArrayList;
+import java.util.List;
+
 /** Tests for {@link MenuViewLayer}. */
 @RunWith(AndroidTestingRunner.class)
 @TestableLooper.RunWithLooper(setAsMainLooper = true)
 @SmallTest
 public class MenuViewLayerTest extends SysuiTestCase {
+    private static final String SELECT_TO_SPEAK_PACKAGE_NAME = "com.google.android.marvin.talkback";
+    private static final String SELECT_TO_SPEAK_SERVICE_NAME =
+            "com.google.android.accessibility.selecttospeak.SelectToSpeakService";
+    private static final ComponentName TEST_SELECT_TO_SPEAK_COMPONENT_NAME = new ComponentName(
+            SELECT_TO_SPEAK_PACKAGE_NAME, SELECT_TO_SPEAK_SERVICE_NAME);
+
     private MenuViewLayer mMenuViewLayer;
+    private String mLastAccessibilityButtonTargets;
+    private String mLastEnabledAccessibilityServices;
 
     @Rule
     public MockitoRule mockito = MockitoJUnit.rule();
@@ -56,13 +78,31 @@
     @Mock
     private IAccessibilityFloatingMenu mFloatingMenu;
 
+    @Mock
+    private AccessibilityManager mStubAccessibilityManager;
+
     @Before
     public void setUp() throws Exception {
         final WindowManager stubWindowManager = mContext.getSystemService(WindowManager.class);
-        final AccessibilityManager stubAccessibilityManager = mContext.getSystemService(
-                AccessibilityManager.class);
-        mMenuViewLayer = new MenuViewLayer(mContext, stubWindowManager, stubAccessibilityManager,
+        mMenuViewLayer = new MenuViewLayer(mContext, stubWindowManager, mStubAccessibilityManager,
                 mFloatingMenu);
+
+        mLastAccessibilityButtonTargets =
+                Settings.Secure.getStringForUser(mContext.getContentResolver(),
+                        Settings.Secure.ACCESSIBILITY_BUTTON_TARGETS, UserHandle.USER_CURRENT);
+        mLastEnabledAccessibilityServices =
+                Settings.Secure.getStringForUser(mContext.getContentResolver(),
+                        Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES, UserHandle.USER_CURRENT);
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        Settings.Secure.putStringForUser(mContext.getContentResolver(),
+                Settings.Secure.ACCESSIBILITY_BUTTON_TARGETS, mLastAccessibilityButtonTargets,
+                UserHandle.USER_CURRENT);
+        Settings.Secure.putStringForUser(mContext.getContentResolver(),
+                Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES, mLastEnabledAccessibilityServices,
+                UserHandle.USER_CURRENT);
     }
 
     @Test
@@ -87,4 +127,45 @@
 
         verify(mFloatingMenu).hide();
     }
+
+    @Test
+    public void tiggerDismissMenuAction_matchA11yButtonTargetsResult() {
+        Settings.Secure.putStringForUser(mContext.getContentResolver(),
+                Settings.Secure.ACCESSIBILITY_BUTTON_TARGETS,
+                MAGNIFICATION_COMPONENT_NAME.flattenToString(), UserHandle.USER_CURRENT);
+
+        mMenuViewLayer.mDismissMenuAction.run();
+        final String value =
+                Settings.Secure.getStringForUser(mContext.getContentResolver(),
+                        Settings.Secure.ACCESSIBILITY_BUTTON_TARGETS, UserHandle.USER_CURRENT);
+
+        assertThat(value).isEqualTo("");
+    }
+
+    @Test
+    public void tiggerDismissMenuAction_matchEnabledA11yServicesResult() {
+        Settings.Secure.putString(mContext.getContentResolver(),
+                Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES,
+                TEST_SELECT_TO_SPEAK_COMPONENT_NAME.flattenToString());
+        final ResolveInfo resolveInfo = new ResolveInfo();
+        final ServiceInfo serviceInfo = new ServiceInfo();
+        final ApplicationInfo applicationInfo = new ApplicationInfo();
+        resolveInfo.serviceInfo = serviceInfo;
+        serviceInfo.applicationInfo = applicationInfo;
+        applicationInfo.targetSdkVersion = Build.VERSION_CODES.R;
+        final AccessibilityServiceInfo accessibilityServiceInfo = new AccessibilityServiceInfo();
+        accessibilityServiceInfo.setResolveInfo(resolveInfo);
+        accessibilityServiceInfo.flags = AccessibilityServiceInfo.FLAG_REQUEST_ACCESSIBILITY_BUTTON;
+        final List<AccessibilityServiceInfo> serviceInfoList = new ArrayList<>();
+        accessibilityServiceInfo.setComponentName(TEST_SELECT_TO_SPEAK_COMPONENT_NAME);
+        serviceInfoList.add(accessibilityServiceInfo);
+        when(mStubAccessibilityManager.getEnabledAccessibilityServiceList(
+                AccessibilityServiceInfo.FEEDBACK_ALL_MASK)).thenReturn(serviceInfoList);
+
+        mMenuViewLayer.mDismissMenuAction.run();
+        final String value = Settings.Secure.getString(mContext.getContentResolver(),
+                Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES);
+
+        assertThat(value).isEqualTo("");
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/battery/BatteryMeterViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/battery/BatteryMeterViewControllerTest.java
index bc8f961..1482f29 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/battery/BatteryMeterViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/battery/BatteryMeterViewControllerTest.java
@@ -34,9 +34,9 @@
 import androidx.test.filters.SmallTest;
 
 import com.android.systemui.SysuiTestCase;
-import com.android.systemui.broadcast.BroadcastDispatcher;
 import com.android.systemui.flags.FakeFeatureFlags;
 import com.android.systemui.flags.Flags;
+import com.android.systemui.settings.UserTracker;
 import com.android.systemui.statusbar.policy.BatteryController;
 import com.android.systemui.statusbar.policy.ConfigurationController;
 import com.android.systemui.tuner.TunerService;
@@ -52,12 +52,12 @@
     private BatteryMeterView mBatteryMeterView;
 
     @Mock
+    private UserTracker mUserTracker;
+    @Mock
     private ConfigurationController mConfigurationController;
     @Mock
     private TunerService mTunerService;
     @Mock
-    private BroadcastDispatcher mBroadcastDispatcher;
-    @Mock
     private Handler mHandler;
     @Mock
     private ContentResolver mContentResolver;
@@ -153,9 +153,9 @@
     private void initController() {
         mController = new BatteryMeterViewController(
                 mBatteryMeterView,
+                mUserTracker,
                 mConfigurationController,
                 mTunerService,
-                mBroadcastDispatcher,
                 mHandler,
                 mContentResolver,
                 mFeatureFlags,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt
index 12c2bbf..898f370 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt
@@ -125,6 +125,21 @@
     }
 
     @Test
+    fun testCredentialPasswordDismissesOnBack() {
+        val container = initializeCredentialPasswordContainer(addToView = true)
+        assertThat(container.parent).isNotNull()
+        val root = container.rootView
+
+        // Simulate back invocation
+        container.dispatchKeyEvent(KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_BACK))
+        container.dispatchKeyEvent(KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_BACK))
+        waitForIdleSync()
+
+        assertThat(container.parent).isNull()
+        assertThat(root.isAttachedToWindow).isFalse()
+    }
+
+    @Test
     fun testIgnoresAnimatedInWhenDismissed() {
         val container = initializeFingerprintContainer(addToView = false)
         container.dismissFromSystemServer()
@@ -369,20 +384,7 @@
 
     @Test
     fun testCredentialUI_disablesClickingOnBackground() {
-        whenever(userManager.getCredentialOwnerProfile(anyInt())).thenReturn(20)
-        whenever(lockPatternUtils.getKeyguardStoredPasswordQuality(eq(20))).thenReturn(
-            DevicePolicyManager.PASSWORD_QUALITY_NUMERIC
-        )
-
-        // In the credential view, clicking on the background (to cancel authentication) is not
-        // valid. Thus, the listener should be null, and it should not be in the accessibility
-        // hierarchy.
-        val container = initializeFingerprintContainer(
-            authenticators = BiometricManager.Authenticators.DEVICE_CREDENTIAL
-        )
-        waitForIdleSync()
-
-        assertThat(container.hasCredentialPasswordView()).isTrue()
+        val container = initializeCredentialPasswordContainer()
         assertThat(container.hasBiometricPrompt()).isFalse()
         assertThat(
             container.findViewById<View>(R.id.background)?.isImportantForAccessibility
@@ -442,6 +444,27 @@
         verify(callback).onTryAgainPressed(authContainer?.requestId ?: 0L)
     }
 
+    private fun initializeCredentialPasswordContainer(
+            addToView: Boolean = true,
+    ): TestAuthContainerView {
+        whenever(userManager.getCredentialOwnerProfile(anyInt())).thenReturn(20)
+        whenever(lockPatternUtils.getKeyguardStoredPasswordQuality(eq(20))).thenReturn(
+            DevicePolicyManager.PASSWORD_QUALITY_NUMERIC
+        )
+
+        // In the credential view, clicking on the background (to cancel authentication) is not
+        // valid. Thus, the listener should be null, and it should not be in the accessibility
+        // hierarchy.
+        val container = initializeFingerprintContainer(
+                authenticators = BiometricManager.Authenticators.DEVICE_CREDENTIAL,
+                addToView = addToView,
+        )
+        waitForIdleSync()
+
+        assertThat(container.hasCredentialPasswordView()).isTrue()
+        return container
+    }
+
     private fun initializeFingerprintContainer(
         authenticators: Int = BiometricManager.Authenticators.BIOMETRIC_WEAK,
         addToView: Boolean = true
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerBaseTest.java
index 75629f4..3c61382 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerBaseTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerBaseTest.java
@@ -94,6 +94,11 @@
             mKeyguardStateControllerCallbackCaptor;
     protected KeyguardStateController.Callback mKeyguardStateControllerCallback;
 
+    private @Captor ArgumentCaptor<StatusBarKeyguardViewManager.KeyguardViewManagerCallback>
+            mKeyguardViewManagerCallbackArgumentCaptor;
+    protected StatusBarKeyguardViewManager.KeyguardViewManagerCallback mKeyguardViewManagerCallback;
+
+
     @Before
     public void setUp() {
         MockitoAnnotations.initMocks(this);
@@ -143,15 +148,22 @@
     }
 
     public UdfpsKeyguardViewController createUdfpsKeyguardViewController() {
-        return createUdfpsKeyguardViewController(false);
+        return createUdfpsKeyguardViewController(false, false);
+    }
+
+    public void captureKeyGuardViewManagerCallback() {
+        verify(mStatusBarKeyguardViewManager).addCallback(
+                mKeyguardViewManagerCallbackArgumentCaptor.capture());
+        mKeyguardViewManagerCallback = mKeyguardViewManagerCallbackArgumentCaptor.getValue();
     }
 
     protected UdfpsKeyguardViewController createUdfpsKeyguardViewController(
-            boolean useModernBouncer) {
+            boolean useModernBouncer, boolean useExpandedOverlay) {
         mFeatureFlags.set(Flags.MODERN_BOUNCER, useModernBouncer);
+        mFeatureFlags.set(Flags.UDFPS_NEW_TOUCH_DETECTION, useExpandedOverlay);
         when(mStatusBarKeyguardViewManager.getPrimaryBouncer()).thenReturn(
                 useModernBouncer ? null : mBouncer);
-        return new UdfpsKeyguardViewController(
+        UdfpsKeyguardViewController controller = new UdfpsKeyguardViewController(
                 mView,
                 mStatusBarStateController,
                 mShadeExpansionStateManager,
@@ -168,5 +180,6 @@
                 mActivityLaunchAnimator,
                 mFeatureFlags,
                 mPrimaryBouncerInteractor);
+        return controller;
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerTest.java
index 16728b6..babe533 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerTest.java
@@ -18,6 +18,7 @@
 
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyBoolean;
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.Mockito.atLeast;
@@ -30,6 +31,7 @@
 
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper.RunWithLooper;
+import android.view.MotionEvent;
 
 import androidx.test.filters.SmallTest;
 
@@ -52,7 +54,8 @@
 
     @Override
     public UdfpsKeyguardViewController createUdfpsKeyguardViewController() {
-        return createUdfpsKeyguardViewController(/* useModernBouncer */ false);
+        return createUdfpsKeyguardViewController(/* useModernBouncer */ false,
+                /* useExpandedOverlay */ false);
     }
 
     @Test
@@ -422,4 +425,37 @@
         verify(mBouncer).addBouncerExpansionCallback(mBouncerExpansionCallbackCaptor.capture());
         mBouncerExpansionCallback = mBouncerExpansionCallbackCaptor.getValue();
     }
+
+    @Test
+    // TODO(b/259264861): Tracking Bug
+    public void testUdfpsExpandedOverlayOn() {
+        // GIVEN view is attached and useExpandedOverlay is true
+        mController = createUdfpsKeyguardViewController(false, true);
+        mController.onViewAttached();
+        captureKeyGuardViewManagerCallback();
+
+        // WHEN a touch is received
+        mKeyguardViewManagerCallback.onTouch(
+                MotionEvent.obtain(0, 0, 0, 0, 0, 0));
+
+        // THEN udfpsController onTouch is not called
+        assertTrue(mView.mUseExpandedOverlay);
+        verify(mUdfpsController, never()).onTouch(any());
+    }
+
+    @Test
+    // TODO(b/259264861): Tracking Bug
+    public void testUdfpsExpandedOverlayOff() {
+        // GIVEN view is attached and useExpandedOverlay is false
+        mController.onViewAttached();
+        captureKeyGuardViewManagerCallback();
+
+        // WHEN a touch is received
+        mKeyguardViewManagerCallback.onTouch(
+                MotionEvent.obtain(0, 0, 0, 0, 0, 0));
+
+        // THEN udfpsController onTouch is called
+        assertFalse(mView.mUseExpandedOverlay);
+        verify(mUdfpsController).onTouch(any());
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerWithCoroutinesTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerWithCoroutinesTest.kt
index 68e744e..517e27a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerWithCoroutinesTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerWithCoroutinesTest.kt
@@ -72,7 +72,10 @@
                 mock(KeyguardBypassController::class.java),
                 mKeyguardUpdateMonitor
             )
-        return createUdfpsKeyguardViewController(/* useModernBouncer */ true)
+        return createUdfpsKeyguardViewController(
+            /* useModernBouncer */ true, /* useExpandedOverlay */
+            false
+        )
     }
 
     /** After migration, replaces LockIconViewControllerTest version */
diff --git a/packages/SystemUI/tests/src/com/android/systemui/classifier/BrightLineClassifierTest.java b/packages/SystemUI/tests/src/com/android/systemui/classifier/BrightLineClassifierTest.java
index f8579ff..0fadc13 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/classifier/BrightLineClassifierTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/classifier/BrightLineClassifierTest.java
@@ -120,6 +120,7 @@
 
         mGestureFinalizedListener = gestureCompleteListenerCaptor.getValue();
         mFakeFeatureFlags.set(Flags.FALSING_FOR_LONG_TAPS, true);
+        mFakeFeatureFlags.set(Flags.MEDIA_FALSING_PENALTY, true);
     }
 
     @Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsEditingActivityTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsEditingActivityTest.kt
index 0b72a68..3b6f7d1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsEditingActivityTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsEditingActivityTest.kt
@@ -10,10 +10,12 @@
 import androidx.test.rule.ActivityTestRule
 import androidx.test.runner.intercepting.SingleActivityFactory
 import com.android.systemui.SysuiTestCase
-import com.android.systemui.broadcast.BroadcastDispatcher
 import com.android.systemui.controls.CustomIconCache
 import com.android.systemui.controls.controller.ControlsControllerImpl
 import com.android.systemui.controls.ui.ControlsUiController
+import com.android.systemui.settings.UserTracker
+import com.android.systemui.util.concurrency.FakeExecutor
+import com.android.systemui.util.time.FakeSystemClock
 import java.util.concurrent.CountDownLatch
 import org.junit.Before
 import org.junit.Rule
@@ -30,9 +32,11 @@
 @RunWith(AndroidTestingRunner::class)
 @TestableLooper.RunWithLooper
 class ControlsEditingActivityTest : SysuiTestCase() {
+    private val uiExecutor = FakeExecutor(FakeSystemClock())
+
     @Mock lateinit var controller: ControlsControllerImpl
 
-    @Mock lateinit var broadcastDispatcher: BroadcastDispatcher
+    @Mock lateinit var userTracker: UserTracker
 
     @Mock lateinit var customIconCache: CustomIconCache
 
@@ -54,8 +58,9 @@
                 ) {
                 override fun create(intent: Intent?): TestableControlsEditingActivity {
                     return TestableControlsEditingActivity(
+                        uiExecutor,
                         controller,
-                        broadcastDispatcher,
+                        userTracker,
                         customIconCache,
                         uiController,
                         mockDispatcher,
@@ -92,13 +97,14 @@
     }
 
     public class TestableControlsEditingActivity(
+        private val executor: FakeExecutor,
         private val controller: ControlsControllerImpl,
-        private val broadcastDispatcher: BroadcastDispatcher,
+        private val userTracker: UserTracker,
         private val customIconCache: CustomIconCache,
         private val uiController: ControlsUiController,
         private val mockDispatcher: OnBackInvokedDispatcher,
         private val latch: CountDownLatch
-    ) : ControlsEditingActivity(controller, broadcastDispatcher, customIconCache, uiController) {
+    ) : ControlsEditingActivity(executor, controller, userTracker, customIconCache, uiController) {
         override fun getOnBackInvokedDispatcher(): OnBackInvokedDispatcher {
             return mockDispatcher
         }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsFavoritingActivityTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsFavoritingActivityTest.kt
index 4b0f7e6..0f06de2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsFavoritingActivityTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsFavoritingActivityTest.kt
@@ -9,10 +9,10 @@
 import androidx.test.rule.ActivityTestRule
 import androidx.test.runner.intercepting.SingleActivityFactory
 import com.android.systemui.SysuiTestCase
-import com.android.systemui.broadcast.BroadcastDispatcher
 import com.android.systemui.controls.controller.ControlsControllerImpl
 import com.android.systemui.controls.ui.ControlsUiController
 import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.settings.UserTracker
 import com.google.common.util.concurrent.MoreExecutors
 import java.util.concurrent.CountDownLatch
 import java.util.concurrent.Executor
@@ -37,7 +37,7 @@
 
     @Mock lateinit var listingController: ControlsListingController
 
-    @Mock lateinit var broadcastDispatcher: BroadcastDispatcher
+    @Mock lateinit var userTracker: UserTracker
 
     @Mock lateinit var uiController: ControlsUiController
 
@@ -60,7 +60,7 @@
                         executor,
                         controller,
                         listingController,
-                        broadcastDispatcher,
+                        userTracker,
                         uiController,
                         mockDispatcher,
                         latch
@@ -97,7 +97,7 @@
         executor: Executor,
         controller: ControlsControllerImpl,
         listingController: ControlsListingController,
-        broadcastDispatcher: BroadcastDispatcher,
+        userTracker: UserTracker,
         uiController: ControlsUiController,
         private val mockDispatcher: OnBackInvokedDispatcher,
         private val latch: CountDownLatch
@@ -106,7 +106,7 @@
             executor,
             controller,
             listingController,
-            broadcastDispatcher,
+            userTracker,
             uiController
         ) {
         override fun getOnBackInvokedDispatcher(): OnBackInvokedDispatcher {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsListingControllerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsListingControllerImplTest.kt
index dedc723..98ff8d1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsListingControllerImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsListingControllerImplTest.kt
@@ -46,6 +46,7 @@
 import org.junit.After
 import org.junit.Assert.assertEquals
 import org.junit.Assert.assertNull
+import org.junit.Assert.assertTrue
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -480,6 +481,19 @@
         assertNull(controller.getCurrentServices()[0].panelActivity)
     }
 
+    @Test
+    fun testListingsNotModifiedByCallback() {
+        // This test checks that if the list passed to the callback is modified, it has no effect
+        // in the resulting services
+        val list = mutableListOf<ServiceInfo>()
+        serviceListingCallbackCaptor.value.onServicesReloaded(list)
+
+        list.add(ServiceInfo(ComponentName("a", "b")))
+        executor.runAllReady()
+
+        assertTrue(controller.getCurrentServices().isEmpty())
+    }
+
     private fun ServiceInfo(
             componentName: ComponentName,
             panelActivityComponentName: ComponentName? = null
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsProviderSelectorActivityTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsProviderSelectorActivityTest.kt
index acc6222..56c3efe 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsProviderSelectorActivityTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsProviderSelectorActivityTest.kt
@@ -25,11 +25,11 @@
 import androidx.test.rule.ActivityTestRule
 import androidx.test.runner.intercepting.SingleActivityFactory
 import com.android.systemui.SysuiTestCase
-import com.android.systemui.broadcast.BroadcastDispatcher
 import com.android.systemui.controls.controller.ControlsController
 import com.android.systemui.controls.ui.ControlsUiController
 import com.android.systemui.dagger.qualifiers.Background
 import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.settings.UserTracker
 import com.google.common.util.concurrent.MoreExecutors
 import java.util.concurrent.CountDownLatch
 import java.util.concurrent.Executor
@@ -56,7 +56,7 @@
 
     @Mock lateinit var controlsController: ControlsController
 
-    @Mock lateinit var broadcastDispatcher: BroadcastDispatcher
+    @Mock lateinit var userTracker: UserTracker
 
     @Mock lateinit var uiController: ControlsUiController
 
@@ -80,7 +80,7 @@
                         backExecutor,
                         listingController,
                         controlsController,
-                        broadcastDispatcher,
+                        userTracker,
                         uiController,
                         mockDispatcher,
                         latch
@@ -118,7 +118,7 @@
         backExecutor: Executor,
         listingController: ControlsListingController,
         controlsController: ControlsController,
-        broadcastDispatcher: BroadcastDispatcher,
+        userTracker: UserTracker,
         uiController: ControlsUiController,
         private val mockDispatcher: OnBackInvokedDispatcher,
         private val latch: CountDownLatch
@@ -128,7 +128,7 @@
             backExecutor,
             listingController,
             controlsController,
-            broadcastDispatcher,
+            userTracker,
             uiController
         ) {
         override fun getOnBackInvokedDispatcher(): OnBackInvokedDispatcher {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsRequestDialogTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsRequestDialogTest.kt
index efb3db7..314b176 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsRequestDialogTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsRequestDialogTest.kt
@@ -34,6 +34,7 @@
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.controls.controller.ControlInfo
 import com.android.systemui.controls.controller.ControlsController
+import com.android.systemui.settings.UserTracker
 import com.android.systemui.util.mockito.capture
 import com.android.systemui.util.mockito.eq
 import org.junit.After
@@ -46,9 +47,10 @@
 import org.mockito.ArgumentCaptor
 import org.mockito.Captor
 import org.mockito.Mock
-import org.mockito.Mockito.`when`
 import org.mockito.Mockito.verify
+import org.mockito.Mockito.`when`
 import org.mockito.MockitoAnnotations
+import java.util.concurrent.Executor
 
 @MediumTest
 @RunWith(AndroidTestingRunner::class)
@@ -67,6 +69,10 @@
     private lateinit var controller: ControlsController
 
     @Mock
+    private lateinit var mainExecutor: Executor
+    @Mock
+    private lateinit var userTracker: UserTracker
+    @Mock
     private lateinit var listingController: ControlsListingController
     @Mock
     private lateinit var iIntentSender: IIntentSender
@@ -81,8 +87,9 @@
             ) {
                     override fun create(intent: Intent?): TestControlsRequestDialog {
                         return TestControlsRequestDialog(
+                                mainExecutor,
                                 controller,
-                                fakeBroadcastDispatcher,
+                                userTracker,
                                 listingController
                         )
                     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/management/TestControlsRequestDialog.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/management/TestControlsRequestDialog.kt
index 3f6308b..ec239f6 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/controls/management/TestControlsRequestDialog.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/controls/management/TestControlsRequestDialog.kt
@@ -16,11 +16,13 @@
 
 package com.android.systemui.controls.management
 
-import com.android.systemui.broadcast.BroadcastDispatcher
 import com.android.systemui.controls.controller.ControlsController
+import com.android.systemui.settings.UserTracker
+import java.util.concurrent.Executor
 
 class TestControlsRequestDialog(
+    mainExecutor: Executor,
     controller: ControlsController,
-    dispatcher: BroadcastDispatcher,
+    userTracker: UserTracker,
     listingController: ControlsListingController
-) : ControlsRequestDialog(controller, dispatcher, listingController)
\ No newline at end of file
+) : ControlsRequestDialog(mainExecutor, controller, userTracker, listingController)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeSensorsTest.java b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeSensorsTest.java
index 2f206ad..07d7e79 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeSensorsTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeSensorsTest.java
@@ -51,6 +51,7 @@
 import com.android.systemui.biometrics.AuthController;
 import com.android.systemui.doze.DozeSensors.TriggerSensor;
 import com.android.systemui.plugins.SensorManagerPlugin;
+import com.android.systemui.settings.UserTracker;
 import com.android.systemui.statusbar.phone.DozeParameters;
 import com.android.systemui.statusbar.policy.DevicePostureController;
 import com.android.systemui.util.sensors.AsyncSensorManager;
@@ -99,6 +100,8 @@
     @Mock
     private DevicePostureController mDevicePostureController;
     @Mock
+    private UserTracker mUserTracker;
+    @Mock
     private ProximitySensor mProximitySensor;
 
     // Capture listeners so that they can be used to send events
@@ -428,7 +431,7 @@
         DozeSensors dozeSensors = new DozeSensors(mSensorManager, mDozeParameters,
                 mAmbientDisplayConfiguration, mWakeLock, mCallback, mProxCallback, mDozeLog,
                 mProximitySensor, mFakeSettings, mAuthController,
-                mDevicePostureController);
+                mDevicePostureController, mUserTracker);
 
         for (TriggerSensor sensor : dozeSensors.mTriggerSensors) {
             assertFalse(sensor.mIgnoresSetting);
@@ -440,7 +443,7 @@
             super(mSensorManager, mDozeParameters,
                     mAmbientDisplayConfiguration, mWakeLock, mCallback, mProxCallback, mDozeLog,
                     mProximitySensor, mFakeSettings, mAuthController,
-                    mDevicePostureController);
+                    mDevicePostureController, mUserTracker);
             for (TriggerSensor sensor : mTriggerSensors) {
                 if (sensor instanceof PluginSensor
                         && ((PluginSensor) sensor).mPluginSensor.getType()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeTriggersTest.java b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeTriggersTest.java
index 6091d3a..82432ce 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeTriggersTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeTriggersTest.java
@@ -49,6 +49,7 @@
 import com.android.systemui.dock.DockManager;
 import com.android.systemui.doze.DozeTriggers.DozingUpdateUiEvent;
 import com.android.systemui.log.SessionTracker;
+import com.android.systemui.settings.UserTracker;
 import com.android.systemui.statusbar.phone.DozeParameters;
 import com.android.systemui.statusbar.policy.DevicePostureController;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
@@ -98,6 +99,8 @@
     @Mock
     private DevicePostureController mDevicePostureController;
     @Mock
+    private UserTracker mUserTracker;
+    @Mock
     private SessionTracker mSessionTracker;
 
     private DozeTriggers mTriggers;
@@ -131,7 +134,7 @@
                 asyncSensorManager, wakeLock, mDockManager, mProximitySensor,
                 mProximityCheck, mDozeLog, mBroadcastDispatcher, new FakeSettings(),
                 mAuthController, mUiEventLogger, mSessionTracker, mKeyguardStateController,
-                mDevicePostureController);
+                mDevicePostureController, mUserTracker);
         mTriggers.setDozeMachine(mMachine);
         waitForSensorManager();
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayAnimationsControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayAnimationsControllerTest.kt
new file mode 100644
index 0000000..99406ed
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayAnimationsControllerTest.kt
@@ -0,0 +1,125 @@
+package com.android.systemui.dreams
+
+import android.animation.Animator
+import android.animation.AnimatorSet
+import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.dreams.complication.ComplicationHostViewController
+import com.android.systemui.statusbar.BlurUtils
+import com.android.systemui.util.mockito.argumentCaptor
+import com.android.systemui.util.mockito.mock
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.Mockito.never
+import org.mockito.Mockito.times
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+class DreamOverlayAnimationsControllerTest : SysuiTestCase() {
+
+    companion object {
+        private const val DREAM_IN_BLUR_ANIMATION_DURATION = 1L
+        private const val DREAM_IN_BLUR_ANIMATION_DELAY = 2L
+        private const val DREAM_IN_COMPLICATIONS_ANIMATION_DURATION = 3L
+        private const val DREAM_IN_TOP_COMPLICATIONS_ANIMATION_DELAY = 4L
+        private const val DREAM_IN_BOTTOM_COMPLICATIONS_ANIMATION_DELAY = 5L
+        private const val DREAM_OUT_TRANSLATION_Y_DISTANCE = 6
+        private const val DREAM_OUT_TRANSLATION_Y_DURATION = 7L
+        private const val DREAM_OUT_TRANSLATION_Y_DELAY_BOTTOM = 8L
+        private const val DREAM_OUT_TRANSLATION_Y_DELAY_TOP = 9L
+        private const val DREAM_OUT_ALPHA_DURATION = 10L
+        private const val DREAM_OUT_ALPHA_DELAY_BOTTOM = 11L
+        private const val DREAM_OUT_ALPHA_DELAY_TOP = 12L
+        private const val DREAM_OUT_BLUR_DURATION = 13L
+    }
+
+    @Mock private lateinit var mockAnimator: AnimatorSet
+    @Mock private lateinit var blurUtils: BlurUtils
+    @Mock private lateinit var hostViewController: ComplicationHostViewController
+    @Mock private lateinit var statusBarViewController: DreamOverlayStatusBarViewController
+    @Mock private lateinit var stateController: DreamOverlayStateController
+    private lateinit var controller: DreamOverlayAnimationsController
+
+    @Before
+    fun setUp() {
+        MockitoAnnotations.initMocks(this)
+        controller =
+            DreamOverlayAnimationsController(
+                blurUtils,
+                hostViewController,
+                statusBarViewController,
+                stateController,
+                DREAM_IN_BLUR_ANIMATION_DURATION,
+                DREAM_IN_BLUR_ANIMATION_DELAY,
+                DREAM_IN_COMPLICATIONS_ANIMATION_DURATION,
+                DREAM_IN_TOP_COMPLICATIONS_ANIMATION_DELAY,
+                DREAM_IN_BOTTOM_COMPLICATIONS_ANIMATION_DELAY,
+                DREAM_OUT_TRANSLATION_Y_DISTANCE,
+                DREAM_OUT_TRANSLATION_Y_DURATION,
+                DREAM_OUT_TRANSLATION_Y_DELAY_BOTTOM,
+                DREAM_OUT_TRANSLATION_Y_DELAY_TOP,
+                DREAM_OUT_ALPHA_DURATION,
+                DREAM_OUT_ALPHA_DELAY_BOTTOM,
+                DREAM_OUT_ALPHA_DELAY_TOP,
+                DREAM_OUT_BLUR_DURATION
+            )
+    }
+
+    @Test
+    fun testExitAnimationOnEnd() {
+        val mockCallback: () -> Unit = mock()
+
+        controller.startExitAnimations(
+            view = mock(),
+            doneCallback = mockCallback,
+            animatorBuilder = { mockAnimator }
+        )
+
+        val captor = argumentCaptor<Animator.AnimatorListener>()
+        verify(mockAnimator).addListener(captor.capture())
+        val listener = captor.value
+
+        verify(mockCallback, never()).invoke()
+        listener.onAnimationEnd(mockAnimator)
+        verify(mockCallback, times(1)).invoke()
+    }
+
+    @Test
+    fun testCancellation() {
+        controller.startExitAnimations(
+            view = mock(),
+            doneCallback = mock(),
+            animatorBuilder = { mockAnimator }
+        )
+
+        verify(mockAnimator, never()).cancel()
+        controller.cancelAnimations()
+        verify(mockAnimator, times(1)).cancel()
+    }
+
+    @Test
+    fun testExitAfterStartWillCancel() {
+        val mockStartAnimator: AnimatorSet = mock()
+        val mockExitAnimator: AnimatorSet = mock()
+
+        controller.startEntryAnimations(view = mock(), animatorBuilder = { mockStartAnimator })
+
+        verify(mockStartAnimator, never()).cancel()
+
+        controller.startExitAnimations(
+            view = mock(),
+            doneCallback = mock(),
+            animatorBuilder = { mockExitAnimator }
+        )
+
+        // Verify that we cancelled the start animator in favor of the exit
+        // animator.
+        verify(mockStartAnimator, times(1)).cancel()
+        verify(mockExitAnimator, never()).cancel()
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayContainerViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayContainerViewControllerTest.java
index 517804d..73c226d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayContainerViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayContainerViewControllerTest.java
@@ -204,7 +204,7 @@
         mController.onViewAttached();
 
         verify(mAnimationsController).startEntryAnimations(mDreamOverlayContainerView);
-        verify(mAnimationsController, never()).cancelRunningEntryAnimations();
+        verify(mAnimationsController, never()).cancelAnimations();
     }
 
     @Test
@@ -221,6 +221,6 @@
         mController.onViewAttached();
         mController.onViewDetached();
 
-        verify(mAnimationsController).cancelRunningEntryAnimations();
+        verify(mAnimationsController).cancelAnimations();
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayServiceTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayServiceTest.java
index f04a37f..ffb8342 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayServiceTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayServiceTest.java
@@ -20,6 +20,7 @@
 
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.Mockito.clearInvocations;
+import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
@@ -337,4 +338,28 @@
         verify(mDreamOverlayComponent).getDreamOverlayContainerViewController();
         verify(mDreamOverlayComponent).getDreamOverlayTouchMonitor();
     }
+
+    @Test
+    public void testWakeUp() throws RemoteException {
+        final IBinder proxy = mService.onBind(new Intent());
+        final IDreamOverlay overlay = IDreamOverlay.Stub.asInterface(proxy);
+
+        // Inform the overlay service of dream starting.
+        overlay.startDream(mWindowParams, mDreamOverlayCallback, DREAM_COMPONENT,
+                true /*shouldShowComplication*/);
+        mMainExecutor.runAllReady();
+
+        final Runnable callback = mock(Runnable.class);
+        mService.onWakeUp(callback);
+        mMainExecutor.runAllReady();
+        verify(mDreamOverlayContainerViewController).wakeUp(callback, mMainExecutor);
+    }
+
+    @Test
+    public void testWakeUpBeforeStartDoesNothing() {
+        final Runnable callback = mock(Runnable.class);
+        mService.onWakeUp(callback);
+        mMainExecutor.runAllReady();
+        verify(mDreamOverlayContainerViewController, never()).wakeUp(callback, mMainExecutor);
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/touch/HideComplicationTouchHandlerTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/touch/HideComplicationTouchHandlerTest.java
index 14a5702..4e3aca7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/dreams/touch/HideComplicationTouchHandlerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/touch/HideComplicationTouchHandlerTest.java
@@ -16,8 +16,6 @@
 
 package com.android.systemui.dreams.touch;
 
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.anyBoolean;
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.never;
@@ -33,6 +31,7 @@
 import androidx.test.filters.SmallTest;
 
 import com.android.systemui.SysuiTestCase;
+import com.android.systemui.dreams.DreamOverlayStateController;
 import com.android.systemui.dreams.complication.Complication;
 import com.android.systemui.shared.system.InputChannelCompat;
 import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
@@ -52,6 +51,7 @@
 @RunWith(AndroidTestingRunner.class)
 public class HideComplicationTouchHandlerTest extends SysuiTestCase {
     private static final int RESTORE_TIMEOUT = 1000;
+    private static final int HIDE_DELAY = 500;
 
     @Mock
     Complication.VisibilityController mVisibilityController;
@@ -71,11 +71,18 @@
     @Mock
     DreamTouchHandler.TouchSession mSession;
 
-    FakeExecutor mFakeExecutor = new FakeExecutor(new FakeSystemClock());
+    @Mock
+    DreamOverlayStateController mStateController;
+
+    FakeSystemClock mClock;
+
+    FakeExecutor mFakeExecutor;
 
     @Before
     public void setup() {
         MockitoAnnotations.initMocks(this);
+        mClock = new FakeSystemClock();
+        mFakeExecutor = new FakeExecutor(mClock);
     }
 
     /**
@@ -86,10 +93,11 @@
         final HideComplicationTouchHandler touchHandler = new HideComplicationTouchHandler(
                 mVisibilityController,
                 RESTORE_TIMEOUT,
+                HIDE_DELAY,
                 mTouchInsetManager,
                 mStatusBarKeyguardViewManager,
                 mFakeExecutor,
-                mHandler);
+                mStateController);
 
         // Report multiple active sessions.
         when(mSession.getActiveSessionCount()).thenReturn(2);
@@ -103,8 +111,10 @@
         // Verify session end.
         verify(mSession).pop();
 
+        mClock.advanceTime(HIDE_DELAY);
+
         // Verify no interaction with visibility controller.
-        verify(mVisibilityController, never()).setVisibility(anyInt(), anyBoolean());
+        verify(mVisibilityController, never()).setVisibility(anyInt());
     }
 
     /**
@@ -115,10 +125,11 @@
         final HideComplicationTouchHandler touchHandler = new HideComplicationTouchHandler(
                 mVisibilityController,
                 RESTORE_TIMEOUT,
+                HIDE_DELAY,
                 mTouchInsetManager,
                 mStatusBarKeyguardViewManager,
                 mFakeExecutor,
-                mHandler);
+                mStateController);
 
         // Report one session.
         when(mSession.getActiveSessionCount()).thenReturn(1);
@@ -132,8 +143,10 @@
         // Verify session end.
         verify(mSession).pop();
 
+        mClock.advanceTime(HIDE_DELAY);
+
         // Verify no interaction with visibility controller.
-        verify(mVisibilityController, never()).setVisibility(anyInt(), anyBoolean());
+        verify(mVisibilityController, never()).setVisibility(anyInt());
     }
 
     /**
@@ -144,10 +157,11 @@
         final HideComplicationTouchHandler touchHandler = new HideComplicationTouchHandler(
                 mVisibilityController,
                 RESTORE_TIMEOUT,
+                HIDE_DELAY,
                 mTouchInsetManager,
                 mStatusBarKeyguardViewManager,
                 mFakeExecutor,
-                mHandler);
+                mStateController);
 
         // Report one session
         when(mSession.getActiveSessionCount()).thenReturn(1);
@@ -177,8 +191,10 @@
         // Verify session ended.
         verify(mSession).pop();
 
+        mClock.advanceTime(HIDE_DELAY);
+
         // Verify no interaction with visibility controller.
-        verify(mVisibilityController, never()).setVisibility(anyInt(), anyBoolean());
+        verify(mVisibilityController, never()).setVisibility(anyInt());
     }
 
     /**
@@ -189,10 +205,11 @@
         final HideComplicationTouchHandler touchHandler = new HideComplicationTouchHandler(
                 mVisibilityController,
                 RESTORE_TIMEOUT,
+                HIDE_DELAY,
                 mTouchInsetManager,
                 mStatusBarKeyguardViewManager,
                 mFakeExecutor,
-                mHandler);
+                mStateController);
 
         // Report one session
         when(mSession.getActiveSessionCount()).thenReturn(1);
@@ -221,11 +238,11 @@
         inputEventListenerCaptor.getValue().onInputEvent(mMotionEvent);
         mFakeExecutor.runAllReady();
 
-        // Verify callback to restore visibility cancelled.
-        verify(mHandler).removeCallbacks(any());
-
+        // Verify visibility controller doesn't hide until after timeout
+        verify(mVisibilityController, never()).setVisibility(eq(View.INVISIBLE));
+        mClock.advanceTime(HIDE_DELAY);
         // Verify visibility controller told to hide complications.
-        verify(mVisibilityController).setVisibility(eq(View.INVISIBLE), anyBoolean());
+        verify(mVisibilityController).setVisibility(eq(View.INVISIBLE));
 
         Mockito.clearInvocations(mVisibilityController, mHandler);
 
@@ -235,11 +252,8 @@
         mFakeExecutor.runAllReady();
 
         // Verify visibility controller told to show complications.
-        ArgumentCaptor<Runnable> delayRunnableCaptor = ArgumentCaptor.forClass(Runnable.class);
-        verify(mHandler).postDelayed(delayRunnableCaptor.capture(),
-                eq(Long.valueOf(RESTORE_TIMEOUT)));
-        delayRunnableCaptor.getValue().run();
-        verify(mVisibilityController).setVisibility(eq(View.VISIBLE), anyBoolean());
+        mClock.advanceTime(RESTORE_TIMEOUT);
+        verify(mVisibilityController).setVisibility(eq(View.VISIBLE));
 
         // Verify session ended.
         verify(mSession).pop();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsDebugRestarterTest.kt b/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsDebugRestarterTest.kt
new file mode 100644
index 0000000..1e7b1f2
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsDebugRestarterTest.kt
@@ -0,0 +1,69 @@
+/*
+ * 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.systemui.flags
+
+import android.test.suitebuilder.annotation.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.keyguard.WakefulnessLifecycle
+import com.android.systemui.keyguard.WakefulnessLifecycle.WAKEFULNESS_ASLEEP
+import com.android.systemui.keyguard.WakefulnessLifecycle.WAKEFULNESS_AWAKE
+import org.junit.Before
+import org.junit.Test
+import org.mockito.ArgumentCaptor
+import org.mockito.Mock
+import org.mockito.Mockito.never
+import org.mockito.Mockito.verify
+import org.mockito.Mockito.`when` as whenever
+import org.mockito.MockitoAnnotations
+
+/**
+ * Be careful with the {FeatureFlagsReleaseRestarter} in this test. It has a call to System.exit()!
+ */
+@SmallTest
+class FeatureFlagsDebugRestarterTest : SysuiTestCase() {
+    private lateinit var restarter: FeatureFlagsDebugRestarter
+
+    @Mock private lateinit var wakefulnessLifecycle: WakefulnessLifecycle
+    @Mock private lateinit var systemExitRestarter: SystemExitRestarter
+
+    @Before
+    fun setup() {
+        MockitoAnnotations.initMocks(this)
+        restarter = FeatureFlagsDebugRestarter(wakefulnessLifecycle, systemExitRestarter)
+    }
+
+    @Test
+    fun testRestart_ImmediateWhenAsleep() {
+        whenever(wakefulnessLifecycle.wakefulness).thenReturn(WAKEFULNESS_ASLEEP)
+        restarter.restart()
+        verify(systemExitRestarter).restart()
+    }
+
+    @Test
+    fun testRestart_WaitsForSceenOff() {
+        whenever(wakefulnessLifecycle.wakefulness).thenReturn(WAKEFULNESS_AWAKE)
+
+        restarter.restart()
+        verify(systemExitRestarter, never()).restart()
+
+        val captor = ArgumentCaptor.forClass(WakefulnessLifecycle.Observer::class.java)
+        verify(wakefulnessLifecycle).addObserver(captor.capture())
+
+        captor.value.onFinishedGoingToSleep()
+
+        verify(systemExitRestarter).restart()
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsReleaseRestarterTest.kt b/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsReleaseRestarterTest.kt
new file mode 100644
index 0000000..68ca48d
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsReleaseRestarterTest.kt
@@ -0,0 +1,145 @@
+/*
+ * 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.systemui.flags
+
+import android.test.suitebuilder.annotation.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.keyguard.WakefulnessLifecycle
+import com.android.systemui.keyguard.WakefulnessLifecycle.WAKEFULNESS_ASLEEP
+import com.android.systemui.keyguard.WakefulnessLifecycle.WAKEFULNESS_AWAKE
+import com.android.systemui.statusbar.policy.BatteryController
+import com.android.systemui.util.concurrency.FakeExecutor
+import com.android.systemui.util.time.FakeSystemClock
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Test
+import org.mockito.ArgumentCaptor
+import org.mockito.Mock
+import org.mockito.Mockito.never
+import org.mockito.Mockito.verify
+import org.mockito.Mockito.`when` as whenever
+import org.mockito.MockitoAnnotations
+
+/**
+ * Be careful with the {FeatureFlagsReleaseRestarter} in this test. It has a call to System.exit()!
+ */
+@SmallTest
+class FeatureFlagsReleaseRestarterTest : SysuiTestCase() {
+    private lateinit var restarter: FeatureFlagsReleaseRestarter
+
+    @Mock private lateinit var wakefulnessLifecycle: WakefulnessLifecycle
+    @Mock private lateinit var batteryController: BatteryController
+    @Mock private lateinit var systemExitRestarter: SystemExitRestarter
+    private val executor = FakeExecutor(FakeSystemClock())
+
+    @Before
+    fun setup() {
+        MockitoAnnotations.initMocks(this)
+        restarter =
+            FeatureFlagsReleaseRestarter(
+                wakefulnessLifecycle,
+                batteryController,
+                executor,
+                systemExitRestarter
+            )
+    }
+
+    @Test
+    fun testRestart_ScheduledWhenReady() {
+        whenever(wakefulnessLifecycle.wakefulness).thenReturn(WAKEFULNESS_ASLEEP)
+        whenever(batteryController.isPluggedIn).thenReturn(true)
+
+        assertThat(executor.numPending()).isEqualTo(0)
+        restarter.restart()
+        assertThat(executor.numPending()).isEqualTo(1)
+    }
+
+    @Test
+    fun testRestart_RestartsWhenIdle() {
+        whenever(wakefulnessLifecycle.wakefulness).thenReturn(WAKEFULNESS_ASLEEP)
+        whenever(batteryController.isPluggedIn).thenReturn(true)
+
+        restarter.restart()
+        verify(systemExitRestarter, never()).restart()
+        executor.advanceClockToLast()
+        executor.runAllReady()
+        verify(systemExitRestarter).restart()
+    }
+
+    @Test
+    fun testRestart_NotScheduledWhenAwake() {
+        whenever(wakefulnessLifecycle.wakefulness).thenReturn(WAKEFULNESS_AWAKE)
+        whenever(batteryController.isPluggedIn).thenReturn(true)
+
+        assertThat(executor.numPending()).isEqualTo(0)
+        restarter.restart()
+        assertThat(executor.numPending()).isEqualTo(0)
+    }
+
+    @Test
+    fun testRestart_NotScheduledWhenNotPluggedIn() {
+        whenever(wakefulnessLifecycle.wakefulness).thenReturn(WAKEFULNESS_ASLEEP)
+        whenever(batteryController.isPluggedIn).thenReturn(false)
+
+        assertThat(executor.numPending()).isEqualTo(0)
+        restarter.restart()
+        assertThat(executor.numPending()).isEqualTo(0)
+    }
+
+    @Test
+    fun testRestart_NotDoubleSheduled() {
+        whenever(wakefulnessLifecycle.wakefulness).thenReturn(WAKEFULNESS_ASLEEP)
+        whenever(batteryController.isPluggedIn).thenReturn(true)
+
+        assertThat(executor.numPending()).isEqualTo(0)
+        restarter.restart()
+        restarter.restart()
+        assertThat(executor.numPending()).isEqualTo(1)
+    }
+
+    @Test
+    fun testWakefulnessLifecycle_CanRestart() {
+        whenever(wakefulnessLifecycle.wakefulness).thenReturn(WAKEFULNESS_AWAKE)
+        whenever(batteryController.isPluggedIn).thenReturn(true)
+        assertThat(executor.numPending()).isEqualTo(0)
+        restarter.restart()
+
+        val captor = ArgumentCaptor.forClass(WakefulnessLifecycle.Observer::class.java)
+        verify(wakefulnessLifecycle).addObserver(captor.capture())
+
+        whenever(wakefulnessLifecycle.wakefulness).thenReturn(WAKEFULNESS_ASLEEP)
+
+        captor.value.onFinishedGoingToSleep()
+        assertThat(executor.numPending()).isEqualTo(1)
+    }
+
+    @Test
+    fun testBatteryController_CanRestart() {
+        whenever(wakefulnessLifecycle.wakefulness).thenReturn(WAKEFULNESS_ASLEEP)
+        whenever(batteryController.isPluggedIn).thenReturn(false)
+        assertThat(executor.numPending()).isEqualTo(0)
+        restarter.restart()
+
+        val captor =
+            ArgumentCaptor.forClass(BatteryController.BatteryStateChangeCallback::class.java)
+        verify(batteryController).addCallback(captor.capture())
+
+        whenever(batteryController.isPluggedIn).thenReturn(true)
+
+        captor.value.onBatteryLevelChanged(0, true, true)
+        assertThat(executor.numPending()).isEqualTo(1)
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogLiteTest.java b/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogLiteTest.java
index 8b1554c..d52616b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogLiteTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogLiteTest.java
@@ -63,6 +63,7 @@
 import com.android.systemui.colorextraction.SysuiColorExtractor;
 import com.android.systemui.plugins.GlobalActions;
 import com.android.systemui.settings.UserContextProvider;
+import com.android.systemui.settings.UserTracker;
 import com.android.systemui.statusbar.NotificationShadeWindowController;
 import com.android.systemui.statusbar.VibratorHelper;
 import com.android.systemui.statusbar.phone.CentralSurfaces;
@@ -103,6 +104,7 @@
     @Mock private SecureSettings mSecureSettings;
     @Mock private Resources mResources;
     @Mock private ConfigurationController mConfigurationController;
+    @Mock private UserTracker mUserTracker;
     @Mock private KeyguardStateController mKeyguardStateController;
     @Mock private UserManager mUserManager;
     @Mock private TrustManager mTrustManager;
@@ -152,6 +154,7 @@
                 mVibratorHelper,
                 mResources,
                 mConfigurationController,
+                mUserTracker,
                 mKeyguardStateController,
                 mUserManager,
                 mTrustManager,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardQuickAffordanceProviderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardQuickAffordanceProviderTest.kt
deleted file mode 100644
index 4d66a16..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardQuickAffordanceProviderTest.kt
+++ /dev/null
@@ -1,302 +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.keyguard
-
-import android.content.pm.PackageManager
-import android.content.pm.ProviderInfo
-import androidx.test.filters.SmallTest
-import com.android.internal.widget.LockPatternUtils
-import com.android.systemui.SystemUIAppComponentFactoryBase
-import com.android.systemui.SysuiTestCase
-import com.android.systemui.flags.FakeFeatureFlags
-import com.android.systemui.flags.Flags
-import com.android.systemui.keyguard.data.quickaffordance.FakeKeyguardQuickAffordanceConfig
-import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceSelectionManager
-import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
-import com.android.systemui.keyguard.data.repository.KeyguardQuickAffordanceRepository
-import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
-import com.android.systemui.keyguard.domain.interactor.KeyguardQuickAffordanceInteractor
-import com.android.systemui.plugins.ActivityStarter
-import com.android.systemui.settings.UserTracker
-import com.android.systemui.shared.keyguard.data.content.KeyguardQuickAffordanceProviderClient as Client
-import com.android.systemui.shared.keyguard.data.content.KeyguardQuickAffordanceProviderContract as Contract
-import com.android.systemui.shared.keyguard.shared.model.KeyguardQuickAffordanceSlots
-import com.android.systemui.statusbar.policy.KeyguardStateController
-import com.android.systemui.util.mockito.mock
-import com.google.common.truth.Truth.assertThat
-import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.runBlocking
-import org.junit.Before
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.junit.runners.JUnit4
-import org.mockito.Mock
-import org.mockito.Mockito.verify
-import org.mockito.MockitoAnnotations
-
-@SmallTest
-@RunWith(JUnit4::class)
-class KeyguardQuickAffordanceProviderTest : SysuiTestCase() {
-
-    @Mock private lateinit var lockPatternUtils: LockPatternUtils
-    @Mock private lateinit var keyguardStateController: KeyguardStateController
-    @Mock private lateinit var userTracker: UserTracker
-    @Mock private lateinit var activityStarter: ActivityStarter
-
-    private lateinit var underTest: KeyguardQuickAffordanceProvider
-
-    @Before
-    fun setUp() {
-        MockitoAnnotations.initMocks(this)
-
-        underTest = KeyguardQuickAffordanceProvider()
-        val quickAffordanceRepository =
-            KeyguardQuickAffordanceRepository(
-                scope = CoroutineScope(IMMEDIATE),
-                backgroundDispatcher = IMMEDIATE,
-                selectionManager = KeyguardQuickAffordanceSelectionManager(),
-                configs =
-                    setOf(
-                        FakeKeyguardQuickAffordanceConfig(
-                            key = AFFORDANCE_1,
-                            pickerIconResourceId = 1,
-                        ),
-                        FakeKeyguardQuickAffordanceConfig(
-                            key = AFFORDANCE_2,
-                            pickerIconResourceId = 2,
-                        ),
-                    ),
-            )
-        underTest.interactor =
-            KeyguardQuickAffordanceInteractor(
-                keyguardInteractor =
-                    KeyguardInteractor(
-                        repository = FakeKeyguardRepository(),
-                    ),
-                registry = mock(),
-                lockPatternUtils = lockPatternUtils,
-                keyguardStateController = keyguardStateController,
-                userTracker = userTracker,
-                activityStarter = activityStarter,
-                featureFlags =
-                    FakeFeatureFlags().apply {
-                        set(Flags.CUSTOMIZABLE_LOCK_SCREEN_QUICK_AFFORDANCES, true)
-                    },
-                repository = { quickAffordanceRepository },
-            )
-
-        underTest.attachInfoForTesting(
-            context,
-            ProviderInfo().apply { authority = Contract.AUTHORITY },
-        )
-        context.contentResolver.addProvider(Contract.AUTHORITY, underTest)
-        context.testablePermissions.setPermission(
-            Contract.PERMISSION,
-            PackageManager.PERMISSION_GRANTED,
-        )
-    }
-
-    @Test
-    fun `onAttachInfo - reportsContext`() {
-        val callback: SystemUIAppComponentFactoryBase.ContextAvailableCallback = mock()
-        underTest.setContextAvailableCallback(callback)
-
-        underTest.attachInfo(context, null)
-
-        verify(callback).onContextAvailable(context)
-    }
-
-    @Test
-    fun getType() {
-        assertThat(underTest.getType(Contract.AffordanceTable.URI))
-            .isEqualTo(
-                "vnd.android.cursor.dir/vnd." +
-                    "${Contract.AUTHORITY}.${Contract.AffordanceTable.TABLE_NAME}"
-            )
-        assertThat(underTest.getType(Contract.SlotTable.URI))
-            .isEqualTo(
-                "vnd.android.cursor.dir/vnd.${Contract.AUTHORITY}.${Contract.SlotTable.TABLE_NAME}"
-            )
-        assertThat(underTest.getType(Contract.SelectionTable.URI))
-            .isEqualTo(
-                "vnd.android.cursor.dir/vnd." +
-                    "${Contract.AUTHORITY}.${Contract.SelectionTable.TABLE_NAME}"
-            )
-    }
-
-    @Test
-    fun `insert and query selection`() =
-        runBlocking(IMMEDIATE) {
-            val slotId = KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START
-            val affordanceId = AFFORDANCE_2
-
-            Client.insertSelection(
-                context = context,
-                slotId = slotId,
-                affordanceId = affordanceId,
-                dispatcher = IMMEDIATE,
-            )
-
-            assertThat(
-                    Client.querySelections(
-                        context = context,
-                        dispatcher = IMMEDIATE,
-                    )
-                )
-                .isEqualTo(
-                    listOf(
-                        Client.Selection(
-                            slotId = slotId,
-                            affordanceId = affordanceId,
-                        )
-                    )
-                )
-        }
-
-    @Test
-    fun `query slots`() =
-        runBlocking(IMMEDIATE) {
-            assertThat(
-                    Client.querySlots(
-                        context = context,
-                        dispatcher = IMMEDIATE,
-                    )
-                )
-                .isEqualTo(
-                    listOf(
-                        Client.Slot(
-                            id = KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START,
-                            capacity = 1,
-                        ),
-                        Client.Slot(
-                            id = KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_END,
-                            capacity = 1,
-                        ),
-                    )
-                )
-        }
-
-    @Test
-    fun `query affordances`() =
-        runBlocking(IMMEDIATE) {
-            assertThat(
-                    Client.queryAffordances(
-                        context = context,
-                        dispatcher = IMMEDIATE,
-                    )
-                )
-                .isEqualTo(
-                    listOf(
-                        Client.Affordance(
-                            id = AFFORDANCE_1,
-                            name = AFFORDANCE_1,
-                            iconResourceId = 1,
-                        ),
-                        Client.Affordance(
-                            id = AFFORDANCE_2,
-                            name = AFFORDANCE_2,
-                            iconResourceId = 2,
-                        ),
-                    )
-                )
-        }
-
-    @Test
-    fun `delete and query selection`() =
-        runBlocking(IMMEDIATE) {
-            Client.insertSelection(
-                context = context,
-                slotId = KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START,
-                affordanceId = AFFORDANCE_1,
-                dispatcher = IMMEDIATE,
-            )
-            Client.insertSelection(
-                context = context,
-                slotId = KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_END,
-                affordanceId = AFFORDANCE_2,
-                dispatcher = IMMEDIATE,
-            )
-
-            Client.deleteSelection(
-                context = context,
-                slotId = KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_END,
-                affordanceId = AFFORDANCE_2,
-                dispatcher = IMMEDIATE,
-            )
-
-            assertThat(
-                    Client.querySelections(
-                        context = context,
-                        dispatcher = IMMEDIATE,
-                    )
-                )
-                .isEqualTo(
-                    listOf(
-                        Client.Selection(
-                            slotId = KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START,
-                            affordanceId = AFFORDANCE_1,
-                        )
-                    )
-                )
-        }
-
-    @Test
-    fun `delete all selections in a slot`() =
-        runBlocking(IMMEDIATE) {
-            Client.insertSelection(
-                context = context,
-                slotId = KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START,
-                affordanceId = AFFORDANCE_1,
-                dispatcher = IMMEDIATE,
-            )
-            Client.insertSelection(
-                context = context,
-                slotId = KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_END,
-                affordanceId = AFFORDANCE_2,
-                dispatcher = IMMEDIATE,
-            )
-
-            Client.deleteAllSelections(
-                context = context,
-                slotId = KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_END,
-                dispatcher = IMMEDIATE,
-            )
-
-            assertThat(
-                    Client.querySelections(
-                        context = context,
-                        dispatcher = IMMEDIATE,
-                    )
-                )
-                .isEqualTo(
-                    listOf(
-                        Client.Selection(
-                            slotId = KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START,
-                            affordanceId = AFFORDANCE_1,
-                        )
-                    )
-                )
-        }
-
-    companion object {
-        private val IMMEDIATE = Dispatchers.Main.immediate
-        private const val AFFORDANCE_1 = "affordance_1"
-        private const val AFFORDANCE_2 = "affordance_2"
-    }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardSliceProviderTest.java b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardSliceProviderTest.java
index 23516c9..729a1cc 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardSliceProviderTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardSliceProviderTest.java
@@ -48,6 +48,7 @@
 import com.android.systemui.SystemUIInitializerImpl;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
+import com.android.systemui.settings.UserTracker;
 import com.android.systemui.statusbar.NotificationMediaManager;
 import com.android.systemui.statusbar.StatusBarState;
 import com.android.systemui.statusbar.phone.DozeParameters;
@@ -93,6 +94,8 @@
     private NextAlarmController mNextAlarmController;
     @Mock
     private KeyguardUpdateMonitor mKeyguardUpdateMonitor;
+    @Mock
+    private UserTracker mUserTracker;
     private TestableKeyguardSliceProvider mProvider;
     private boolean mIsZenMode;
 
@@ -105,6 +108,7 @@
         mProvider.attachInfo(getContext(), null);
         reset(mContentResolver);
         SliceProvider.setSpecs(new HashSet<>(Arrays.asList(SliceSpecs.LIST)));
+        when(mUserTracker.getUserId()).thenReturn(100);
     }
 
     @After
@@ -267,6 +271,7 @@
             mKeyguardBypassController = KeyguardSliceProviderTest.this.mKeyguardBypassController;
             mMediaManager = KeyguardSliceProviderTest.this.mNotificationMediaManager;
             mKeyguardUpdateMonitor = KeyguardSliceProviderTest.this.mKeyguardUpdateMonitor;
+            mUserTracker = KeyguardSliceProviderTest.this.mUserTracker;
         }
 
         @Override
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
index b6780a1..45aaaa2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
@@ -57,6 +57,7 @@
 import com.android.systemui.dreams.DreamOverlayStateController;
 import com.android.systemui.dump.DumpManager;
 import com.android.systemui.navigationbar.NavigationModeController;
+import com.android.systemui.settings.UserTracker;
 import com.android.systemui.shade.ShadeController;
 import com.android.systemui.statusbar.NotificationShadeDepthController;
 import com.android.systemui.statusbar.NotificationShadeWindowController;
@@ -86,6 +87,7 @@
 public class KeyguardViewMediatorTest extends SysuiTestCase {
     private KeyguardViewMediator mViewMediator;
 
+    private @Mock UserTracker mUserTracker;
     private @Mock DevicePolicyManager mDevicePolicyManager;
     private @Mock LockPatternUtils mLockPatternUtils;
     private @Mock KeyguardUpdateMonitor mUpdateMonitor;
@@ -286,6 +288,7 @@
     private void createAndStartViewMediator() {
         mViewMediator = new KeyguardViewMediator(
                 mContext,
+                mUserTracker,
                 mFalsingCollector,
                 mLockPatternUtils,
                 mBroadcastDispatcher,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/CameraQuickAffordanceConfigTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/CameraQuickAffordanceConfigTest.kt
new file mode 100644
index 0000000..623becf
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/CameraQuickAffordanceConfigTest.kt
@@ -0,0 +1,61 @@
+/*
+ *  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.keyguard.data.quickaffordance
+
+import android.app.StatusBarManager
+import android.content.Context
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.camera.CameraGestureHelper
+import org.junit.Assert.assertEquals
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+import org.mockito.Mock
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@RunWith(JUnit4::class)
+class CameraQuickAffordanceConfigTest : SysuiTestCase() {
+
+    @Mock private lateinit var cameraGestureHelper: CameraGestureHelper
+    @Mock private lateinit var context: Context
+    private lateinit var underTest: CameraQuickAffordanceConfig
+
+    @Before
+    fun setUp() {
+        MockitoAnnotations.initMocks(this)
+        underTest = CameraQuickAffordanceConfig(
+                context,
+                cameraGestureHelper,
+        )
+    }
+
+    @Test
+    fun `affordance triggered -- camera launch called`() {
+        //when
+        val result = underTest.onTriggered(null)
+
+        //then
+        verify(cameraGestureHelper)
+                .launchCamera(StatusBarManager.CAMERA_LAUNCH_SOURCE_QUICK_AFFORDANCE)
+        assertEquals(KeyguardQuickAffordanceConfig.OnTriggeredResult.Handled, result)
+    }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardQuickAffordanceRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardQuickAffordanceRepositoryTest.kt
index 5a7f2bb..dceb492 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardQuickAffordanceRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardQuickAffordanceRepositoryTest.kt
@@ -18,13 +18,13 @@
 package com.android.systemui.keyguard.data.repository
 
 import androidx.test.filters.SmallTest
+import com.android.systemui.R
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.keyguard.data.quickaffordance.FakeKeyguardQuickAffordanceConfig
 import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceConfig
 import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceSelectionManager
 import com.android.systemui.keyguard.shared.model.KeyguardQuickAffordancePickerRepresentation
 import com.android.systemui.keyguard.shared.model.KeyguardSlotPickerRepresentation
-import com.android.systemui.shared.keyguard.shared.model.KeyguardQuickAffordanceSlots
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.Dispatchers
@@ -53,6 +53,7 @@
         config2 = FakeKeyguardQuickAffordanceConfig("built_in:2")
         underTest =
             KeyguardQuickAffordanceRepository(
+                appContext = context,
                 scope = CoroutineScope(IMMEDIATE),
                 backgroundDispatcher = IMMEDIATE,
                 selectionManager = KeyguardQuickAffordanceSelectionManager(),
@@ -119,16 +120,32 @@
 
     @Test
     fun getSlotPickerRepresentations() {
+        val slot1 = "slot1"
+        val slot2 = "slot2"
+        val slot3 = "slot3"
+        context.orCreateTestableResources.addOverride(
+            R.array.config_keyguardQuickAffordanceSlots,
+            arrayOf(
+                "$slot1:2",
+                "$slot2:4",
+                "$slot3:5",
+            ),
+        )
+
         assertThat(underTest.getSlotPickerRepresentations())
             .isEqualTo(
                 listOf(
                     KeyguardSlotPickerRepresentation(
-                        id = KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START,
-                        maxSelectedAffordances = 1,
+                        id = slot1,
+                        maxSelectedAffordances = 2,
                     ),
                     KeyguardSlotPickerRepresentation(
-                        id = KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_END,
-                        maxSelectedAffordances = 1,
+                        id = slot2,
+                        maxSelectedAffordances = 4,
+                    ),
+                    KeyguardSlotPickerRepresentation(
+                        id = slot3,
+                        maxSelectedAffordances = 5,
                     ),
                 )
             )
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorParameterizedTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorParameterizedTest.kt
index 8b6603d..737f242 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorParameterizedTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorParameterizedTest.kt
@@ -230,6 +230,7 @@
             FakeKeyguardQuickAffordanceConfig(BuiltInKeyguardQuickAffordanceKeys.QR_CODE_SCANNER)
         val quickAffordanceRepository =
             KeyguardQuickAffordanceRepository(
+                appContext = context,
                 scope = CoroutineScope(IMMEDIATE),
                 backgroundDispatcher = IMMEDIATE,
                 selectionManager = KeyguardQuickAffordanceSelectionManager(),
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorTest.kt
index 3364535..ffcf832 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorTest.kt
@@ -92,6 +92,7 @@
 
         val quickAffordanceRepository =
             KeyguardQuickAffordanceRepository(
+                appContext = context,
                 scope = CoroutineScope(IMMEDIATE),
                 backgroundDispatcher = IMMEDIATE,
                 selectionManager = KeyguardQuickAffordanceSelectionManager(),
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt
index 78148c4..fa2ac46 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt
@@ -117,6 +117,7 @@
             .thenReturn(LockPatternUtils.StrongAuthTracker.STRONG_AUTH_NOT_REQUIRED)
         val quickAffordanceRepository =
             KeyguardQuickAffordanceRepository(
+                appContext = context,
                 scope = CoroutineScope(IMMEDIATE),
                 backgroundDispatcher = IMMEDIATE,
                 selectionManager = KeyguardQuickAffordanceSelectionManager(),
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/models/player/SeekBarViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/models/player/SeekBarViewModelTest.kt
index 7cd8e74..56c91bc 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/models/player/SeekBarViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/models/player/SeekBarViewModelTest.kt
@@ -42,6 +42,7 @@
 import org.mockito.ArgumentCaptor
 import org.mockito.Mock
 import org.mockito.Mockito.any
+import org.mockito.Mockito.anyInt
 import org.mockito.Mockito.eq
 import org.mockito.Mockito.mock
 import org.mockito.Mockito.never
@@ -464,7 +465,7 @@
     fun onFalseTapOrTouch() {
         whenever(mockController.getTransportControls()).thenReturn(mockTransport)
         whenever(falsingManager.isFalseTouch(Classifier.MEDIA_SEEKBAR)).thenReturn(true)
-        whenever(falsingManager.isFalseTap(FalsingManager.LOW_PENALTY)).thenReturn(true)
+        whenever(falsingManager.isFalseTap(anyInt())).thenReturn(true)
         viewModel.updateController(mockController)
         val pos = 169
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaDataFilterTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaDataFilterTest.kt
index 575b1c6..9d33e6f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaDataFilterTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaDataFilterTest.kt
@@ -22,13 +22,13 @@
 import androidx.test.filters.SmallTest
 import com.android.internal.logging.InstanceId
 import com.android.systemui.SysuiTestCase
-import com.android.systemui.broadcast.BroadcastDispatcher
 import com.android.systemui.broadcast.BroadcastSender
 import com.android.systemui.media.controls.MediaTestUtils
 import com.android.systemui.media.controls.models.player.MediaData
 import com.android.systemui.media.controls.models.recommendation.SmartspaceMediaData
 import com.android.systemui.media.controls.ui.MediaPlayerData
 import com.android.systemui.media.controls.util.MediaUiEventLogger
+import com.android.systemui.settings.UserTracker
 import com.android.systemui.statusbar.NotificationLockscreenUserManager
 import com.android.systemui.util.mockito.any
 import com.android.systemui.util.mockito.eq
@@ -64,7 +64,7 @@
 class MediaDataFilterTest : SysuiTestCase() {
 
     @Mock private lateinit var listener: MediaDataManager.Listener
-    @Mock private lateinit var broadcastDispatcher: BroadcastDispatcher
+    @Mock private lateinit var userTracker: UserTracker
     @Mock private lateinit var broadcastSender: BroadcastSender
     @Mock private lateinit var mediaDataManager: MediaDataManager
     @Mock private lateinit var lockscreenUserManager: NotificationLockscreenUserManager
@@ -85,7 +85,7 @@
         mediaDataFilter =
             MediaDataFilter(
                 context,
-                broadcastDispatcher,
+                userTracker,
                 broadcastSender,
                 lockscreenUserManager,
                 executor,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaDataManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaDataManagerTest.kt
index 11eb26b..8bd7163 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaDataManagerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaDataManagerTest.kt
@@ -20,6 +20,8 @@
 import android.app.Notification.MediaStyle
 import android.app.PendingIntent
 import android.app.smartspace.SmartspaceAction
+import android.app.smartspace.SmartspaceConfig
+import android.app.smartspace.SmartspaceManager
 import android.app.smartspace.SmartspaceTarget
 import android.content.Intent
 import android.graphics.Bitmap
@@ -106,6 +108,7 @@
     lateinit var metadataBuilder: MediaMetadata.Builder
     lateinit var backgroundExecutor: FakeExecutor
     lateinit var foregroundExecutor: FakeExecutor
+    lateinit var uiExecutor: FakeExecutor
     @Mock lateinit var dumpManager: DumpManager
     @Mock lateinit var broadcastDispatcher: BroadcastDispatcher
     @Mock lateinit var mediaTimeoutListener: MediaTimeoutListener
@@ -117,6 +120,7 @@
     @Mock lateinit var listener: MediaDataManager.Listener
     @Mock lateinit var pendingIntent: PendingIntent
     @Mock lateinit var activityStarter: ActivityStarter
+    @Mock lateinit var smartspaceManager: SmartspaceManager
     lateinit var smartspaceMediaDataProvider: SmartspaceMediaDataProvider
     @Mock lateinit var mediaSmartspaceTarget: SmartspaceTarget
     @Mock private lateinit var mediaRecommendationItem: SmartspaceAction
@@ -131,6 +135,7 @@
     @Mock private lateinit var tunerService: TunerService
     @Captor lateinit var tunableCaptor: ArgumentCaptor<TunerService.Tunable>
     @Captor lateinit var callbackCaptor: ArgumentCaptor<(String, PlaybackState) -> Unit>
+    @Captor lateinit var smartSpaceConfigBuilderCaptor: ArgumentCaptor<SmartspaceConfig>
 
     private val instanceIdSequence = InstanceIdSequenceFake(1 shl 20)
 
@@ -145,6 +150,7 @@
     fun setup() {
         foregroundExecutor = FakeExecutor(clock)
         backgroundExecutor = FakeExecutor(clock)
+        uiExecutor = FakeExecutor(clock)
         smartspaceMediaDataProvider = SmartspaceMediaDataProvider()
         Settings.Secure.putInt(
             context.contentResolver,
@@ -155,6 +161,7 @@
             MediaDataManager(
                 context = context,
                 backgroundExecutor = backgroundExecutor,
+                uiExecutor = uiExecutor,
                 foregroundExecutor = foregroundExecutor,
                 mediaControllerFactory = mediaControllerFactory,
                 broadcastDispatcher = broadcastDispatcher,
@@ -172,7 +179,8 @@
                 systemClock = clock,
                 tunerService = tunerService,
                 mediaFlags = mediaFlags,
-                logger = logger
+                logger = logger,
+                smartspaceManager = smartspaceManager,
             )
         verify(tunerService)
             .addTunable(capture(tunableCaptor), eq(Settings.Secure.MEDIA_CONTROLS_RECOMMENDATION))
@@ -191,6 +199,7 @@
                 putString(MediaMetadata.METADATA_KEY_ARTIST, SESSION_ARTIST)
                 putString(MediaMetadata.METADATA_KEY_TITLE, SESSION_TITLE)
             }
+        verify(smartspaceManager).createSmartspaceSession(capture(smartSpaceConfigBuilderCaptor))
         whenever(mediaControllerFactory.create(eq(session.sessionToken))).thenReturn(controller)
         whenever(controller.transportControls).thenReturn(transportControls)
         whenever(controller.playbackInfo).thenReturn(playbackInfo)
@@ -767,15 +776,14 @@
             .onSmartspaceMediaDataLoaded(anyObject(), anyObject(), anyBoolean())
     }
 
-    @Ignore("b/233283726")
     @Test
     fun testOnSmartspaceMediaDataLoaded_hasNoneMediaTarget_callsRemoveListener() {
         smartspaceMediaDataProvider.onTargetsAvailable(listOf(mediaSmartspaceTarget))
         verify(logger).getNewInstanceId()
 
         smartspaceMediaDataProvider.onTargetsAvailable(listOf())
-        foregroundExecutor.advanceClockToLast()
-        foregroundExecutor.runAllReady()
+        uiExecutor.advanceClockToLast()
+        uiExecutor.runAllReady()
 
         verify(listener).onSmartspaceMediaDataRemoved(eq(KEY_MEDIA_SMARTSPACE), eq(false))
         verifyNoMoreInteractions(logger)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaControlPanelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaControlPanelTest.kt
index eb1f5e4..761773b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaControlPanelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaControlPanelTest.kt
@@ -212,7 +212,10 @@
     private lateinit var recSubtitle3: TextView
     private var shouldShowBroadcastButton: Boolean = false
     private val fakeFeatureFlag =
-        FakeFeatureFlags().apply { this.set(Flags.UMO_SURFACE_RIPPLE, false) }
+        FakeFeatureFlags().apply {
+            this.set(Flags.UMO_SURFACE_RIPPLE, false)
+            this.set(Flags.MEDIA_FALSING_PENALTY, true)
+        }
 
     @JvmField @Rule val mockito = MockitoJUnit.rule()
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/data/ShellRecentTaskListProviderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/data/ShellRecentTaskListProviderTest.kt
index 939af16..d35a212 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/data/ShellRecentTaskListProviderTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/data/ShellRecentTaskListProviderTest.kt
@@ -4,6 +4,7 @@
 import android.testing.AndroidTestingRunner
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
+import com.android.systemui.settings.UserTracker
 import com.android.systemui.util.mockito.any
 import com.android.systemui.util.mockito.mock
 import com.android.systemui.util.mockito.whenever
@@ -11,11 +12,11 @@
 import com.android.wm.shell.util.GroupedRecentTaskInfo
 import com.google.common.truth.Truth.assertThat
 import java.util.*
+import java.util.function.Consumer
 import kotlinx.coroutines.Dispatchers
 import kotlinx.coroutines.runBlocking
 import org.junit.Test
 import org.junit.runner.RunWith
-import java.util.function.Consumer
 
 @RunWith(AndroidTestingRunner::class)
 @SmallTest
@@ -23,8 +24,14 @@
 
     private val dispatcher = Dispatchers.Unconfined
     private val recentTasks: RecentTasks = mock()
+    private val userTracker: UserTracker = mock()
     private val recentTaskListProvider =
-        ShellRecentTaskListProvider(dispatcher, Runnable::run, Optional.of(recentTasks))
+        ShellRecentTaskListProvider(
+            dispatcher,
+            Runnable::run,
+            Optional.of(recentTasks),
+            userTracker
+        )
 
     @Test
     fun loadRecentTasks_oneTask_returnsTheSameTask() {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/AirplaneModeTileTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/AirplaneModeTileTest.kt
index 73a0cbc..030c59f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/AirplaneModeTileTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/AirplaneModeTileTest.kt
@@ -34,6 +34,7 @@
 import com.android.systemui.qs.QSHost
 import com.android.systemui.qs.logging.QSLogger
 import com.android.systemui.qs.tileimpl.QSTileImpl
+import com.android.systemui.settings.UserTracker
 import com.android.systemui.util.settings.GlobalSettings
 import com.google.common.truth.Truth.assertThat
 import dagger.Lazy
@@ -64,6 +65,8 @@
     private lateinit var mConnectivityManager: Lazy<ConnectivityManager>
     @Mock
     private lateinit var mGlobalSettings: GlobalSettings
+    @Mock
+    private lateinit var mUserTracker: UserTracker
     private lateinit var mTestableLooper: TestableLooper
     private lateinit var mTile: AirplaneModeTile
 
@@ -87,7 +90,8 @@
             mQsLogger,
             mBroadcastDispatcher,
             mConnectivityManager,
-            mGlobalSettings)
+            mGlobalSettings,
+            mUserTracker)
     }
 
     @Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/UserDetailViewAdapterTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/UserDetailViewAdapterTest.kt
index 3131f60..08a90b7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/UserDetailViewAdapterTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/UserDetailViewAdapterTest.kt
@@ -42,27 +42,21 @@
 import org.mockito.ArgumentMatchers.anyBoolean
 import org.mockito.ArgumentMatchers.anyInt
 import org.mockito.Mock
-import org.mockito.Mockito.`when`
 import org.mockito.Mockito.mock
 import org.mockito.Mockito.verify
+import org.mockito.Mockito.`when`
 import org.mockito.MockitoAnnotations
 
 @RunWith(AndroidTestingRunner::class)
 @SmallTest
 class UserDetailViewAdapterTest : SysuiTestCase() {
 
-    @Mock
-    private lateinit var mUserSwitcherController: UserSwitcherController
-    @Mock
-    private lateinit var mParent: ViewGroup
-    @Mock
-    private lateinit var mUserDetailItemView: UserDetailItemView
-    @Mock
-    private lateinit var mOtherView: View
-    @Mock
-    private lateinit var mInflatedUserDetailItemView: UserDetailItemView
-    @Mock
-    private lateinit var mLayoutInflater: LayoutInflater
+    @Mock private lateinit var mUserSwitcherController: UserSwitcherController
+    @Mock private lateinit var mParent: ViewGroup
+    @Mock private lateinit var mUserDetailItemView: UserDetailItemView
+    @Mock private lateinit var mOtherView: View
+    @Mock private lateinit var mInflatedUserDetailItemView: UserDetailItemView
+    @Mock private lateinit var mLayoutInflater: LayoutInflater
     private var falsingManagerFake: FalsingManagerFake = FalsingManagerFake()
     private lateinit var adapter: UserDetailView.Adapter
     private lateinit var uiEventLogger: UiEventLoggerFake
@@ -77,10 +71,13 @@
         `when`(mLayoutInflater.inflate(anyInt(), any(ViewGroup::class.java), anyBoolean()))
             .thenReturn(mInflatedUserDetailItemView)
         `when`(mParent.context).thenReturn(mContext)
-        adapter = UserDetailView.Adapter(
-            mContext, mUserSwitcherController, uiEventLogger,
-            falsingManagerFake
-        )
+        adapter =
+            UserDetailView.Adapter(
+                mContext,
+                mUserSwitcherController,
+                uiEventLogger,
+                falsingManagerFake
+            )
         mPicture = UserIcons.convertToBitmap(mContext.getDrawable(R.drawable.ic_avatar_user))
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/ImageExporterTest.java b/packages/SystemUI/tests/src/com/android/systemui/screenshot/ImageExporterTest.java
index f4bc232..df3a62f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/screenshot/ImageExporterTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/ImageExporterTest.java
@@ -75,7 +75,7 @@
     private static final byte[] EXIF_FILE_TAG = "Exif\u0000\u0000".getBytes(US_ASCII);
 
     private static final ZonedDateTime CAPTURE_TIME =
-            ZonedDateTime.of(LocalDateTime.of(2020, 12, 15, 13, 15), ZoneId.of("EST"));
+            ZonedDateTime.of(LocalDateTime.of(2020, 12, 15, 13, 15), ZoneId.of("America/New_York"));
 
     private FakeFeatureFlags mFeatureFlags = new FakeFeatureFlags();
     @Mock
diff --git a/packages/SystemUI/tests/src/com/android/systemui/settings/CurrentUserTrackerTest.java b/packages/SystemUI/tests/src/com/android/systemui/settings/CurrentUserTrackerTest.java
deleted file mode 100644
index 1b515c6..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/settings/CurrentUserTrackerTest.java
+++ /dev/null
@@ -1,74 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package com.android.systemui.settings;
-
-import android.content.Intent;
-
-import androidx.test.filters.SmallTest;
-
-import com.android.systemui.SysuiTestCase;
-import com.android.systemui.broadcast.BroadcastDispatcher;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-
-/**
- * Testing functionality of the current user tracker
- */
-@SmallTest
-public class CurrentUserTrackerTest extends SysuiTestCase {
-
-    private CurrentUserTracker mTracker;
-    private CurrentUserTracker.UserReceiver mReceiver;
-    @Mock
-    private BroadcastDispatcher mBroadcastDispatcher;
-
-    @Before
-    public void setUp() {
-        MockitoAnnotations.initMocks(this);
-        mReceiver = new CurrentUserTracker.UserReceiver(mBroadcastDispatcher);
-        mTracker = new CurrentUserTracker(mReceiver) {
-            @Override
-            public void onUserSwitched(int newUserId) {
-                stopTracking();
-            }
-        };
-    }
-
-    @Test
-    public void testBroadCastDoesntCrashOnConcurrentModification() {
-        mTracker.startTracking();
-        CurrentUserTracker secondTracker = new CurrentUserTracker(mReceiver) {
-            @Override
-            public void onUserSwitched(int newUserId) {
-                stopTracking();
-            }
-        };
-        secondTracker.startTracking();
-        triggerUserSwitch();
-    }
-    /**
-     * Simulates a user switch event.
-     */
-    private void triggerUserSwitch() {
-        Intent intent = new Intent(Intent.ACTION_USER_SWITCHED);
-        intent.putExtra(Intent.EXTRA_USER_HANDLE, 1);
-        mReceiver.onReceive(getContext(), intent);
-    }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/settings/brightness/BrightnessDialogTest.kt b/packages/SystemUI/tests/src/com/android/systemui/settings/brightness/BrightnessDialogTest.kt
index 1130bda..9d1802a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/settings/brightness/BrightnessDialogTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/settings/brightness/BrightnessDialogTest.kt
@@ -28,9 +28,10 @@
 import androidx.test.runner.intercepting.SingleActivityFactory
 import com.android.systemui.R
 import com.android.systemui.SysuiTestCase
-import com.android.systemui.broadcast.BroadcastDispatcher
+import com.android.systemui.settings.UserTracker
 import com.android.systemui.util.mockito.any
 import com.google.common.truth.Truth.assertThat
+import java.util.concurrent.Executor
 import org.junit.After
 import org.junit.Before
 import org.junit.Rule
@@ -45,7 +46,9 @@
 @TestableLooper.RunWithLooper
 class BrightnessDialogTest : SysuiTestCase() {
 
+    @Mock private lateinit var userTracker: UserTracker
     @Mock private lateinit var brightnessSliderControllerFactory: BrightnessSliderController.Factory
+    @Mock private lateinit var mainExecutor: Executor
     @Mock private lateinit var backgroundHandler: Handler
     @Mock private lateinit var brightnessSliderController: BrightnessSliderController
 
@@ -56,8 +59,9 @@
             object : SingleActivityFactory<TestDialog>(TestDialog::class.java) {
                 override fun create(intent: Intent?): TestDialog {
                     return TestDialog(
-                        fakeBroadcastDispatcher,
+                        userTracker,
                         brightnessSliderControllerFactory,
+                        mainExecutor,
                         backgroundHandler
                     )
                 }
@@ -100,8 +104,15 @@
     }
 
     class TestDialog(
-        broadcastDispatcher: BroadcastDispatcher,
+        userTracker: UserTracker,
         brightnessSliderControllerFactory: BrightnessSliderController.Factory,
+        mainExecutor: Executor,
         backgroundHandler: Handler
-    ) : BrightnessDialog(broadcastDispatcher, brightnessSliderControllerFactory, backgroundHandler)
+    ) :
+        BrightnessDialog(
+            userTracker,
+            brightnessSliderControllerFactory,
+            mainExecutor,
+            backgroundHandler
+        )
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/CombinedShadeHeaderConstraintsTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/CombinedShadeHeaderConstraintsTest.kt
index d7eb337..bc17c19 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/CombinedShadeHeaderConstraintsTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/CombinedShadeHeaderConstraintsTest.kt
@@ -363,6 +363,22 @@
         }
     }
 
+    @Test
+    fun testEmptyCutoutDateIconsAreConstrainedWidth() {
+        CombinedShadeHeadersConstraintManagerImpl.emptyCutoutConstraints()()
+
+        assertThat(qqsConstraint.getConstraint(R.id.date).layout.constrainedWidth).isTrue()
+        assertThat(qqsConstraint.getConstraint(R.id.statusIcons).layout.constrainedWidth).isTrue()
+    }
+
+    @Test
+    fun testCenterCutoutDateIconsAreConstrainedWidth() {
+        CombinedShadeHeadersConstraintManagerImpl.centerCutoutConstraints(false, 10)()
+
+        assertThat(qqsConstraint.getConstraint(R.id.date).layout.constrainedWidth).isTrue()
+        assertThat(qqsConstraint.getConstraint(R.id.statusIcons).layout.constrainedWidth).isTrue()
+    }
+
     private operator fun ConstraintsChanges.invoke() {
         qqsConstraintsChanges?.invoke(qqsConstraint)
         qsConstraintsChanges?.invoke(qsConstraint)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java
index 7d2251e..69a4559 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java
@@ -133,6 +133,7 @@
 import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator;
 import com.android.systemui.statusbar.notification.row.ExpandableView;
 import com.android.systemui.statusbar.notification.row.ExpandableView.OnHeightChangedListener;
+import com.android.systemui.statusbar.notification.row.NotificationGutsManager;
 import com.android.systemui.statusbar.notification.stack.AmbientState;
 import com.android.systemui.statusbar.notification.stack.NotificationListContainer;
 import com.android.systemui.statusbar.notification.stack.NotificationRoundnessManager;
@@ -198,6 +199,7 @@
     @Mock private KeyguardBottomAreaView mQsFrame;
     @Mock private HeadsUpManagerPhone mHeadsUpManager;
     @Mock private NotificationShelfController mNotificationShelfController;
+    @Mock private NotificationGutsManager mGutsManager;
     @Mock private KeyguardStatusBarView mKeyguardStatusBar;
     @Mock private KeyguardUserSwitcherView mUserSwitcherView;
     @Mock private ViewStub mUserSwitcherStubView;
@@ -453,6 +455,7 @@
                 () -> flingAnimationUtilsBuilder, mStatusBarTouchableRegionManager,
                 mConversationNotificationManager, mMediaHierarchyManager,
                 mStatusBarKeyguardViewManager,
+                mGutsManager,
                 mNotificationsQSContainerController,
                 mNotificationStackScrollLayoutController,
                 mKeyguardStatusViewComponentFactory,
@@ -754,6 +757,8 @@
 
     @Test
     public void testOnTouchEvent_expansionResumesAfterBriefTouch() {
+        mFalsingManager.setIsClassifierEnabled(true);
+        mFalsingManager.setIsFalseTouch(false);
         // Start shade collapse with swipe up
         onTouchEvent(MotionEvent.obtain(0L /* downTime */,
                 0L /* eventTime */, MotionEvent.ACTION_DOWN, 0f /* x */, 0f /* y */,
@@ -1119,6 +1124,19 @@
     }
 
     @Test
+    public void testUnlockedSplitShadeTransitioningToKeyguard_closesQS() {
+        enableSplitShade(true);
+        mStatusBarStateController.setState(SHADE);
+        mNotificationPanelViewController.setQsExpanded(true);
+
+        mStatusBarStateController.setState(KEYGUARD);
+
+
+        assertThat(mNotificationPanelViewController.isQsExpanded()).isEqualTo(false);
+        assertThat(mNotificationPanelViewController.isQsExpandImmediate()).isEqualTo(false);
+    }
+
+    @Test
     public void testSwitchesToCorrectClockInSinglePaneShade() {
         mStatusBarStateController.setState(KEYGUARD);
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt
index db7e017..c3207c2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt
@@ -33,6 +33,7 @@
 import com.android.systemui.keyguard.ui.viewmodel.KeyguardBouncerViewModel
 import com.android.systemui.shade.NotificationShadeWindowView.InteractionEventHandler
 import com.android.systemui.statusbar.LockscreenShadeTransitionController
+import com.android.systemui.statusbar.NotificationInsetsController
 import com.android.systemui.statusbar.NotificationShadeDepthController
 import com.android.systemui.statusbar.NotificationShadeWindowController
 import com.android.systemui.statusbar.SysuiStatusBarStateController
@@ -94,6 +95,8 @@
     private lateinit var phoneStatusBarViewController: PhoneStatusBarViewController
     @Mock
     private lateinit var pulsingGestureListener: PulsingGestureListener
+    @Mock
+    private lateinit var notificationInsetsController: NotificationInsetsController
     @Mock lateinit var keyguardBouncerComponentFactory: KeyguardBouncerComponent.Factory
     @Mock lateinit var keyguardBouncerContainer: ViewGroup
     @Mock lateinit var keyguardBouncerComponent: KeyguardBouncerComponent
@@ -124,6 +127,7 @@
             centralSurfaces,
             notificationShadeWindowController,
             keyguardUnlockAnimationController,
+            notificationInsetsController,
             ambientState,
             pulsingGestureListener,
             featureFlags,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.java
index a6c80ab6..4bf00c4 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.java
@@ -43,6 +43,7 @@
 import com.android.systemui.keyguard.ui.viewmodel.KeyguardBouncerViewModel;
 import com.android.systemui.statusbar.DragDownHelper;
 import com.android.systemui.statusbar.LockscreenShadeTransitionController;
+import com.android.systemui.statusbar.NotificationInsetsController;
 import com.android.systemui.statusbar.NotificationShadeDepthController;
 import com.android.systemui.statusbar.NotificationShadeWindowController;
 import com.android.systemui.statusbar.SysuiStatusBarStateController;
@@ -91,6 +92,7 @@
     @Mock private FeatureFlags mFeatureFlags;
     @Mock private KeyguardBouncerViewModel mKeyguardBouncerViewModel;
     @Mock private KeyguardBouncerComponent.Factory mKeyguardBouncerComponentFactory;
+    @Mock private NotificationInsetsController mNotificationInsetsController;
 
     @Captor private ArgumentCaptor<NotificationShadeWindowView.InteractionEventHandler>
             mInteractionEventHandlerCaptor;
@@ -125,6 +127,7 @@
                 mCentralSurfaces,
                 mNotificationShadeWindowController,
                 mKeyguardUnlockAnimationController,
+                mNotificationInsetsController,
                 mAmbientState,
                 mPulsingGestureListener,
                 mFeatureFlags,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shared/clocks/DefaultClockProviderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shared/clocks/DefaultClockProviderTest.kt
index 539a54b..f5bed79 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shared/clocks/DefaultClockProviderTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shared/clocks/DefaultClockProviderTest.kt
@@ -139,12 +139,19 @@
     }
 
     @Test
-    fun defaultClock_events_onFontSettingChanged() {
+    fun defaultSmallClock_events_onFontSettingChanged() {
         val clock = provider.createClock(DEFAULT_CLOCK_ID)
-        clock.events.onFontSettingChanged()
+        clock.smallClock.events.onFontSettingChanged(100f)
 
-        verify(mockSmallClockView).setTextSize(eq(TypedValue.COMPLEX_UNIT_PX), anyFloat())
-        verify(mockLargeClockView).setTextSize(eq(TypedValue.COMPLEX_UNIT_PX), anyFloat())
+        verify(mockSmallClockView).setTextSize(eq(TypedValue.COMPLEX_UNIT_PX), eq(100f))
+    }
+
+    @Test
+    fun defaultLargeClock_events_onFontSettingChanged() {
+        val clock = provider.createClock(DEFAULT_CLOCK_ID)
+        clock.largeClock.events.onFontSettingChanged(200f)
+
+        verify(mockLargeClockView).setTextSize(eq(TypedValue.COMPLEX_UNIT_PX), eq(200f))
         verify(mockLargeClockView).setLayoutParams(any())
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java
index e8a7ec8..32a281e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java
@@ -38,6 +38,7 @@
 import static com.android.systemui.keyguard.KeyguardIndicationRotateTextViewController.INDICATION_TYPE_USER_LOCKED;
 import static com.android.systemui.keyguard.ScreenLifecycle.SCREEN_OFF;
 import static com.android.systemui.keyguard.ScreenLifecycle.SCREEN_ON;
+import static com.android.systemui.keyguard.ScreenLifecycle.SCREEN_TURNING_ON;
 
 import static com.google.common.truth.Truth.assertThat;
 
@@ -87,6 +88,7 @@
 import com.android.internal.widget.LockPatternUtils;
 import com.android.keyguard.KeyguardUpdateMonitor;
 import com.android.keyguard.KeyguardUpdateMonitorCallback;
+import com.android.keyguard.TrustGrantFlags;
 import com.android.keyguard.logging.KeyguardLogger;
 import com.android.settingslib.fuelgauge.BatteryStatus;
 import com.android.systemui.R;
@@ -1068,7 +1070,8 @@
 
         // GIVEN a trust granted message but trust isn't granted
         final String trustGrantedMsg = "testing trust granted message";
-        mController.getKeyguardCallback().onTrustGrantedWithFlags(0, 0, trustGrantedMsg);
+        mController.getKeyguardCallback().onTrustGrantedForCurrentUser(
+                false, new TrustGrantFlags(0), trustGrantedMsg);
 
         verifyHideIndication(INDICATION_TYPE_TRUST);
 
@@ -1092,7 +1095,8 @@
 
         // WHEN the showTrustGranted method is called
         final String trustGrantedMsg = "testing trust granted message";
-        mController.getKeyguardCallback().onTrustGrantedWithFlags(0, 0, trustGrantedMsg);
+        mController.getKeyguardCallback().onTrustGrantedForCurrentUser(
+                false, new TrustGrantFlags(0), trustGrantedMsg);
 
         // THEN verify the trust granted message shows
         verifyIndicationMessage(
@@ -1109,7 +1113,8 @@
         when(mKeyguardUpdateMonitor.getUserHasTrust(anyInt())).thenReturn(true);
 
         // WHEN the showTrustGranted method is called with a null message
-        mController.getKeyguardCallback().onTrustGrantedWithFlags(0, 0, null);
+        mController.getKeyguardCallback().onTrustGrantedForCurrentUser(
+                false, new TrustGrantFlags(0), null);
 
         // THEN verify the default trust granted message shows
         verifyIndicationMessage(
@@ -1126,7 +1131,8 @@
         when(mKeyguardUpdateMonitor.getUserHasTrust(anyInt())).thenReturn(true);
 
         // WHEN the showTrustGranted method is called with an EMPTY string
-        mController.getKeyguardCallback().onTrustGrantedWithFlags(0, 0, "");
+        mController.getKeyguardCallback().onTrustGrantedForCurrentUser(
+                false, new TrustGrantFlags(0), "");
 
         // THEN verify NO trust message is shown
         verifyNoMessage(INDICATION_TYPE_TRUST);
@@ -1501,6 +1507,44 @@
                 mContext.getString(R.string.keyguard_face_unlock_unavailable));
     }
 
+    @Test
+    public void onBiometricError_screenIsTurningOn_faceLockedOutFpIsNotAvailable_showsMessage() {
+        createController();
+        screenIsTurningOn();
+        fingerprintUnlockIsNotPossible();
+
+        onFaceLockoutError("lockout error");
+        verifyNoMoreInteractions(mRotateTextViewController);
+
+        mScreenObserver.onScreenTurnedOn();
+
+        verifyIndicationShown(INDICATION_TYPE_BIOMETRIC_MESSAGE,
+                "lockout error");
+        verifyIndicationShown(INDICATION_TYPE_BIOMETRIC_MESSAGE_FOLLOW_UP,
+                mContext.getString(R.string.keyguard_unlock));
+    }
+
+    @Test
+    public void onBiometricError_screenIsTurningOn_faceLockedOutFpIsAvailable_showsMessage() {
+        createController();
+        screenIsTurningOn();
+        fingerprintUnlockIsPossible();
+
+        onFaceLockoutError("lockout error");
+        verifyNoMoreInteractions(mRotateTextViewController);
+
+        mScreenObserver.onScreenTurnedOn();
+
+        verifyIndicationShown(INDICATION_TYPE_BIOMETRIC_MESSAGE,
+                "lockout error");
+        verifyIndicationShown(INDICATION_TYPE_BIOMETRIC_MESSAGE_FOLLOW_UP,
+                mContext.getString(R.string.keyguard_suggest_fingerprint));
+    }
+
+    private void screenIsTurningOn() {
+        when(mScreenLifecycle.getScreenState()).thenReturn(SCREEN_TURNING_ON);
+    }
+
     private void sendUpdateDisclosureBroadcast() {
         mBroadcastReceiver.onReceive(mContext, new Intent());
     }
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 bdafa48..15a687d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerTest.java
@@ -53,6 +53,7 @@
 import com.android.systemui.dump.DumpManager;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.recents.OverviewProxyService;
+import com.android.systemui.settings.UserTracker;
 import com.android.systemui.statusbar.NotificationLockscreenUserManager.NotificationStateChangedListener;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder;
@@ -78,6 +79,8 @@
     private NotificationPresenter mPresenter;
     @Mock
     private UserManager mUserManager;
+    @Mock
+    private UserTracker mUserTracker;
 
     // Dependency mocks:
     @Mock
@@ -115,6 +118,7 @@
         MockitoAnnotations.initMocks(this);
 
         int currentUserId = ActivityManager.getCurrentUser();
+        when(mUserTracker.getUserId()).thenReturn(currentUserId);
         mSettings = new FakeSettings();
         mSettings.setUserId(ActivityManager.getCurrentUser());
         mCurrentUser = new UserInfo(currentUserId, "", 0);
@@ -344,6 +348,7 @@
                     mBroadcastDispatcher,
                     mDevicePolicyManager,
                     mUserManager,
+                    mUserTracker,
                     (() -> mVisibilityProvider),
                     (() -> mNotifCollection),
                     mClickNotifier,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerBaseTest.java
index 9c870b5..faf4592 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerBaseTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerBaseTest.java
@@ -71,6 +71,7 @@
 import com.android.systemui.demomode.DemoModeController;
 import com.android.systemui.dump.DumpManager;
 import com.android.systemui.plugins.log.LogBuffer;
+import com.android.systemui.settings.UserTracker;
 import com.android.systemui.statusbar.pipeline.mobile.util.FakeMobileMappingsProxy;
 import com.android.systemui.statusbar.policy.DeviceProvisionedController;
 import com.android.systemui.statusbar.policy.DeviceProvisionedController.DeviceProvisionedListener;
@@ -116,6 +117,7 @@
     protected TelephonyManager mMockTm;
     protected TelephonyListenerManager mTelephonyListenerManager;
     protected BroadcastDispatcher mMockBd;
+    protected UserTracker mUserTracker;
     protected Config mConfig;
     protected CallbackHandler mCallbackHandler;
     protected SubscriptionDefaults mMockSubDefaults;
@@ -172,6 +174,7 @@
         mMockSm = mock(SubscriptionManager.class);
         mMockCm = mock(ConnectivityManager.class);
         mMockBd = mock(BroadcastDispatcher.class);
+        mUserTracker = mock(UserTracker.class);
         mMockNsm = mock(NetworkScoreManager.class);
         mMockSubDefaults = mock(SubscriptionDefaults.class);
         mCarrierConfigTracker = mock(CarrierConfigTracker.class);
@@ -246,6 +249,7 @@
                 mMockSubDefaults,
                 mMockProvisionController,
                 mMockBd,
+                mUserTracker,
                 mDemoModeController,
                 mCarrierConfigTracker,
                 mWifiStatusTrackerFactory,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerDataTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerDataTest.java
index 1d11226..ca75a40 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerDataTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerDataTest.java
@@ -154,6 +154,7 @@
                 mMockSubDefaults,
                 mock(DeviceProvisionedController.class),
                 mMockBd,
+                mUserTracker,
                 mDemoModeController,
                 mock(CarrierConfigTracker.class),
                 mWifiStatusTrackerFactory,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerSignalTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerSignalTest.java
index d5f5105..84c242c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerSignalTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerSignalTest.java
@@ -82,6 +82,7 @@
                 mMockSubDefaults,
                 mMockProvisionController,
                 mMockBd,
+                mUserTracker,
                 mDemoModeController,
                 mCarrierConfigTracker,
                 mWifiStatusTrackerFactory,
@@ -118,6 +119,7 @@
                 mMockSubDefaults,
                 mMockProvisionController,
                 mMockBd,
+                mUserTracker,
                 mDemoModeController,
                 mCarrierConfigTracker,
                 mWifiStatusTrackerFactory,
@@ -152,6 +154,7 @@
                 mMockSubDefaults,
                 mock(DeviceProvisionedController.class),
                 mMockBd,
+                mUserTracker,
                 mDemoModeController,
                 mock(CarrierConfigTracker.class),
                 mWifiStatusTrackerFactory,
@@ -189,6 +192,7 @@
                 mMockSubDefaults,
                 mock(DeviceProvisionedController.class),
                 mMockBd,
+                mUserTracker,
                 mDemoModeController,
                 mock(CarrierConfigTracker.class),
                 mWifiStatusTrackerFactory,
@@ -274,6 +278,7 @@
                 mMockSubDefaults,
                 mock(DeviceProvisionedController.class),
                 mMockBd,
+                mUserTracker,
                 mDemoModeController,
                 mock(CarrierConfigTracker.class),
                 mWifiStatusTrackerFactory,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinatorTest.kt
index 7e2e6f6..bdedd24 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinatorTest.kt
@@ -13,57 +13,55 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
+@file:OptIn(ExperimentalCoroutinesApi::class)
+
 package com.android.systemui.statusbar.notification.collection.coordinator
 
 import android.testing.AndroidTestingRunner
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
+import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
 import com.android.systemui.plugins.statusbar.StatusBarStateController
 import com.android.systemui.statusbar.StatusBarState
+import com.android.systemui.statusbar.notification.NotifPipelineFlags
+import com.android.systemui.statusbar.notification.collection.GroupEntryBuilder
 import com.android.systemui.statusbar.notification.collection.NotifPipeline
+import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder
 import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifFilter
+import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener
 import com.android.systemui.statusbar.notification.collection.provider.SectionHeaderVisibilityProvider
 import com.android.systemui.statusbar.notification.interruption.KeyguardNotificationVisibilityProvider
 import com.android.systemui.util.mockito.eq
 import com.android.systemui.util.mockito.mock
 import com.android.systemui.util.mockito.withArgCaptor
-import java.util.function.Consumer
-import org.junit.Before
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.TestCoroutineScheduler
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.UnconfinedTestDispatcher
+import kotlinx.coroutines.test.runTest
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.mockito.Mockito.clearInvocations
 import org.mockito.Mockito.verify
+import java.util.function.Consumer
+import kotlin.time.Duration.Companion.seconds
 import org.mockito.Mockito.`when` as whenever
 
 @SmallTest
 @RunWith(AndroidTestingRunner::class)
 class KeyguardCoordinatorTest : SysuiTestCase() {
-    private val notifPipeline: NotifPipeline = mock()
+
     private val keyguardNotifVisibilityProvider: KeyguardNotificationVisibilityProvider = mock()
+    private val keyguardRepository = FakeKeyguardRepository()
+    private val notifPipelineFlags: NotifPipelineFlags = mock()
+    private val notifPipeline: NotifPipeline = mock()
     private val sectionHeaderVisibilityProvider: SectionHeaderVisibilityProvider = mock()
     private val statusBarStateController: StatusBarStateController = mock()
 
-    private lateinit var onStateChangeListener: Consumer<String>
-    private lateinit var keyguardFilter: NotifFilter
-
-    @Before
-    fun setup() {
-        val keyguardCoordinator = KeyguardCoordinator(
-            keyguardNotifVisibilityProvider,
-            sectionHeaderVisibilityProvider,
-            statusBarStateController
-        )
-        keyguardCoordinator.attach(notifPipeline)
-        onStateChangeListener = withArgCaptor {
-            verify(keyguardNotifVisibilityProvider).addOnStateChangedListener(capture())
-        }
-        keyguardFilter = withArgCaptor {
-            verify(notifPipeline).addFinalizeFilter(capture())
-        }
-    }
-
     @Test
-    fun testSetSectionHeadersVisibleInShade() {
+    fun testSetSectionHeadersVisibleInShade() = runKeyguardCoordinatorTest {
         clearInvocations(sectionHeaderVisibilityProvider)
         whenever(statusBarStateController.state).thenReturn(StatusBarState.SHADE)
         onStateChangeListener.accept("state change")
@@ -71,10 +69,176 @@
     }
 
     @Test
-    fun testSetSectionHeadersNotVisibleOnKeyguard() {
+    fun testSetSectionHeadersNotVisibleOnKeyguard() = runKeyguardCoordinatorTest {
         clearInvocations(sectionHeaderVisibilityProvider)
         whenever(statusBarStateController.state).thenReturn(StatusBarState.KEYGUARD)
         onStateChangeListener.accept("state change")
         verify(sectionHeaderVisibilityProvider).sectionHeadersVisible = eq(false)
     }
+
+    @Test
+    fun unseenFilterSuppressesSeenNotifWhileKeyguardShowing() {
+        whenever(notifPipelineFlags.shouldFilterUnseenNotifsOnKeyguard).thenReturn(true)
+
+        // GIVEN: Keyguard is not showing, and a notification is present
+        keyguardRepository.setKeyguardShowing(false)
+        runKeyguardCoordinatorTest {
+            val fakeEntry = NotificationEntryBuilder().build()
+            collectionListener.onEntryAdded(fakeEntry)
+
+            // WHEN: The keyguard is now showing
+            keyguardRepository.setKeyguardShowing(true)
+            testScheduler.runCurrent()
+
+            // THEN: The notification is recognized as "seen" and is filtered out.
+            assertThat(unseenFilter.shouldFilterOut(fakeEntry, 0L)).isTrue()
+
+            // WHEN: The keyguard goes away
+            keyguardRepository.setKeyguardShowing(false)
+            testScheduler.runCurrent()
+
+            // THEN: The notification is shown regardless
+            assertThat(unseenFilter.shouldFilterOut(fakeEntry, 0L)).isFalse()
+        }
+    }
+
+    @Test
+    fun unseenFilterAllowsNewNotif() {
+        whenever(notifPipelineFlags.shouldFilterUnseenNotifsOnKeyguard).thenReturn(true)
+
+        // GIVEN: Keyguard is showing, no notifications present
+        keyguardRepository.setKeyguardShowing(true)
+        runKeyguardCoordinatorTest {
+            // WHEN: A new notification is posted
+            val fakeEntry = NotificationEntryBuilder().build()
+            collectionListener.onEntryAdded(fakeEntry)
+
+            // THEN: The notification is recognized as "unseen" and is not filtered out.
+            assertThat(unseenFilter.shouldFilterOut(fakeEntry, 0L)).isFalse()
+        }
+    }
+
+    @Test
+    fun unseenFilterSeenGroupSummaryWithUnseenChild() {
+        whenever(notifPipelineFlags.shouldFilterUnseenNotifsOnKeyguard).thenReturn(true)
+
+        // GIVEN: Keyguard is not showing, and a notification is present
+        keyguardRepository.setKeyguardShowing(false)
+        runKeyguardCoordinatorTest {
+            // WHEN: A new notification is posted
+            val fakeSummary = NotificationEntryBuilder().build()
+            val fakeChild = NotificationEntryBuilder()
+                    .setGroup(context, "group")
+                    .setGroupSummary(context, false)
+                    .build()
+            GroupEntryBuilder()
+                    .setSummary(fakeSummary)
+                    .addChild(fakeChild)
+                    .build()
+
+            collectionListener.onEntryAdded(fakeSummary)
+            collectionListener.onEntryAdded(fakeChild)
+
+            // WHEN: Keyguard is now showing, both notifications are marked as seen
+            keyguardRepository.setKeyguardShowing(true)
+            testScheduler.runCurrent()
+
+            // WHEN: The child notification is now unseen
+            collectionListener.onEntryUpdated(fakeChild)
+
+            // THEN: The summary is not filtered out, because the child is unseen
+            assertThat(unseenFilter.shouldFilterOut(fakeSummary, 0L)).isFalse()
+        }
+    }
+
+    @Test
+    fun unseenNotificationIsMarkedAsSeenWhenKeyguardGoesAway() {
+        whenever(notifPipelineFlags.shouldFilterUnseenNotifsOnKeyguard).thenReturn(true)
+
+        // GIVEN: Keyguard is showing, unseen notification is present
+        keyguardRepository.setKeyguardShowing(true)
+        runKeyguardCoordinatorTest {
+            val fakeEntry = NotificationEntryBuilder().build()
+            collectionListener.onEntryAdded(fakeEntry)
+
+            // WHEN: Keyguard is no longer showing for 5 seconds
+            keyguardRepository.setKeyguardShowing(false)
+            testScheduler.runCurrent()
+            testScheduler.advanceTimeBy(5.seconds.inWholeMilliseconds)
+            testScheduler.runCurrent()
+
+            // WHEN: Keyguard is shown again
+            keyguardRepository.setKeyguardShowing(true)
+            testScheduler.runCurrent()
+
+            // THEN: The notification is now recognized as "seen" and is filtered out.
+            assertThat(unseenFilter.shouldFilterOut(fakeEntry, 0L)).isTrue()
+        }
+    }
+
+    @Test
+    fun unseenNotificationIsNotMarkedAsSeenIfTimeThresholdNotMet() {
+        whenever(notifPipelineFlags.shouldFilterUnseenNotifsOnKeyguard).thenReturn(true)
+
+        // GIVEN: Keyguard is showing, unseen notification is present
+        keyguardRepository.setKeyguardShowing(true)
+        runKeyguardCoordinatorTest {
+            val fakeEntry = NotificationEntryBuilder().build()
+            collectionListener.onEntryAdded(fakeEntry)
+
+            // WHEN: Keyguard is no longer showing for <5 seconds
+            keyguardRepository.setKeyguardShowing(false)
+            testScheduler.runCurrent()
+            testScheduler.advanceTimeBy(1.seconds.inWholeMilliseconds)
+
+            // WHEN: Keyguard is shown again
+            keyguardRepository.setKeyguardShowing(true)
+            testScheduler.runCurrent()
+
+            // THEN: The notification is not recognized as "seen" and is not filtered out.
+            assertThat(unseenFilter.shouldFilterOut(fakeEntry, 0L)).isFalse()
+        }
+    }
+
+    private fun runKeyguardCoordinatorTest(
+        testBlock: suspend KeyguardCoordinatorTestScope.() -> Unit
+    ) {
+        val testScope = TestScope(UnconfinedTestDispatcher())
+        val keyguardCoordinator =
+            KeyguardCoordinator(
+                keyguardNotifVisibilityProvider,
+                keyguardRepository,
+                notifPipelineFlags,
+                testScope.backgroundScope,
+                sectionHeaderVisibilityProvider,
+                statusBarStateController,
+            )
+        keyguardCoordinator.attach(notifPipeline)
+        KeyguardCoordinatorTestScope(keyguardCoordinator, testScope).run {
+            testScheduler.advanceUntilIdle()
+            testScope.runTest(dispatchTimeoutMs = 1.seconds.inWholeMilliseconds) { testBlock() }
+        }
+    }
+
+    private inner class KeyguardCoordinatorTestScope(
+        private val keyguardCoordinator: KeyguardCoordinator,
+        private val scope: TestScope,
+    ) : CoroutineScope by scope {
+        val testScheduler: TestCoroutineScheduler
+            get() = scope.testScheduler
+
+        val onStateChangeListener: Consumer<String> =
+            withArgCaptor {
+                verify(keyguardNotifVisibilityProvider).addOnStateChangedListener(capture())
+            }
+
+        val unseenFilter: NotifFilter
+            get() = keyguardCoordinator.unseenNotifFilter
+
+        // TODO(254647461): Remove lazy once Flags.FILTER_UNSEEN_NOTIFS_ON_KEYGUARD is enabled and
+        //  removed
+        val collectionListener: NotifCollectionListener by lazy {
+            withArgCaptor { verify(notifPipeline).addCollectionListener(capture()) }
+        }
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinatorTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinatorTest.java
index b4a5f5c..e488f39 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinatorTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinatorTest.java
@@ -16,6 +16,8 @@
 
 package com.android.systemui.statusbar.notification.collection.coordinator;
 
+import static com.google.common.truth.Truth.assertThat;
+
 import static junit.framework.Assert.assertFalse;
 
 import static org.junit.Assert.assertTrue;
@@ -38,6 +40,7 @@
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.shade.ShadeStateEvents;
 import com.android.systemui.shade.ShadeStateEvents.ShadeStateEventsListener;
+import com.android.systemui.statusbar.notification.VisibilityLocationProvider;
 import com.android.systemui.statusbar.notification.collection.GroupEntry;
 import com.android.systemui.statusbar.notification.collection.GroupEntryBuilder;
 import com.android.systemui.statusbar.notification.collection.NotifPipeline;
@@ -73,6 +76,7 @@
     @Mock private Pluggable.PluggableListener<NotifStabilityManager> mInvalidateListener;
     @Mock private HeadsUpManager mHeadsUpManager;
     @Mock private ShadeStateEvents mShadeStateEvents;
+    @Mock private VisibilityLocationProvider mVisibilityLocationProvider;
     @Mock private VisualStabilityProvider mVisualStabilityProvider;
 
     @Captor private ArgumentCaptor<WakefulnessLifecycle.Observer> mWakefulnessObserverCaptor;
@@ -100,6 +104,7 @@
                 mHeadsUpManager,
                 mShadeStateEvents,
                 mStatusBarStateController,
+                mVisibilityLocationProvider,
                 mVisualStabilityProvider,
                 mWakefulnessLifecycle);
 
@@ -355,6 +360,38 @@
     }
 
     @Test
+    public void testMovingVisibleHeadsUpNotAllowed() {
+        // GIVEN stability enforcing conditions
+        setPanelExpanded(true);
+        setSleepy(false);
+
+        // WHEN a notification is alerting and visible
+        when(mHeadsUpManager.isAlerting(mEntry.getKey())).thenReturn(true);
+        when(mVisibilityLocationProvider.isInVisibleLocation(any(NotificationEntry.class)))
+                .thenReturn(true);
+
+        // VERIFY the notification cannot be reordered
+        assertThat(mNotifStabilityManager.isEntryReorderingAllowed(mEntry)).isFalse();
+        assertThat(mNotifStabilityManager.isSectionChangeAllowed(mEntry)).isFalse();
+    }
+
+    @Test
+    public void testMovingInvisibleHeadsUpAllowed() {
+        // GIVEN stability enforcing conditions
+        setPanelExpanded(true);
+        setSleepy(false);
+
+        // WHEN a notification is alerting but not visible
+        when(mHeadsUpManager.isAlerting(mEntry.getKey())).thenReturn(true);
+        when(mVisibilityLocationProvider.isInVisibleLocation(any(NotificationEntry.class)))
+                .thenReturn(false);
+
+        // VERIFY the notification can be reordered
+        assertThat(mNotifStabilityManager.isEntryReorderingAllowed(mEntry)).isTrue();
+        assertThat(mNotifStabilityManager.isSectionChangeAllowed(mEntry)).isTrue();
+    }
+
+    @Test
     public void testNeverSuppressedChanges_noInvalidationCalled() {
         // GIVEN no notifications are currently being suppressed from grouping nor being sorted
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java
index 90061b0..026c82e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java
@@ -61,6 +61,7 @@
 import com.android.systemui.statusbar.notification.DynamicPrivacyController;
 import com.android.systemui.statusbar.notification.collection.NotifCollection;
 import com.android.systemui.statusbar.notification.collection.NotifPipeline;
+import com.android.systemui.statusbar.notification.collection.provider.VisibilityLocationProviderDelegator;
 import com.android.systemui.statusbar.notification.collection.render.GroupExpansionManager;
 import com.android.systemui.statusbar.notification.collection.render.NotificationVisibilityProvider;
 import com.android.systemui.statusbar.notification.collection.render.SectionHeaderController;
@@ -122,6 +123,7 @@
     @Mock private UiEventLogger mUiEventLogger;
     @Mock private LockscreenShadeTransitionController mLockscreenShadeTransitionController;
     @Mock private NotificationRemoteInputManager mRemoteInputManager;
+    @Mock private VisibilityLocationProviderDelegator mVisibilityLocationProviderDelegator;
     @Mock private ShadeController mShadeController;
     @Mock private InteractionJankMonitor mJankMonitor;
     @Mock private StackStateLogger mStackLogger;
@@ -173,6 +175,7 @@
                 mShadeTransitionController,
                 mUiEventLogger,
                 mRemoteInputManager,
+                mVisibilityLocationProviderDelegator,
                 mShadeController,
                 mJankMonitor,
                 mStackLogger,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ConfigurationControllerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ConfigurationControllerImplTest.kt
index fee3ccb..038af8f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ConfigurationControllerImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ConfigurationControllerImplTest.kt
@@ -14,23 +14,37 @@
 
 package com.android.systemui.statusbar.phone
 
-import androidx.test.filters.SmallTest
+import android.content.res.Configuration
+import android.content.res.Configuration.SCREENLAYOUT_LAYOUTDIR_LTR
+import android.content.res.Configuration.SCREENLAYOUT_LAYOUTDIR_RTL
+import android.content.res.Configuration.UI_MODE_NIGHT_NO
+import android.content.res.Configuration.UI_MODE_NIGHT_YES
+import android.content.res.Configuration.UI_MODE_TYPE_CAR
+import android.os.LocaleList
 import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.statusbar.policy.ConfigurationController.ConfigurationListener
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.mockito.Mockito.doAnswer
 import org.mockito.Mockito.mock
 import org.mockito.Mockito.never
 import org.mockito.Mockito.verify
+import java.util.Locale
 
 @RunWith(AndroidTestingRunner::class)
 @SmallTest
 class ConfigurationControllerImplTest : SysuiTestCase() {
 
-    private val mConfigurationController =
-            com.android.systemui.statusbar.phone.ConfigurationControllerImpl(mContext)
+    private lateinit var mConfigurationController: ConfigurationControllerImpl
+
+    @Before
+    fun setUp() {
+        mConfigurationController = ConfigurationControllerImpl(mContext)
+    }
 
     @Test
     fun testThemeChange() {
@@ -57,4 +71,303 @@
         verify(listener).onThemeChanged()
         verify(listener2, never()).onThemeChanged()
     }
+
+    @Test
+    fun configChanged_listenerNotified() {
+        val config = mContext.resources.configuration
+        config.densityDpi = 12
+        config.smallestScreenWidthDp = 240
+        mConfigurationController.onConfigurationChanged(config)
+
+        val listener = createAndAddListener()
+
+        // WHEN the config is updated
+        config.densityDpi = 20
+        config.smallestScreenWidthDp = 300
+        mConfigurationController.onConfigurationChanged(config)
+
+        // THEN the listener is notified
+        assertThat(listener.changedConfig?.densityDpi).isEqualTo(20)
+        assertThat(listener.changedConfig?.smallestScreenWidthDp).isEqualTo(300)
+    }
+
+    @Test
+    fun densityChanged_listenerNotified() {
+        val config = mContext.resources.configuration
+        config.densityDpi = 12
+        mConfigurationController.onConfigurationChanged(config)
+
+        val listener = createAndAddListener()
+
+        // WHEN the density is updated
+        config.densityDpi = 20
+        mConfigurationController.onConfigurationChanged(config)
+
+        // THEN the listener is notified
+        assertThat(listener.densityOrFontScaleChanged).isTrue()
+    }
+
+    @Test
+    fun fontChanged_listenerNotified() {
+        val config = mContext.resources.configuration
+        config.fontScale = 1.5f
+        mConfigurationController.onConfigurationChanged(config)
+
+        val listener = createAndAddListener()
+
+        // WHEN the font is updated
+        config.fontScale = 1.4f
+        mConfigurationController.onConfigurationChanged(config)
+
+        // THEN the listener is notified
+        assertThat(listener.densityOrFontScaleChanged).isTrue()
+    }
+
+    @Test
+    fun isCarAndUiModeChanged_densityListenerNotified() {
+        val config = mContext.resources.configuration
+        config.uiMode = UI_MODE_TYPE_CAR or UI_MODE_NIGHT_YES
+        // Re-create the controller since we calculate car mode on creation
+        mConfigurationController = ConfigurationControllerImpl(mContext)
+
+        val listener = createAndAddListener()
+
+        // WHEN the ui mode is updated
+        config.uiMode = UI_MODE_TYPE_CAR or UI_MODE_NIGHT_NO
+        mConfigurationController.onConfigurationChanged(config)
+
+        // THEN the listener is notified
+        assertThat(listener.densityOrFontScaleChanged).isTrue()
+    }
+
+    @Test
+    fun isNotCarAndUiModeChanged_densityListenerNotNotified() {
+        val config = mContext.resources.configuration
+        config.uiMode = UI_MODE_NIGHT_YES
+        // Re-create the controller since we calculate car mode on creation
+        mConfigurationController = ConfigurationControllerImpl(mContext)
+
+        val listener = createAndAddListener()
+
+        // WHEN the ui mode is updated
+        config.uiMode = UI_MODE_NIGHT_NO
+        mConfigurationController.onConfigurationChanged(config)
+
+        // THEN the listener is not notified because it's not car mode
+        assertThat(listener.densityOrFontScaleChanged).isFalse()
+    }
+
+    @Test
+    fun smallestScreenWidthChanged_listenerNotified() {
+        val config = mContext.resources.configuration
+        config.smallestScreenWidthDp = 240
+        mConfigurationController.onConfigurationChanged(config)
+
+        val listener = createAndAddListener()
+
+        // WHEN the width is updated
+        config.smallestScreenWidthDp = 300
+        mConfigurationController.onConfigurationChanged(config)
+
+        // THEN the listener is notified
+        assertThat(listener.smallestScreenWidthChanged).isTrue()
+    }
+
+    @Test
+    fun maxBoundsChange_newConfigObject_listenerNotified() {
+        val config = mContext.resources.configuration
+        config.windowConfiguration.setMaxBounds(0, 0, 200, 200)
+        mConfigurationController.onConfigurationChanged(config)
+
+        val listener = createAndAddListener()
+
+        // WHEN a new configuration object with new bounds is sent
+        val newConfig = Configuration()
+        newConfig.windowConfiguration.setMaxBounds(0, 0, 100, 100)
+        mConfigurationController.onConfigurationChanged(newConfig)
+
+        // THEN the listener is notified
+        assertThat(listener.maxBoundsChanged).isTrue()
+    }
+
+    // Regression test for b/245799099
+    @Test
+    fun maxBoundsChange_sameObject_listenerNotified() {
+        val config = mContext.resources.configuration
+        config.windowConfiguration.setMaxBounds(0, 0, 200, 200)
+        mConfigurationController.onConfigurationChanged(config)
+
+        val listener = createAndAddListener()
+
+        // WHEN the existing config is updated with new bounds
+        config.windowConfiguration.setMaxBounds(0, 0, 100, 100)
+        mConfigurationController.onConfigurationChanged(config)
+
+        // THEN the listener is notified
+        assertThat(listener.maxBoundsChanged).isTrue()
+    }
+
+
+    @Test
+    fun localeListChanged_listenerNotified() {
+        val config = mContext.resources.configuration
+        config.locales = LocaleList(Locale.CANADA, Locale.GERMANY)
+        mConfigurationController.onConfigurationChanged(config)
+
+        val listener = createAndAddListener()
+
+        // WHEN the locales are updated
+        config.locales = LocaleList(Locale.FRANCE, Locale.JAPAN, Locale.CHINESE)
+        mConfigurationController.onConfigurationChanged(config)
+
+        // THEN the listener is notified
+        assertThat(listener.localeListChanged).isTrue()
+    }
+
+    @Test
+    fun uiModeChanged_listenerNotified() {
+        val config = mContext.resources.configuration
+        config.uiMode = UI_MODE_NIGHT_YES
+        mConfigurationController.onConfigurationChanged(config)
+
+        val listener = createAndAddListener()
+
+        // WHEN the ui mode is updated
+        config.uiMode = UI_MODE_NIGHT_NO
+        mConfigurationController.onConfigurationChanged(config)
+
+        // THEN the listener is notified
+        assertThat(listener.uiModeChanged).isTrue()
+    }
+
+    @Test
+    fun layoutDirectionUpdated_listenerNotified() {
+        val config = mContext.resources.configuration
+        config.screenLayout = SCREENLAYOUT_LAYOUTDIR_LTR
+        mConfigurationController.onConfigurationChanged(config)
+
+        val listener = createAndAddListener()
+
+        // WHEN the layout is updated
+        config.screenLayout = SCREENLAYOUT_LAYOUTDIR_RTL
+        mConfigurationController.onConfigurationChanged(config)
+
+        // THEN the listener is notified
+        assertThat(listener.layoutDirectionChanged).isTrue()
+    }
+
+    @Test
+    fun assetPathsUpdated_listenerNotified() {
+        val config = mContext.resources.configuration
+        config.assetsSeq = 45
+        mConfigurationController.onConfigurationChanged(config)
+
+        val listener = createAndAddListener()
+
+        // WHEN the assets sequence is updated
+        config.assetsSeq = 46
+        mConfigurationController.onConfigurationChanged(config)
+
+        // THEN the listener is notified
+        assertThat(listener.themeChanged).isTrue()
+    }
+
+    @Test
+    fun multipleUpdates_listenerNotifiedOfAll() {
+        val config = mContext.resources.configuration
+        config.densityDpi = 14
+        config.windowConfiguration.setMaxBounds(0, 0, 2, 2)
+        config.uiMode = UI_MODE_NIGHT_YES
+        mConfigurationController.onConfigurationChanged(config)
+
+        val listener = createAndAddListener()
+
+        // WHEN multiple fields are updated
+        config.densityDpi = 20
+        config.windowConfiguration.setMaxBounds(0, 0, 3, 3)
+        config.uiMode = UI_MODE_NIGHT_NO
+        mConfigurationController.onConfigurationChanged(config)
+
+        // THEN the listener is notified of all of them
+        assertThat(listener.densityOrFontScaleChanged).isTrue()
+        assertThat(listener.maxBoundsChanged).isTrue()
+        assertThat(listener.uiModeChanged).isTrue()
+    }
+
+    @Test
+    fun equivalentConfigObject_listenerNotNotified() {
+        val config = mContext.resources.configuration
+        val listener = createAndAddListener()
+
+        // WHEN we update with the new object that has all the same fields
+        mConfigurationController.onConfigurationChanged(Configuration(config))
+
+        listener.assertNoMethodsCalled()
+    }
+
+    private fun createAndAddListener(): TestListener {
+        val listener = TestListener()
+        mConfigurationController.addCallback(listener)
+        // Adding a listener can trigger some callbacks, so we want to reset the values right
+        // after the listener is added
+        listener.reset()
+        return listener
+    }
+
+    private class TestListener : ConfigurationListener {
+        var changedConfig: Configuration? = null
+        var densityOrFontScaleChanged = false
+        var smallestScreenWidthChanged = false
+        var maxBoundsChanged = false
+        var uiModeChanged = false
+        var themeChanged = false
+        var localeListChanged = false
+        var layoutDirectionChanged = false
+
+        override fun onConfigChanged(newConfig: Configuration?) {
+            changedConfig = newConfig
+        }
+        override fun onDensityOrFontScaleChanged() {
+            densityOrFontScaleChanged = true
+        }
+        override fun onSmallestScreenWidthChanged() {
+            smallestScreenWidthChanged = true
+        }
+        override fun onMaxBoundsChanged() {
+            maxBoundsChanged = true
+        }
+        override fun onUiModeChanged() {
+            uiModeChanged = true
+        }
+        override fun onThemeChanged() {
+            themeChanged = true
+        }
+        override fun onLocaleListChanged() {
+            localeListChanged = true
+        }
+        override fun onLayoutDirectionChanged(isLayoutRtl: Boolean) {
+            layoutDirectionChanged = true
+        }
+
+        fun assertNoMethodsCalled() {
+            assertThat(densityOrFontScaleChanged).isFalse()
+            assertThat(smallestScreenWidthChanged).isFalse()
+            assertThat(maxBoundsChanged).isFalse()
+            assertThat(uiModeChanged).isFalse()
+            assertThat(themeChanged).isFalse()
+            assertThat(localeListChanged).isFalse()
+            assertThat(layoutDirectionChanged).isFalse()
+        }
+
+        fun reset() {
+            changedConfig = null
+            densityOrFontScaleChanged = false
+            smallestScreenWidthChanged = false
+            maxBoundsChanged = false
+            uiModeChanged = false
+            themeChanged = false
+            localeListChanged = false
+            layoutDirectionChanged = false
+        }
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarContentInsetsProviderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarContentInsetsProviderTest.kt
index e86676b..1759fb7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarContentInsetsProviderTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarContentInsetsProviderTest.kt
@@ -19,9 +19,9 @@
 import android.content.Context
 import android.content.res.Configuration
 import android.graphics.Rect
-import android.test.suitebuilder.annotation.SmallTest
 import android.view.Display
 import android.view.DisplayCutout
+import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.dump.DumpManager
 import com.android.systemui.statusbar.policy.ConfigurationController
@@ -463,16 +463,10 @@
         val provider = StatusBarContentInsetsProvider(contextMock, configurationController,
             mock(DumpManager::class.java))
 
-        givenDisplay(
-            screenBounds = Rect(0, 0, 1080, 2160),
-            displayUniqueId = "1"
-        )
+        configuration.windowConfiguration.maxBounds = Rect(0, 0, 1080, 2160)
         val firstDisplayInsets = provider.getStatusBarContentAreaForRotation(ROTATION_NONE)
-        givenDisplay(
-            screenBounds = Rect(0, 0, 800, 600),
-            displayUniqueId = "2"
-        )
-        configurationController.onConfigurationChanged(configuration)
+
+        configuration.windowConfiguration.maxBounds = Rect(0, 0, 800, 600)
 
         // WHEN: get insets on the second display
         val secondDisplayInsets = provider.getStatusBarContentAreaForRotation(ROTATION_NONE)
@@ -487,23 +481,15 @@
         // get insets and switch back
         val provider = StatusBarContentInsetsProvider(contextMock, configurationController,
             mock(DumpManager::class.java))
-        givenDisplay(
-            screenBounds = Rect(0, 0, 1080, 2160),
-            displayUniqueId = "1"
-        )
+
+        configuration.windowConfiguration.maxBounds = Rect(0, 0, 1080, 2160)
         val firstDisplayInsetsFirstCall = provider
             .getStatusBarContentAreaForRotation(ROTATION_NONE)
-        givenDisplay(
-            screenBounds = Rect(0, 0, 800, 600),
-            displayUniqueId = "2"
-        )
-        configurationController.onConfigurationChanged(configuration)
+
+        configuration.windowConfiguration.maxBounds = Rect(0, 0, 800, 600)
         provider.getStatusBarContentAreaForRotation(ROTATION_NONE)
-        givenDisplay(
-            screenBounds = Rect(0, 0, 1080, 2160),
-            displayUniqueId = "1"
-        )
-        configurationController.onConfigurationChanged(configuration)
+
+        configuration.windowConfiguration.maxBounds = Rect(0, 0, 1080, 2160)
 
         // WHEN: get insets on the first display again
         val firstDisplayInsetsSecondCall = provider
@@ -513,9 +499,70 @@
         assertThat(firstDisplayInsetsFirstCall).isEqualTo(firstDisplayInsetsSecondCall)
     }
 
-    private fun givenDisplay(screenBounds: Rect, displayUniqueId: String) {
-        `when`(display.uniqueId).thenReturn(displayUniqueId)
-        configuration.windowConfiguration.maxBounds = screenBounds
+    // Regression test for b/245799099
+    @Test
+    fun onMaxBoundsChanged_listenerNotified() {
+        // Start out with an existing configuration with bounds
+        configuration.windowConfiguration.setMaxBounds(0, 0, 100, 100)
+        configurationController.onConfigurationChanged(configuration)
+        val provider = StatusBarContentInsetsProvider(contextMock, configurationController,
+                mock(DumpManager::class.java))
+        val listener = object : StatusBarContentInsetsChangedListener {
+            var triggered = false
+
+            override fun onStatusBarContentInsetsChanged() {
+                triggered = true
+            }
+        }
+        provider.addCallback(listener)
+
+        // WHEN the config is updated with new bounds
+        configuration.windowConfiguration.setMaxBounds(0, 0, 456, 789)
+        configurationController.onConfigurationChanged(configuration)
+
+        // THEN the listener is notified
+        assertThat(listener.triggered).isTrue()
+    }
+
+    @Test
+    fun onDensityOrFontScaleChanged_listenerNotified() {
+        configuration.densityDpi = 12
+        val provider = StatusBarContentInsetsProvider(contextMock, configurationController,
+                mock(DumpManager::class.java))
+        val listener = object : StatusBarContentInsetsChangedListener {
+            var triggered = false
+
+            override fun onStatusBarContentInsetsChanged() {
+                triggered = true
+            }
+        }
+        provider.addCallback(listener)
+
+        // WHEN the config is updated
+        configuration.densityDpi = 20
+        configurationController.onConfigurationChanged(configuration)
+
+        // THEN the listener is notified
+        assertThat(listener.triggered).isTrue()
+    }
+
+    @Test
+    fun onThemeChanged_listenerNotified() {
+        val provider = StatusBarContentInsetsProvider(contextMock, configurationController,
+                mock(DumpManager::class.java))
+        val listener = object : StatusBarContentInsetsChangedListener {
+            var triggered = false
+
+            override fun onStatusBarContentInsetsChanged() {
+                triggered = true
+            }
+        }
+        provider.addCallback(listener)
+
+        configurationController.notifyThemeChanged()
+
+        // THEN the listener is notified
+        assertThat(listener.triggered).isTrue()
     }
 
     private fun assertRects(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BaseUserSwitcherAdapterTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BaseUserSwitcherAdapterTest.kt
index f304647..0a3da0b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BaseUserSwitcherAdapterTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BaseUserSwitcherAdapterTest.kt
@@ -237,7 +237,7 @@
     fun refresh() {
         underTest.refresh()
 
-        verify(controller).refreshUsers(UserHandle.USER_NULL)
+        verify(controller).refreshUsers()
     }
 
     private fun createUserRecord(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BluetoothControllerImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BluetoothControllerImplTest.java
index d0391ac..833cabb 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BluetoothControllerImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BluetoothControllerImplTest.java
@@ -43,6 +43,7 @@
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.bluetooth.BluetoothLogger;
 import com.android.systemui.dump.DumpManager;
+import com.android.systemui.settings.UserTracker;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -56,6 +57,7 @@
 @SmallTest
 public class BluetoothControllerImplTest extends SysuiTestCase {
 
+    private UserTracker mUserTracker;
     private LocalBluetoothManager mMockBluetoothManager;
     private CachedBluetoothDeviceManager mMockDeviceManager;
     private LocalBluetoothAdapter mMockAdapter;
@@ -70,6 +72,7 @@
         mTestableLooper = TestableLooper.get(this);
         mMockBluetoothManager = mDependency.injectMockDependency(LocalBluetoothManager.class);
         mDevices = new ArrayList<>();
+        mUserTracker = mock(UserTracker.class);
         mMockDeviceManager = mock(CachedBluetoothDeviceManager.class);
         when(mMockDeviceManager.getCachedDevicesCopy()).thenReturn(mDevices);
         when(mMockBluetoothManager.getCachedDeviceManager()).thenReturn(mMockDeviceManager);
@@ -81,6 +84,7 @@
         mMockDumpManager = mock(DumpManager.class);
 
         mBluetoothControllerImpl = new BluetoothControllerImpl(mContext,
+                mUserTracker,
                 mMockDumpManager,
                 mock(BluetoothLogger.class),
                 mTestableLooper.getLooper(),
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/HotspotControllerImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/HotspotControllerImplTest.java
index 26df03f..dc08aba 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/HotspotControllerImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/HotspotControllerImplTest.java
@@ -41,6 +41,7 @@
 import com.android.systemui.R;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.dump.DumpManager;
+import com.android.systemui.settings.UserTracker;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -61,6 +62,8 @@
 public class HotspotControllerImplTest extends SysuiTestCase {
 
     @Mock
+    private UserTracker mUserTracker;
+    @Mock
     private DumpManager mDumpManager;
     @Mock
     private TetheringManager mTetheringManager;
@@ -104,7 +107,8 @@
 
         Handler handler = new Handler(mLooper.getLooper());
 
-        mController = new HotspotControllerImpl(mContext, handler, handler, mDumpManager);
+        mController = new HotspotControllerImpl(mContext, mUserTracker, handler, handler,
+                mDumpManager);
         verify(mTetheringManager)
                 .registerTetheringEventCallback(any(), mTetheringCallbackCaptor.capture());
     }
@@ -191,7 +195,7 @@
         Handler handler = new Handler(mLooper.getLooper());
 
         HotspotController controller =
-                new HotspotControllerImpl(mContext, handler, handler, mDumpManager);
+                new HotspotControllerImpl(mContext, mUserTracker, handler, handler, mDumpManager);
 
         verifyNoMoreInteractions(mTetheringManager);
         assertFalse(controller.isHotspotSupported());
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SecurityControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SecurityControllerTest.java
index d44cdb2..15235b6 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SecurityControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SecurityControllerTest.java
@@ -50,6 +50,7 @@
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.broadcast.BroadcastDispatcher;
 import com.android.systemui.dump.DumpManager;
+import com.android.systemui.settings.UserTracker;
 import com.android.systemui.util.concurrency.FakeExecutor;
 import com.android.systemui.util.time.FakeSystemClock;
 
@@ -72,10 +73,12 @@
     private final DevicePolicyManager mDevicePolicyManager = mock(DevicePolicyManager.class);
     private final IKeyChainService.Stub mKeyChainService = mock(IKeyChainService.Stub.class);
     private final UserManager mUserManager = mock(UserManager.class);
+    private final UserTracker mUserTracker = mock(UserTracker.class);
     private final BroadcastDispatcher mBroadcastDispatcher = mock(BroadcastDispatcher.class);
     private final Handler mHandler = mock(Handler.class);
     private SecurityControllerImpl mSecurityController;
     private ConnectivityManager mConnectivityManager = mock(ConnectivityManager.class);
+    private FakeExecutor mMainExecutor;
     private FakeExecutor mBgExecutor;
     private BroadcastReceiver mBroadcastReceiver;
 
@@ -102,11 +105,14 @@
         ArgumentCaptor<BroadcastReceiver> brCaptor =
                 ArgumentCaptor.forClass(BroadcastReceiver.class);
 
+        mMainExecutor = new FakeExecutor(new FakeSystemClock());
         mBgExecutor = new FakeExecutor(new FakeSystemClock());
         mSecurityController = new SecurityControllerImpl(
                 mContext,
+                mUserTracker,
                 mHandler,
                 mBroadcastDispatcher,
+                mMainExecutor,
                 mBgExecutor,
                 Mockito.mock(DumpManager.class));
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/UserSwitcherControllerOldImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/UserSwitcherControllerOldImplTest.kt
deleted file mode 100644
index 169f4fb..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/UserSwitcherControllerOldImplTest.kt
+++ /dev/null
@@ -1,727 +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.systemui.statusbar.policy
-
-import android.app.IActivityManager
-import android.app.NotificationManager
-import android.app.admin.DevicePolicyManager
-import android.content.BroadcastReceiver
-import android.content.Context
-import android.content.DialogInterface
-import android.content.Intent
-import android.content.pm.UserInfo
-import android.graphics.Bitmap
-import android.hardware.face.FaceManager
-import android.hardware.fingerprint.FingerprintManager
-import android.os.Handler
-import android.os.UserHandle
-import android.os.UserManager
-import android.provider.Settings
-import android.testing.AndroidTestingRunner
-import android.testing.TestableLooper
-import android.view.ThreadedRenderer
-import androidx.test.filters.SmallTest
-import com.android.internal.jank.InteractionJankMonitor
-import com.android.internal.logging.testing.UiEventLoggerFake
-import com.android.internal.util.LatencyTracker
-import com.android.internal.util.UserIcons
-import com.android.systemui.GuestResetOrExitSessionReceiver
-import com.android.systemui.GuestResumeSessionReceiver
-import com.android.systemui.GuestSessionNotification
-import com.android.systemui.R
-import com.android.systemui.SysuiTestCase
-import com.android.systemui.animation.DialogCuj
-import com.android.systemui.animation.DialogLaunchAnimator
-import com.android.systemui.broadcast.BroadcastDispatcher
-import com.android.systemui.broadcast.BroadcastSender
-import com.android.systemui.dump.DumpManager
-import com.android.systemui.plugins.ActivityStarter
-import com.android.systemui.plugins.FalsingManager
-import com.android.systemui.qs.QSUserSwitcherEvent
-import com.android.systemui.qs.user.UserSwitchDialogController
-import com.android.systemui.settings.UserTracker
-import com.android.systemui.shade.NotificationShadeWindowView
-import com.android.systemui.telephony.TelephonyListenerManager
-import com.android.systemui.user.data.source.UserRecord
-import com.android.systemui.user.legacyhelper.data.LegacyUserDataHelper
-import com.android.systemui.user.shared.model.UserActionModel
-import com.android.systemui.util.concurrency.FakeExecutor
-import com.android.systemui.util.mockito.any
-import com.android.systemui.util.mockito.argumentCaptor
-import com.android.systemui.util.mockito.capture
-import com.android.systemui.util.mockito.kotlinArgumentCaptor
-import com.android.systemui.util.mockito.nullable
-import com.android.systemui.util.settings.GlobalSettings
-import com.android.systemui.util.settings.SecureSettings
-import com.android.systemui.util.time.FakeSystemClock
-import com.google.common.truth.Truth
-import org.junit.Assert.assertEquals
-import org.junit.Assert.assertFalse
-import org.junit.Assert.assertNotNull
-import org.junit.Assert.assertTrue
-import org.junit.Before
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.mockito.ArgumentMatchers.anyInt
-import org.mockito.Mock
-import org.mockito.Mockito.doNothing
-import org.mockito.Mockito.doReturn
-import org.mockito.Mockito.eq
-import org.mockito.Mockito.mock
-import org.mockito.Mockito.never
-import org.mockito.Mockito.verify
-import org.mockito.Mockito.`when`
-import org.mockito.MockitoAnnotations
-
-@RunWith(AndroidTestingRunner::class)
-@TestableLooper.RunWithLooper(setAsMainLooper = true)
-@SmallTest
-class UserSwitcherControllerOldImplTest : SysuiTestCase() {
-    @Mock private lateinit var keyguardStateController: KeyguardStateController
-    @Mock private lateinit var activityManager: IActivityManager
-    @Mock private lateinit var deviceProvisionedController: DeviceProvisionedController
-    @Mock private lateinit var devicePolicyManager: DevicePolicyManager
-    @Mock private lateinit var handler: Handler
-    @Mock private lateinit var userTracker: UserTracker
-    @Mock private lateinit var userManager: UserManager
-    @Mock private lateinit var activityStarter: ActivityStarter
-    @Mock private lateinit var broadcastDispatcher: BroadcastDispatcher
-    @Mock private lateinit var broadcastSender: BroadcastSender
-    @Mock private lateinit var telephonyListenerManager: TelephonyListenerManager
-    @Mock private lateinit var secureSettings: SecureSettings
-    @Mock private lateinit var falsingManager: FalsingManager
-    @Mock private lateinit var dumpManager: DumpManager
-    @Mock private lateinit var interactionJankMonitor: InteractionJankMonitor
-    @Mock private lateinit var latencyTracker: LatencyTracker
-    @Mock private lateinit var dialogShower: UserSwitchDialogController.DialogShower
-    @Mock private lateinit var notificationShadeWindowView: NotificationShadeWindowView
-    @Mock private lateinit var threadedRenderer: ThreadedRenderer
-    @Mock private lateinit var dialogLaunchAnimator: DialogLaunchAnimator
-    @Mock private lateinit var globalSettings: GlobalSettings
-    @Mock private lateinit var guestSessionNotification: GuestSessionNotification
-    @Mock private lateinit var guestResetOrExitSessionReceiver: GuestResetOrExitSessionReceiver
-    private lateinit var resetSessionDialogFactory:
-                            GuestResumeSessionReceiver.ResetSessionDialog.Factory
-    private lateinit var guestResumeSessionReceiver: GuestResumeSessionReceiver
-    private lateinit var testableLooper: TestableLooper
-    private lateinit var bgExecutor: FakeExecutor
-    private lateinit var longRunningExecutor: FakeExecutor
-    private lateinit var uiExecutor: FakeExecutor
-    private lateinit var uiEventLogger: UiEventLoggerFake
-    private lateinit var userSwitcherController: UserSwitcherControllerOldImpl
-    private lateinit var picture: Bitmap
-    private val ownerId = UserHandle.USER_SYSTEM
-    private val ownerInfo = UserInfo(ownerId, "Owner", null,
-            UserInfo.FLAG_ADMIN or UserInfo.FLAG_FULL or UserInfo.FLAG_INITIALIZED or
-                    UserInfo.FLAG_PRIMARY or UserInfo.FLAG_SYSTEM or UserInfo.FLAG_ADMIN,
-            UserManager.USER_TYPE_FULL_SYSTEM)
-    private val guestId = 1234
-    private val guestInfo = UserInfo(guestId, "Guest", null,
-            UserInfo.FLAG_FULL or UserInfo.FLAG_GUEST, UserManager.USER_TYPE_FULL_GUEST)
-    private val secondaryUser =
-            UserInfo(10, "Secondary", null, 0, UserManager.USER_TYPE_FULL_SECONDARY)
-
-    @Before
-    fun setUp() {
-        MockitoAnnotations.initMocks(this)
-        testableLooper = TestableLooper.get(this)
-        bgExecutor = FakeExecutor(FakeSystemClock())
-        longRunningExecutor = FakeExecutor(FakeSystemClock())
-        uiExecutor = FakeExecutor(FakeSystemClock())
-        uiEventLogger = UiEventLoggerFake()
-
-        mContext.orCreateTestableResources.addOverride(
-                com.android.internal.R.bool.config_guestUserAutoCreated, false)
-
-        mContext.addMockSystemService(Context.FACE_SERVICE, mock(FaceManager::class.java))
-        mContext.addMockSystemService(Context.NOTIFICATION_SERVICE,
-                mock(NotificationManager::class.java))
-        mContext.addMockSystemService(Context.FINGERPRINT_SERVICE,
-                mock(FingerprintManager::class.java))
-
-        resetSessionDialogFactory = object : GuestResumeSessionReceiver.ResetSessionDialog.Factory {
-                override fun create(userId: Int): GuestResumeSessionReceiver.ResetSessionDialog {
-                    return GuestResumeSessionReceiver.ResetSessionDialog(
-                                mContext,
-                                mock(UserSwitcherController::class.java),
-                                uiEventLogger,
-                                userId
-                            )
-                }
-            }
-
-        guestResumeSessionReceiver = GuestResumeSessionReceiver(userTracker,
-                                        secureSettings,
-                                        broadcastDispatcher,
-                                        guestSessionNotification,
-                                        resetSessionDialogFactory)
-
-        `when`(userManager.canAddMoreUsers(eq(UserManager.USER_TYPE_FULL_SECONDARY)))
-                .thenReturn(true)
-        `when`(notificationShadeWindowView.context).thenReturn(context)
-
-        // Since userSwitcherController involves InteractionJankMonitor.
-        // Let's fulfill the dependencies.
-        val mockedContext = mock(Context::class.java)
-        doReturn(mockedContext).`when`(notificationShadeWindowView).context
-        doReturn(true).`when`(notificationShadeWindowView).isAttachedToWindow
-        doNothing().`when`(threadedRenderer).addObserver(any())
-        doNothing().`when`(threadedRenderer).removeObserver(any())
-        doReturn(threadedRenderer).`when`(notificationShadeWindowView).threadedRenderer
-
-        picture = UserIcons.convertToBitmap(context.getDrawable(R.drawable.ic_avatar_user))
-
-        // Create defaults for the current user
-        `when`(userTracker.userId).thenReturn(ownerId)
-        `when`(userTracker.userInfo).thenReturn(ownerInfo)
-
-        `when`(
-            globalSettings.getIntForUser(
-                eq(Settings.Global.ADD_USERS_WHEN_LOCKED),
-                anyInt(),
-                eq(UserHandle.USER_SYSTEM)
-            )
-        ).thenReturn(0)
-
-        `when`(
-            globalSettings.getIntForUser(
-                eq(Settings.Global.USER_SWITCHER_ENABLED),
-                anyInt(),
-                eq(UserHandle.USER_SYSTEM)
-            )
-        ).thenReturn(1)
-
-        setupController()
-    }
-
-    private fun setupController() {
-        userSwitcherController =
-            UserSwitcherControllerOldImpl(
-                mContext,
-                activityManager,
-                userManager,
-                userTracker,
-                keyguardStateController,
-                deviceProvisionedController,
-                devicePolicyManager,
-                handler,
-                activityStarter,
-                broadcastDispatcher,
-                broadcastSender,
-                uiEventLogger,
-                falsingManager,
-                telephonyListenerManager,
-                secureSettings,
-                globalSettings,
-                bgExecutor,
-                longRunningExecutor,
-                uiExecutor,
-                interactionJankMonitor,
-                latencyTracker,
-                dumpManager,
-                dialogLaunchAnimator,
-                guestResumeSessionReceiver,
-                guestResetOrExitSessionReceiver
-            )
-        userSwitcherController.init(notificationShadeWindowView)
-    }
-
-    @Test
-    fun testSwitchUser_parentDialogDismissed() {
-        val otherUserRecord = UserRecord(
-            secondaryUser,
-            picture,
-            false /* guest */,
-            false /* current */,
-            false /* isAddUser */,
-            false /* isRestricted */,
-            true /* isSwitchToEnabled */,
-            false /* isAddSupervisedUser */
-        )
-        `when`(userTracker.userId).thenReturn(ownerId)
-        `when`(userTracker.userInfo).thenReturn(ownerInfo)
-
-        userSwitcherController.onUserListItemClicked(otherUserRecord, dialogShower)
-        testableLooper.processAllMessages()
-
-        verify(dialogShower).dismiss()
-    }
-
-    @Test
-    fun testAddGuest_okButtonPressed() {
-        val emptyGuestUserRecord =
-            UserRecord(
-                null,
-                null,
-                true /* guest */,
-                false /* current */,
-                false /* isAddUser */,
-                false /* isRestricted */,
-                true /* isSwitchToEnabled */,
-                false /* isAddSupervisedUser */
-            )
-        `when`(userTracker.userId).thenReturn(ownerId)
-        `when`(userTracker.userInfo).thenReturn(ownerInfo)
-
-        `when`(userManager.createGuest(any())).thenReturn(guestInfo)
-
-        userSwitcherController.onUserListItemClicked(emptyGuestUserRecord, null)
-        bgExecutor.runAllReady()
-        uiExecutor.runAllReady()
-        testableLooper.processAllMessages()
-        verify(interactionJankMonitor).begin(any())
-        verify(latencyTracker).onActionStart(LatencyTracker.ACTION_USER_SWITCH)
-        verify(activityManager).switchUser(guestInfo.id)
-        assertEquals(1, uiEventLogger.numLogs())
-        assertEquals(QSUserSwitcherEvent.QS_USER_GUEST_ADD.id, uiEventLogger.eventId(0))
-    }
-
-    @Test
-    fun testAddGuest_parentDialogDismissed() {
-        val emptyGuestUserRecord =
-            UserRecord(
-                null,
-                null,
-                true /* guest */,
-                false /* current */,
-                false /* isAddUser */,
-                false /* isRestricted */,
-                true /* isSwitchToEnabled */,
-                false /* isAddSupervisedUser */
-            )
-        `when`(userTracker.userId).thenReturn(ownerId)
-        `when`(userTracker.userInfo).thenReturn(ownerInfo)
-
-        `when`(userManager.createGuest(any())).thenReturn(guestInfo)
-
-        userSwitcherController.onUserListItemClicked(emptyGuestUserRecord, dialogShower)
-        bgExecutor.runAllReady()
-        uiExecutor.runAllReady()
-        testableLooper.processAllMessages()
-        verify(dialogShower).dismiss()
-    }
-
-    @Test
-    fun testRemoveGuest_removeButtonPressed_isLogged() {
-        val currentGuestUserRecord =
-            UserRecord(
-                guestInfo,
-                picture,
-                true /* guest */,
-                true /* current */,
-                false /* isAddUser */,
-                false /* isRestricted */,
-                true /* isSwitchToEnabled */,
-                false /* isAddSupervisedUser */
-            )
-        `when`(userTracker.userId).thenReturn(guestInfo.id)
-        `when`(userTracker.userInfo).thenReturn(guestInfo)
-
-        userSwitcherController.onUserListItemClicked(currentGuestUserRecord, null)
-        assertNotNull(userSwitcherController.mExitGuestDialog)
-        userSwitcherController.mExitGuestDialog
-                .getButton(DialogInterface.BUTTON_POSITIVE).performClick()
-        testableLooper.processAllMessages()
-        assertEquals(1, uiEventLogger.numLogs())
-        assertTrue(
-            QSUserSwitcherEvent.QS_USER_GUEST_REMOVE.id == uiEventLogger.eventId(0) ||
-            QSUserSwitcherEvent.QS_USER_SWITCH.id == uiEventLogger.eventId(0)
-        )
-    }
-
-    @Test
-    fun testRemoveGuest_removeButtonPressed_dialogDismissed() {
-        val currentGuestUserRecord =
-            UserRecord(
-                guestInfo,
-                picture,
-                true /* guest */,
-                true /* current */,
-                false /* isAddUser */,
-                false /* isRestricted */,
-                true /* isSwitchToEnabled */,
-                false /* isAddSupervisedUser */
-            )
-        `when`(userTracker.userId).thenReturn(guestInfo.id)
-        `when`(userTracker.userInfo).thenReturn(guestInfo)
-
-        userSwitcherController.onUserListItemClicked(currentGuestUserRecord, null)
-        assertNotNull(userSwitcherController.mExitGuestDialog)
-        userSwitcherController.mExitGuestDialog
-                .getButton(DialogInterface.BUTTON_POSITIVE).performClick()
-        testableLooper.processAllMessages()
-        assertFalse(userSwitcherController.mExitGuestDialog.isShowing)
-    }
-
-    @Test
-    fun testRemoveGuest_dialogShowerUsed() {
-        val currentGuestUserRecord =
-            UserRecord(
-                guestInfo,
-                picture,
-                true /* guest */,
-                true /* current */,
-                false /* isAddUser */,
-                false /* isRestricted */,
-                true /* isSwitchToEnabled */,
-                false /* isAddSupervisedUser */
-            )
-        `when`(userTracker.userId).thenReturn(guestInfo.id)
-        `when`(userTracker.userInfo).thenReturn(guestInfo)
-
-        userSwitcherController.onUserListItemClicked(currentGuestUserRecord, dialogShower)
-        assertNotNull(userSwitcherController.mExitGuestDialog)
-        testableLooper.processAllMessages()
-        verify(dialogShower)
-            .showDialog(
-                userSwitcherController.mExitGuestDialog,
-                DialogCuj(InteractionJankMonitor.CUJ_USER_DIALOG_OPEN, "exit_guest_mode"))
-    }
-
-    @Test
-    fun testRemoveGuest_cancelButtonPressed_isNotLogged() {
-        val currentGuestUserRecord =
-            UserRecord(
-                guestInfo,
-                picture,
-                true /* guest */,
-                true /* current */,
-                false /* isAddUser */,
-                false /* isRestricted */,
-                true /* isSwitchToEnabled */,
-                false /* isAddSupervisedUser */
-            )
-        `when`(userTracker.userId).thenReturn(guestId)
-        `when`(userTracker.userInfo).thenReturn(guestInfo)
-
-        userSwitcherController.onUserListItemClicked(currentGuestUserRecord, null)
-        assertNotNull(userSwitcherController.mExitGuestDialog)
-        userSwitcherController.mExitGuestDialog
-                .getButton(DialogInterface.BUTTON_NEUTRAL).performClick()
-        testableLooper.processAllMessages()
-        assertEquals(0, uiEventLogger.numLogs())
-    }
-
-    @Test
-    fun testWipeGuest_startOverButtonPressed_isLogged() {
-        val currentGuestUserRecord =
-            UserRecord(
-                guestInfo,
-                picture,
-                true /* guest */,
-                false /* current */,
-                false /* isAddUser */,
-                false /* isRestricted */,
-                true /* isSwitchToEnabled */,
-                false /* isAddSupervisedUser */
-            )
-        `when`(userTracker.userId).thenReturn(guestId)
-        `when`(userTracker.userInfo).thenReturn(guestInfo)
-
-        // Simulate that guest user has already logged in
-        `when`(secureSettings.getIntForUser(
-                eq(GuestResumeSessionReceiver.SETTING_GUEST_HAS_LOGGED_IN), anyInt(), anyInt()))
-                .thenReturn(1)
-
-        userSwitcherController.onUserListItemClicked(currentGuestUserRecord, null)
-
-        // Simulate a user switch event
-        val intent = Intent(Intent.ACTION_USER_SWITCHED).putExtra(Intent.EXTRA_USER_HANDLE, guestId)
-
-        assertNotNull(userSwitcherController.mGuestResumeSessionReceiver)
-        userSwitcherController.mGuestResumeSessionReceiver.onReceive(context, intent)
-
-        assertNotNull(userSwitcherController.mGuestResumeSessionReceiver.mNewSessionDialog)
-        userSwitcherController.mGuestResumeSessionReceiver.mNewSessionDialog
-                .getButton(GuestResumeSessionReceiver.ResetSessionDialog.BUTTON_WIPE).performClick()
-        testableLooper.processAllMessages()
-        assertEquals(1, uiEventLogger.numLogs())
-        assertEquals(QSUserSwitcherEvent.QS_USER_GUEST_WIPE.id, uiEventLogger.eventId(0))
-    }
-
-    @Test
-    fun testWipeGuest_continueButtonPressed_isLogged() {
-        val currentGuestUserRecord =
-            UserRecord(
-                guestInfo,
-                picture,
-                true /* guest */,
-                false /* current */,
-                false /* isAddUser */,
-                false /* isRestricted */,
-                true /* isSwitchToEnabled */,
-                false /* isAddSupervisedUser */
-            )
-        `when`(userTracker.userId).thenReturn(guestId)
-        `when`(userTracker.userInfo).thenReturn(guestInfo)
-
-        // Simulate that guest user has already logged in
-        `when`(secureSettings.getIntForUser(
-                eq(GuestResumeSessionReceiver.SETTING_GUEST_HAS_LOGGED_IN), anyInt(), anyInt()))
-                .thenReturn(1)
-
-        userSwitcherController.onUserListItemClicked(currentGuestUserRecord, null)
-
-        // Simulate a user switch event
-        val intent = Intent(Intent.ACTION_USER_SWITCHED).putExtra(Intent.EXTRA_USER_HANDLE, guestId)
-
-        assertNotNull(userSwitcherController.mGuestResumeSessionReceiver)
-        userSwitcherController.mGuestResumeSessionReceiver.onReceive(context, intent)
-
-        assertNotNull(userSwitcherController.mGuestResumeSessionReceiver.mNewSessionDialog)
-        userSwitcherController.mGuestResumeSessionReceiver.mNewSessionDialog
-                .getButton(GuestResumeSessionReceiver.ResetSessionDialog.BUTTON_DONTWIPE)
-                .performClick()
-        testableLooper.processAllMessages()
-        assertEquals(1, uiEventLogger.numLogs())
-        assertEquals(QSUserSwitcherEvent.QS_USER_GUEST_CONTINUE.id, uiEventLogger.eventId(0))
-    }
-
-    @Test
-    fun test_getCurrentUserName_shouldReturnNameOfTheCurrentUser() {
-        fun addUser(id: Int, name: String, isCurrent: Boolean) {
-            userSwitcherController.users.add(
-                UserRecord(
-                    UserInfo(id, name, 0),
-                    null, false, isCurrent, false,
-                    false, false, false
-                )
-            )
-        }
-        val bgUserName = "background_user"
-        val fgUserName = "foreground_user"
-
-        addUser(1, bgUserName, false)
-        addUser(2, fgUserName, true)
-
-        assertEquals(fgUserName, userSwitcherController.currentUserName)
-    }
-
-    @Test
-    fun isSystemUser_currentUserIsSystemUser_shouldReturnTrue() {
-        `when`(userTracker.userId).thenReturn(UserHandle.USER_SYSTEM)
-        assertEquals(true, userSwitcherController.isSystemUser)
-    }
-
-    @Test
-    fun isSystemUser_currentUserIsNotSystemUser_shouldReturnFalse() {
-        `when`(userTracker.userId).thenReturn(1)
-        assertEquals(false, userSwitcherController.isSystemUser)
-    }
-
-    @Test
-    fun testCanCreateSupervisedUserWithConfiguredPackage() {
-        // GIVEN the supervised user creation package is configured
-        `when`(context.getString(
-            com.android.internal.R.string.config_supervisedUserCreationPackage))
-            .thenReturn("some_pkg")
-
-        // AND the current user is allowed to create new users
-        `when`(userTracker.userId).thenReturn(ownerId)
-        `when`(userTracker.userInfo).thenReturn(ownerInfo)
-
-        // WHEN the controller is started with the above config
-        setupController()
-        testableLooper.processAllMessages()
-
-        // THEN a supervised user can be constructed
-        assertTrue(userSwitcherController.canCreateSupervisedUser())
-    }
-
-    @Test
-    fun testCannotCreateSupervisedUserWithConfiguredPackage() {
-        // GIVEN the supervised user creation package is NOT configured
-        `when`(context.getString(
-            com.android.internal.R.string.config_supervisedUserCreationPackage))
-            .thenReturn(null)
-
-        // AND the current user is allowed to create new users
-        `when`(userTracker.userId).thenReturn(ownerId)
-        `when`(userTracker.userInfo).thenReturn(ownerInfo)
-
-        // WHEN the controller is started with the above config
-        setupController()
-        testableLooper.processAllMessages()
-
-        // THEN a supervised user can NOT be constructed
-        assertFalse(userSwitcherController.canCreateSupervisedUser())
-    }
-
-    @Test
-    fun testCannotCreateUserWhenUserSwitcherDisabled() {
-        `when`(
-            globalSettings.getIntForUser(
-                eq(Settings.Global.USER_SWITCHER_ENABLED),
-                anyInt(),
-                eq(UserHandle.USER_SYSTEM)
-            )
-        ).thenReturn(0)
-        setupController()
-        assertFalse(userSwitcherController.canCreateUser())
-    }
-
-    @Test
-    fun testCannotCreateGuestUserWhenUserSwitcherDisabled() {
-        `when`(
-            globalSettings.getIntForUser(
-                eq(Settings.Global.USER_SWITCHER_ENABLED),
-                anyInt(),
-                eq(UserHandle.USER_SYSTEM)
-            )
-        ).thenReturn(0)
-        setupController()
-        assertFalse(userSwitcherController.canCreateGuest(false))
-    }
-
-    @Test
-    fun testCannotCreateSupervisedUserWhenUserSwitcherDisabled() {
-        `when`(
-            globalSettings.getIntForUser(
-                eq(Settings.Global.USER_SWITCHER_ENABLED),
-                anyInt(),
-                eq(UserHandle.USER_SYSTEM)
-            )
-        ).thenReturn(0)
-        setupController()
-        assertFalse(userSwitcherController.canCreateSupervisedUser())
-    }
-
-    @Test
-    fun testCanManageUser_userSwitcherEnabled_addUserWhenLocked() {
-        `when`(
-            globalSettings.getIntForUser(
-                eq(Settings.Global.USER_SWITCHER_ENABLED),
-                anyInt(),
-                eq(UserHandle.USER_SYSTEM)
-            )
-        ).thenReturn(1)
-
-        `when`(
-            globalSettings.getIntForUser(
-                eq(Settings.Global.ADD_USERS_WHEN_LOCKED),
-                anyInt(),
-                eq(UserHandle.USER_SYSTEM)
-            )
-        ).thenReturn(1)
-        setupController()
-        assertTrue(userSwitcherController.canManageUsers())
-    }
-
-    @Test
-    fun testCanManageUser_userSwitcherDisabled_addUserWhenLocked() {
-        `when`(
-            globalSettings.getIntForUser(
-                eq(Settings.Global.USER_SWITCHER_ENABLED),
-                anyInt(),
-                eq(UserHandle.USER_SYSTEM)
-            )
-        ).thenReturn(0)
-
-        `when`(
-            globalSettings.getIntForUser(
-                eq(Settings.Global.ADD_USERS_WHEN_LOCKED),
-                anyInt(),
-                eq(UserHandle.USER_SYSTEM)
-            )
-        ).thenReturn(1)
-        setupController()
-        assertFalse(userSwitcherController.canManageUsers())
-    }
-
-    @Test
-    fun testCanManageUser_userSwitcherEnabled_isAdmin() {
-        `when`(
-            globalSettings.getIntForUser(
-                eq(Settings.Global.USER_SWITCHER_ENABLED),
-                anyInt(),
-                eq(UserHandle.USER_SYSTEM)
-            )
-        ).thenReturn(1)
-
-        setupController()
-        assertTrue(userSwitcherController.canManageUsers())
-    }
-
-    @Test
-    fun testCanManageUser_userSwitcherDisabled_isAdmin() {
-        `when`(
-            globalSettings.getIntForUser(
-                eq(Settings.Global.USER_SWITCHER_ENABLED),
-                anyInt(),
-                eq(UserHandle.USER_SYSTEM)
-            )
-        ).thenReturn(0)
-
-        setupController()
-        assertFalse(userSwitcherController.canManageUsers())
-    }
-
-    @Test
-    fun addUserSwitchCallback() {
-        val broadcastReceiverCaptor = argumentCaptor<BroadcastReceiver>()
-        verify(broadcastDispatcher).registerReceiver(
-                capture(broadcastReceiverCaptor),
-                any(),
-                nullable(), nullable(), anyInt(), nullable())
-
-        val cb = mock(UserSwitcherController.UserSwitchCallback::class.java)
-        userSwitcherController.addUserSwitchCallback(cb)
-
-        val intent = Intent(Intent.ACTION_USER_SWITCHED).putExtra(Intent.EXTRA_USER_HANDLE, guestId)
-        broadcastReceiverCaptor.value.onReceive(context, intent)
-        verify(cb).onUserSwitched()
-    }
-
-    @Test
-    fun onUserItemClicked_guest_runsOnBgThread() {
-        val dialogShower = mock(UserSwitchDialogController.DialogShower::class.java)
-        val guestUserRecord = UserRecord(
-            null,
-            picture,
-            true /* guest */,
-            false /* current */,
-            false /* isAddUser */,
-            false /* isRestricted */,
-            true /* isSwitchToEnabled */,
-            false /* isAddSupervisedUser */
-        )
-
-        userSwitcherController.onUserListItemClicked(guestUserRecord, dialogShower)
-        assertTrue(bgExecutor.numPending() > 0)
-        verify(userManager, never()).createGuest(context)
-        bgExecutor.runAllReady()
-        verify(userManager).createGuest(context)
-    }
-
-    @Test
-    fun onUserItemClicked_manageUsers() {
-        val manageUserRecord = LegacyUserDataHelper.createRecord(
-            mContext,
-            ownerId,
-            UserActionModel.NAVIGATE_TO_USER_MANAGEMENT,
-            isRestricted = false,
-            isSwitchToEnabled = true
-        )
-
-        userSwitcherController.onUserListItemClicked(manageUserRecord, null)
-        val intentCaptor = kotlinArgumentCaptor<Intent>()
-        verify(activityStarter).startActivity(intentCaptor.capture(),
-            eq(true)
-        )
-        Truth.assertThat(intentCaptor.value.action).isEqualTo(Settings.ACTION_USER_SETTINGS)
-    }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/ZenModeControllerImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/ZenModeControllerImplTest.java
index 3fe1a9f..c06dbdc 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/ZenModeControllerImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/ZenModeControllerImplTest.java
@@ -35,6 +35,7 @@
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.broadcast.BroadcastDispatcher;
 import com.android.systemui.dump.DumpManager;
+import com.android.systemui.settings.UserTracker;
 import com.android.systemui.statusbar.policy.ZenModeController.Callback;
 import com.android.systemui.util.settings.FakeSettings;
 
@@ -58,6 +59,8 @@
     BroadcastDispatcher mBroadcastDispatcher;
     @Mock
     DumpManager mDumpManager;
+    @Mock
+    UserTracker mUserTracker;
 
     private ZenModeControllerImpl mController;
 
@@ -72,7 +75,8 @@
                 Handler.createAsync(Looper.myLooper()),
                 mBroadcastDispatcher,
                 mDumpManager,
-                new FakeSettings());
+                new FakeSettings(),
+                mUserTracker);
     }
 
     @Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/user/data/repository/UserRepositoryImplRefactoredTest.kt b/packages/SystemUI/tests/src/com/android/systemui/user/data/repository/UserRepositoryImplRefactoredTest.kt
deleted file mode 100644
index 7c7f0e1..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/user/data/repository/UserRepositoryImplRefactoredTest.kt
+++ /dev/null
@@ -1,248 +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.user.data.repository
-
-import android.content.pm.UserInfo
-import android.os.UserHandle
-import android.os.UserManager
-import android.provider.Settings
-import androidx.test.filters.SmallTest
-import com.android.systemui.user.data.model.UserSwitcherSettingsModel
-import com.google.common.truth.Truth.assertThat
-import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.Job
-import kotlinx.coroutines.cancel
-import kotlinx.coroutines.flow.launchIn
-import kotlinx.coroutines.flow.onEach
-import kotlinx.coroutines.runBlocking
-import org.junit.Before
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.junit.runners.JUnit4
-import org.mockito.Mockito.`when` as whenever
-
-@SmallTest
-@RunWith(JUnit4::class)
-class UserRepositoryImplRefactoredTest : UserRepositoryImplTest() {
-
-    @Before
-    fun setUp() {
-        super.setUp(isRefactored = true)
-    }
-
-    @Test
-    fun userSwitcherSettings() = runSelfCancelingTest {
-        setUpGlobalSettings(
-            isSimpleUserSwitcher = true,
-            isAddUsersFromLockscreen = true,
-            isUserSwitcherEnabled = true,
-        )
-        underTest = create(this)
-
-        var value: UserSwitcherSettingsModel? = null
-        underTest.userSwitcherSettings.onEach { value = it }.launchIn(this)
-
-        assertUserSwitcherSettings(
-            model = value,
-            expectedSimpleUserSwitcher = true,
-            expectedAddUsersFromLockscreen = true,
-            expectedUserSwitcherEnabled = true,
-        )
-
-        setUpGlobalSettings(
-            isSimpleUserSwitcher = false,
-            isAddUsersFromLockscreen = true,
-            isUserSwitcherEnabled = true,
-        )
-        assertUserSwitcherSettings(
-            model = value,
-            expectedSimpleUserSwitcher = false,
-            expectedAddUsersFromLockscreen = true,
-            expectedUserSwitcherEnabled = true,
-        )
-    }
-
-    @Test
-    fun refreshUsers() = runSelfCancelingTest {
-        underTest = create(this)
-        val initialExpectedValue =
-            setUpUsers(
-                count = 3,
-                selectedIndex = 0,
-            )
-        var userInfos: List<UserInfo>? = null
-        var selectedUserInfo: UserInfo? = null
-        underTest.userInfos.onEach { userInfos = it }.launchIn(this)
-        underTest.selectedUserInfo.onEach { selectedUserInfo = it }.launchIn(this)
-
-        underTest.refreshUsers()
-        assertThat(userInfos).isEqualTo(initialExpectedValue)
-        assertThat(selectedUserInfo).isEqualTo(initialExpectedValue[0])
-        assertThat(underTest.lastSelectedNonGuestUserId).isEqualTo(selectedUserInfo?.id)
-
-        val secondExpectedValue =
-            setUpUsers(
-                count = 4,
-                selectedIndex = 1,
-            )
-        underTest.refreshUsers()
-        assertThat(userInfos).isEqualTo(secondExpectedValue)
-        assertThat(selectedUserInfo).isEqualTo(secondExpectedValue[1])
-        assertThat(underTest.lastSelectedNonGuestUserId).isEqualTo(selectedUserInfo?.id)
-
-        val selectedNonGuestUserId = selectedUserInfo?.id
-        val thirdExpectedValue =
-            setUpUsers(
-                count = 2,
-                isLastGuestUser = true,
-                selectedIndex = 1,
-            )
-        underTest.refreshUsers()
-        assertThat(userInfos).isEqualTo(thirdExpectedValue)
-        assertThat(selectedUserInfo).isEqualTo(thirdExpectedValue[1])
-        assertThat(selectedUserInfo?.isGuest).isTrue()
-        assertThat(underTest.lastSelectedNonGuestUserId).isEqualTo(selectedNonGuestUserId)
-    }
-
-    @Test
-    fun `refreshUsers - sorts by creation time - guest user last`() = runSelfCancelingTest {
-        underTest = create(this)
-        val unsortedUsers =
-            setUpUsers(
-                count = 3,
-                selectedIndex = 0,
-                isLastGuestUser = true,
-            )
-        unsortedUsers[0].creationTime = 999
-        unsortedUsers[1].creationTime = 900
-        unsortedUsers[2].creationTime = 950
-        val expectedUsers =
-            listOf(
-                unsortedUsers[1],
-                unsortedUsers[0],
-                unsortedUsers[2], // last because this is the guest
-            )
-        var userInfos: List<UserInfo>? = null
-        underTest.userInfos.onEach { userInfos = it }.launchIn(this)
-
-        underTest.refreshUsers()
-        assertThat(userInfos).isEqualTo(expectedUsers)
-    }
-
-    @Test
-    fun `userTrackerCallback - updates selectedUserInfo`() = runSelfCancelingTest {
-        underTest = create(this)
-        var selectedUserInfo: UserInfo? = null
-        underTest.selectedUserInfo.onEach { selectedUserInfo = it }.launchIn(this)
-        setUpUsers(
-            count = 2,
-            selectedIndex = 0,
-        )
-        tracker.onProfileChanged()
-        assertThat(selectedUserInfo?.id == 0)
-        setUpUsers(
-            count = 2,
-            selectedIndex = 1,
-        )
-        tracker.onProfileChanged()
-        assertThat(selectedUserInfo?.id == 1)
-    }
-
-    private fun setUpUsers(
-        count: Int,
-        isLastGuestUser: Boolean = false,
-        selectedIndex: Int = 0,
-    ): List<UserInfo> {
-        val userInfos =
-            (0 until count).map { index ->
-                createUserInfo(
-                    index,
-                    isGuest = isLastGuestUser && index == count - 1,
-                )
-            }
-        whenever(manager.aliveUsers).thenReturn(userInfos)
-        tracker.set(userInfos, selectedIndex)
-        return userInfos
-    }
-
-    private fun createUserInfo(
-        id: Int,
-        isGuest: Boolean,
-    ): UserInfo {
-        val flags = 0
-        return UserInfo(
-            id,
-            "user_$id",
-            /* iconPath= */ "",
-            flags,
-            if (isGuest) UserManager.USER_TYPE_FULL_GUEST else UserInfo.getDefaultUserType(flags),
-        )
-    }
-
-    private fun setUpGlobalSettings(
-        isSimpleUserSwitcher: Boolean = false,
-        isAddUsersFromLockscreen: Boolean = false,
-        isUserSwitcherEnabled: Boolean = true,
-    ) {
-        context.orCreateTestableResources.addOverride(
-            com.android.internal.R.bool.config_expandLockScreenUserSwitcher,
-            true,
-        )
-        globalSettings.putIntForUser(
-            UserRepositoryImpl.SETTING_SIMPLE_USER_SWITCHER,
-            if (isSimpleUserSwitcher) 1 else 0,
-            UserHandle.USER_SYSTEM,
-        )
-        globalSettings.putIntForUser(
-            Settings.Global.ADD_USERS_WHEN_LOCKED,
-            if (isAddUsersFromLockscreen) 1 else 0,
-            UserHandle.USER_SYSTEM,
-        )
-        globalSettings.putIntForUser(
-            Settings.Global.USER_SWITCHER_ENABLED,
-            if (isUserSwitcherEnabled) 1 else 0,
-            UserHandle.USER_SYSTEM,
-        )
-    }
-
-    private fun assertUserSwitcherSettings(
-        model: UserSwitcherSettingsModel?,
-        expectedSimpleUserSwitcher: Boolean,
-        expectedAddUsersFromLockscreen: Boolean,
-        expectedUserSwitcherEnabled: Boolean,
-    ) {
-        checkNotNull(model)
-        assertThat(model.isSimpleUserSwitcher).isEqualTo(expectedSimpleUserSwitcher)
-        assertThat(model.isAddUsersFromLockscreen).isEqualTo(expectedAddUsersFromLockscreen)
-        assertThat(model.isUserSwitcherEnabled).isEqualTo(expectedUserSwitcherEnabled)
-    }
-
-    /**
-     * Executes the given block of execution within the scope of a dedicated [CoroutineScope] which
-     * is then automatically canceled and cleaned-up.
-     */
-    private fun runSelfCancelingTest(
-        block: suspend CoroutineScope.() -> Unit,
-    ) =
-        runBlocking(Dispatchers.Main.immediate) {
-            val scope = CoroutineScope(coroutineContext + Job())
-            block(scope)
-            scope.cancel()
-        }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/user/data/repository/UserRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/user/data/repository/UserRepositoryImplTest.kt
index dcea83a..2e527be1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/user/data/repository/UserRepositoryImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/user/data/repository/UserRepositoryImplTest.kt
@@ -17,54 +17,263 @@
 
 package com.android.systemui.user.data.repository
 
+import android.content.pm.UserInfo
+import android.os.UserHandle
 import android.os.UserManager
+import android.provider.Settings
+import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
-import com.android.systemui.flags.FakeFeatureFlags
-import com.android.systemui.flags.Flags
 import com.android.systemui.settings.FakeUserTracker
-import com.android.systemui.statusbar.policy.UserSwitcherController
+import com.android.systemui.user.data.model.UserSwitcherSettingsModel
 import com.android.systemui.util.settings.FakeSettings
+import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.Job
+import kotlinx.coroutines.cancel
+import kotlinx.coroutines.flow.launchIn
+import kotlinx.coroutines.flow.onEach
+import kotlinx.coroutines.runBlocking
 import kotlinx.coroutines.test.TestCoroutineScope
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
 import org.mockito.Mock
+import org.mockito.Mockito.`when` as whenever
 import org.mockito.MockitoAnnotations
 
-abstract class UserRepositoryImplTest : SysuiTestCase() {
+@SmallTest
+@RunWith(JUnit4::class)
+class UserRepositoryImplTest : SysuiTestCase() {
 
-    @Mock protected lateinit var manager: UserManager
-    @Mock protected lateinit var controller: UserSwitcherController
+    @Mock private lateinit var manager: UserManager
 
-    protected lateinit var underTest: UserRepositoryImpl
+    private lateinit var underTest: UserRepositoryImpl
 
-    protected lateinit var globalSettings: FakeSettings
-    protected lateinit var tracker: FakeUserTracker
-    protected lateinit var featureFlags: FakeFeatureFlags
+    private lateinit var globalSettings: FakeSettings
+    private lateinit var tracker: FakeUserTracker
 
-    protected fun setUp(isRefactored: Boolean) {
+    @Before
+    fun setUp() {
         MockitoAnnotations.initMocks(this)
 
         globalSettings = FakeSettings()
         tracker = FakeUserTracker()
-        featureFlags = FakeFeatureFlags()
-        featureFlags.set(Flags.USER_INTERACTOR_AND_REPO_USE_CONTROLLER, !isRefactored)
     }
 
-    protected fun create(scope: CoroutineScope = TestCoroutineScope()): UserRepositoryImpl {
+    @Test
+    fun userSwitcherSettings() = runSelfCancelingTest {
+        setUpGlobalSettings(
+            isSimpleUserSwitcher = true,
+            isAddUsersFromLockscreen = true,
+            isUserSwitcherEnabled = true,
+        )
+        underTest = create(this)
+
+        var value: UserSwitcherSettingsModel? = null
+        underTest.userSwitcherSettings.onEach { value = it }.launchIn(this)
+
+        assertUserSwitcherSettings(
+            model = value,
+            expectedSimpleUserSwitcher = true,
+            expectedAddUsersFromLockscreen = true,
+            expectedUserSwitcherEnabled = true,
+        )
+
+        setUpGlobalSettings(
+            isSimpleUserSwitcher = false,
+            isAddUsersFromLockscreen = true,
+            isUserSwitcherEnabled = true,
+        )
+        assertUserSwitcherSettings(
+            model = value,
+            expectedSimpleUserSwitcher = false,
+            expectedAddUsersFromLockscreen = true,
+            expectedUserSwitcherEnabled = true,
+        )
+    }
+
+    @Test
+    fun refreshUsers() = runSelfCancelingTest {
+        underTest = create(this)
+        val initialExpectedValue =
+            setUpUsers(
+                count = 3,
+                selectedIndex = 0,
+            )
+        var userInfos: List<UserInfo>? = null
+        var selectedUserInfo: UserInfo? = null
+        underTest.userInfos.onEach { userInfos = it }.launchIn(this)
+        underTest.selectedUserInfo.onEach { selectedUserInfo = it }.launchIn(this)
+
+        underTest.refreshUsers()
+        assertThat(userInfos).isEqualTo(initialExpectedValue)
+        assertThat(selectedUserInfo).isEqualTo(initialExpectedValue[0])
+        assertThat(underTest.lastSelectedNonGuestUserId).isEqualTo(selectedUserInfo?.id)
+
+        val secondExpectedValue =
+            setUpUsers(
+                count = 4,
+                selectedIndex = 1,
+            )
+        underTest.refreshUsers()
+        assertThat(userInfos).isEqualTo(secondExpectedValue)
+        assertThat(selectedUserInfo).isEqualTo(secondExpectedValue[1])
+        assertThat(underTest.lastSelectedNonGuestUserId).isEqualTo(selectedUserInfo?.id)
+
+        val selectedNonGuestUserId = selectedUserInfo?.id
+        val thirdExpectedValue =
+            setUpUsers(
+                count = 2,
+                isLastGuestUser = true,
+                selectedIndex = 1,
+            )
+        underTest.refreshUsers()
+        assertThat(userInfos).isEqualTo(thirdExpectedValue)
+        assertThat(selectedUserInfo).isEqualTo(thirdExpectedValue[1])
+        assertThat(selectedUserInfo?.isGuest).isTrue()
+        assertThat(underTest.lastSelectedNonGuestUserId).isEqualTo(selectedNonGuestUserId)
+    }
+
+    @Test
+    fun `refreshUsers - sorts by creation time - guest user last`() = runSelfCancelingTest {
+        underTest = create(this)
+        val unsortedUsers =
+            setUpUsers(
+                count = 3,
+                selectedIndex = 0,
+                isLastGuestUser = true,
+            )
+        unsortedUsers[0].creationTime = 999
+        unsortedUsers[1].creationTime = 900
+        unsortedUsers[2].creationTime = 950
+        val expectedUsers =
+            listOf(
+                unsortedUsers[1],
+                unsortedUsers[0],
+                unsortedUsers[2], // last because this is the guest
+            )
+        var userInfos: List<UserInfo>? = null
+        underTest.userInfos.onEach { userInfos = it }.launchIn(this)
+
+        underTest.refreshUsers()
+        assertThat(userInfos).isEqualTo(expectedUsers)
+    }
+
+    private fun setUpUsers(
+        count: Int,
+        isLastGuestUser: Boolean = false,
+        selectedIndex: Int = 0,
+    ): List<UserInfo> {
+        val userInfos =
+            (0 until count).map { index ->
+                createUserInfo(
+                    index,
+                    isGuest = isLastGuestUser && index == count - 1,
+                )
+            }
+        whenever(manager.aliveUsers).thenReturn(userInfos)
+        tracker.set(userInfos, selectedIndex)
+        return userInfos
+    }
+    @Test
+    fun `userTrackerCallback - updates selectedUserInfo`() = runSelfCancelingTest {
+        underTest = create(this)
+        var selectedUserInfo: UserInfo? = null
+        underTest.selectedUserInfo.onEach { selectedUserInfo = it }.launchIn(this)
+        setUpUsers(
+            count = 2,
+            selectedIndex = 0,
+        )
+        tracker.onProfileChanged()
+        assertThat(selectedUserInfo?.id).isEqualTo(0)
+        setUpUsers(
+            count = 2,
+            selectedIndex = 1,
+        )
+        tracker.onProfileChanged()
+        assertThat(selectedUserInfo?.id).isEqualTo(1)
+    }
+
+    private fun createUserInfo(
+        id: Int,
+        isGuest: Boolean,
+    ): UserInfo {
+        val flags = 0
+        return UserInfo(
+            id,
+            "user_$id",
+            /* iconPath= */ "",
+            flags,
+            if (isGuest) UserManager.USER_TYPE_FULL_GUEST else UserInfo.getDefaultUserType(flags),
+        )
+    }
+
+    private fun setUpGlobalSettings(
+        isSimpleUserSwitcher: Boolean = false,
+        isAddUsersFromLockscreen: Boolean = false,
+        isUserSwitcherEnabled: Boolean = true,
+    ) {
+        context.orCreateTestableResources.addOverride(
+            com.android.internal.R.bool.config_expandLockScreenUserSwitcher,
+            true,
+        )
+        globalSettings.putIntForUser(
+            UserRepositoryImpl.SETTING_SIMPLE_USER_SWITCHER,
+            if (isSimpleUserSwitcher) 1 else 0,
+            UserHandle.USER_SYSTEM,
+        )
+        globalSettings.putIntForUser(
+            Settings.Global.ADD_USERS_WHEN_LOCKED,
+            if (isAddUsersFromLockscreen) 1 else 0,
+            UserHandle.USER_SYSTEM,
+        )
+        globalSettings.putIntForUser(
+            Settings.Global.USER_SWITCHER_ENABLED,
+            if (isUserSwitcherEnabled) 1 else 0,
+            UserHandle.USER_SYSTEM,
+        )
+    }
+
+    private fun assertUserSwitcherSettings(
+        model: UserSwitcherSettingsModel?,
+        expectedSimpleUserSwitcher: Boolean,
+        expectedAddUsersFromLockscreen: Boolean,
+        expectedUserSwitcherEnabled: Boolean,
+    ) {
+        checkNotNull(model)
+        assertThat(model.isSimpleUserSwitcher).isEqualTo(expectedSimpleUserSwitcher)
+        assertThat(model.isAddUsersFromLockscreen).isEqualTo(expectedAddUsersFromLockscreen)
+        assertThat(model.isUserSwitcherEnabled).isEqualTo(expectedUserSwitcherEnabled)
+    }
+
+    /**
+     * Executes the given block of execution within the scope of a dedicated [CoroutineScope] which
+     * is then automatically canceled and cleaned-up.
+     */
+    private fun runSelfCancelingTest(
+        block: suspend CoroutineScope.() -> Unit,
+    ) =
+        runBlocking(Dispatchers.Main.immediate) {
+            val scope = CoroutineScope(coroutineContext + Job())
+            block(scope)
+            scope.cancel()
+        }
+
+    private fun create(scope: CoroutineScope = TestCoroutineScope()): UserRepositoryImpl {
         return UserRepositoryImpl(
             appContext = context,
             manager = manager,
-            controller = controller,
             applicationScope = scope,
             mainDispatcher = IMMEDIATE,
             backgroundDispatcher = IMMEDIATE,
             globalSettings = globalSettings,
             tracker = tracker,
-            featureFlags = featureFlags,
         )
     }
 
     companion object {
-        @JvmStatic protected val IMMEDIATE = Dispatchers.Main.immediate
+        @JvmStatic private val IMMEDIATE = Dispatchers.Main.immediate
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/user/data/repository/UserRepositoryImplUnrefactoredTest.kt b/packages/SystemUI/tests/src/com/android/systemui/user/data/repository/UserRepositoryImplUnrefactoredTest.kt
deleted file mode 100644
index a363a03..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/user/data/repository/UserRepositoryImplUnrefactoredTest.kt
+++ /dev/null
@@ -1,209 +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.user.data.repository
-
-import android.content.pm.UserInfo
-import androidx.test.filters.SmallTest
-import com.android.systemui.statusbar.policy.UserSwitcherController
-import com.android.systemui.user.data.source.UserRecord
-import com.android.systemui.user.shared.model.UserActionModel
-import com.android.systemui.user.shared.model.UserModel
-import com.android.systemui.util.mockito.any
-import com.android.systemui.util.mockito.capture
-import com.google.common.truth.Truth.assertThat
-import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.flow.MutableStateFlow
-import kotlinx.coroutines.flow.launchIn
-import kotlinx.coroutines.flow.map
-import kotlinx.coroutines.flow.onEach
-import kotlinx.coroutines.runBlocking
-import org.junit.Before
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.junit.runners.JUnit4
-import org.mockito.ArgumentCaptor
-import org.mockito.Captor
-import org.mockito.Mockito.verify
-import org.mockito.Mockito.`when` as whenever
-
-@SmallTest
-@RunWith(JUnit4::class)
-class UserRepositoryImplUnrefactoredTest : UserRepositoryImplTest() {
-
-    companion object {
-        private val IMMEDIATE = Dispatchers.Main.immediate
-    }
-
-    @Captor
-    private lateinit var userSwitchCallbackCaptor:
-        ArgumentCaptor<UserSwitcherController.UserSwitchCallback>
-
-    @Before
-    fun setUp() {
-        super.setUp(isRefactored = false)
-
-        whenever(controller.isAddUsersFromLockScreenEnabled).thenReturn(MutableStateFlow(false))
-        whenever(controller.isGuestUserAutoCreated).thenReturn(false)
-        whenever(controller.isGuestUserResetting).thenReturn(false)
-
-        underTest = create()
-    }
-
-    @Test
-    fun `users - registers for updates`() =
-        runBlocking(IMMEDIATE) {
-            val job = underTest.users.onEach {}.launchIn(this)
-
-            verify(controller).addUserSwitchCallback(any())
-
-            job.cancel()
-        }
-
-    @Test
-    fun `users - unregisters from updates`() =
-        runBlocking(IMMEDIATE) {
-            val job = underTest.users.onEach {}.launchIn(this)
-            verify(controller).addUserSwitchCallback(capture(userSwitchCallbackCaptor))
-
-            job.cancel()
-
-            verify(controller).removeUserSwitchCallback(userSwitchCallbackCaptor.value)
-        }
-
-    @Test
-    fun `users - does not include actions`() =
-        runBlocking(IMMEDIATE) {
-            whenever(controller.users)
-                .thenReturn(
-                    arrayListOf(
-                        createUserRecord(0, isSelected = true),
-                        createActionRecord(UserActionModel.ADD_USER),
-                        createUserRecord(1),
-                        createUserRecord(2),
-                        createActionRecord(UserActionModel.ADD_SUPERVISED_USER),
-                        createActionRecord(UserActionModel.ENTER_GUEST_MODE),
-                        createActionRecord(UserActionModel.NAVIGATE_TO_USER_MANAGEMENT),
-                    )
-                )
-            var models: List<UserModel>? = null
-            val job = underTest.users.onEach { models = it }.launchIn(this)
-
-            assertThat(models).hasSize(3)
-            assertThat(models?.get(0)?.id).isEqualTo(0)
-            assertThat(models?.get(0)?.isSelected).isTrue()
-            assertThat(models?.get(1)?.id).isEqualTo(1)
-            assertThat(models?.get(1)?.isSelected).isFalse()
-            assertThat(models?.get(2)?.id).isEqualTo(2)
-            assertThat(models?.get(2)?.isSelected).isFalse()
-            job.cancel()
-        }
-
-    @Test
-    fun selectedUser() =
-        runBlocking(IMMEDIATE) {
-            whenever(controller.users)
-                .thenReturn(
-                    arrayListOf(
-                        createUserRecord(0, isSelected = true),
-                        createUserRecord(1),
-                        createUserRecord(2),
-                    )
-                )
-            var id: Int? = null
-            val job = underTest.selectedUser.map { it.id }.onEach { id = it }.launchIn(this)
-
-            assertThat(id).isEqualTo(0)
-
-            whenever(controller.users)
-                .thenReturn(
-                    arrayListOf(
-                        createUserRecord(0),
-                        createUserRecord(1),
-                        createUserRecord(2, isSelected = true),
-                    )
-                )
-            verify(controller).addUserSwitchCallback(capture(userSwitchCallbackCaptor))
-            userSwitchCallbackCaptor.value.onUserSwitched()
-            assertThat(id).isEqualTo(2)
-
-            job.cancel()
-        }
-
-    @Test
-    fun `actions - unregisters from updates`() =
-        runBlocking(IMMEDIATE) {
-            val job = underTest.actions.onEach {}.launchIn(this)
-            verify(controller).addUserSwitchCallback(capture(userSwitchCallbackCaptor))
-
-            job.cancel()
-
-            verify(controller).removeUserSwitchCallback(userSwitchCallbackCaptor.value)
-        }
-
-    @Test
-    fun `actions - registers for updates`() =
-        runBlocking(IMMEDIATE) {
-            val job = underTest.actions.onEach {}.launchIn(this)
-
-            verify(controller).addUserSwitchCallback(any())
-
-            job.cancel()
-        }
-
-    @Test
-    fun `actions - does not include users`() =
-        runBlocking(IMMEDIATE) {
-            whenever(controller.users)
-                .thenReturn(
-                    arrayListOf(
-                        createUserRecord(0, isSelected = true),
-                        createActionRecord(UserActionModel.ADD_USER),
-                        createUserRecord(1),
-                        createUserRecord(2),
-                        createActionRecord(UserActionModel.ADD_SUPERVISED_USER),
-                        createActionRecord(UserActionModel.ENTER_GUEST_MODE),
-                        createActionRecord(UserActionModel.NAVIGATE_TO_USER_MANAGEMENT),
-                    )
-                )
-            var models: List<UserActionModel>? = null
-            val job = underTest.actions.onEach { models = it }.launchIn(this)
-
-            assertThat(models).hasSize(4)
-            assertThat(models?.get(0)).isEqualTo(UserActionModel.ADD_USER)
-            assertThat(models?.get(1)).isEqualTo(UserActionModel.ADD_SUPERVISED_USER)
-            assertThat(models?.get(2)).isEqualTo(UserActionModel.ENTER_GUEST_MODE)
-            assertThat(models?.get(3)).isEqualTo(UserActionModel.NAVIGATE_TO_USER_MANAGEMENT)
-            job.cancel()
-        }
-
-    private fun createUserRecord(id: Int, isSelected: Boolean = false): UserRecord {
-        return UserRecord(
-            info = UserInfo(id, "name$id", 0),
-            isCurrent = isSelected,
-        )
-    }
-
-    private fun createActionRecord(action: UserActionModel): UserRecord {
-        return UserRecord(
-            isAddUser = action == UserActionModel.ADD_USER,
-            isAddSupervisedUser = action == UserActionModel.ADD_SUPERVISED_USER,
-            isGuest = action == UserActionModel.ENTER_GUEST_MODE,
-            isManageUsers = action == UserActionModel.NAVIGATE_TO_USER_MANAGEMENT,
-        )
-    }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/UserInteractorRefactoredTest.kt b/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/UserInteractorRefactoredTest.kt
deleted file mode 100644
index f682e31..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/UserInteractorRefactoredTest.kt
+++ /dev/null
@@ -1,740 +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.user.domain.interactor
-
-import android.content.Intent
-import android.content.pm.UserInfo
-import android.graphics.Bitmap
-import android.graphics.drawable.Drawable
-import android.os.UserHandle
-import android.os.UserManager
-import android.provider.Settings
-import androidx.test.filters.SmallTest
-import com.android.internal.R.drawable.ic_account_circle
-import com.android.systemui.R
-import com.android.systemui.common.shared.model.Text
-import com.android.systemui.qs.user.UserSwitchDialogController
-import com.android.systemui.user.data.model.UserSwitcherSettingsModel
-import com.android.systemui.user.data.source.UserRecord
-import com.android.systemui.user.domain.model.ShowDialogRequestModel
-import com.android.systemui.user.shared.model.UserActionModel
-import com.android.systemui.user.shared.model.UserModel
-import com.android.systemui.util.mockito.any
-import com.android.systemui.util.mockito.eq
-import com.android.systemui.util.mockito.kotlinArgumentCaptor
-import com.android.systemui.util.mockito.mock
-import com.android.systemui.util.mockito.whenever
-import com.google.common.truth.Truth.assertThat
-import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.flow.launchIn
-import kotlinx.coroutines.flow.onEach
-import kotlinx.coroutines.runBlocking
-import kotlinx.coroutines.test.advanceUntilIdle
-import org.junit.Before
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.junit.runners.JUnit4
-import org.mockito.ArgumentMatchers.anyBoolean
-import org.mockito.ArgumentMatchers.anyInt
-import org.mockito.Mockito.never
-import org.mockito.Mockito.verify
-
-@SmallTest
-@RunWith(JUnit4::class)
-class UserInteractorRefactoredTest : UserInteractorTest() {
-
-    override fun isRefactored(): Boolean {
-        return true
-    }
-
-    @Before
-    override fun setUp() {
-        super.setUp()
-
-        overrideResource(R.drawable.ic_account_circle, GUEST_ICON)
-        overrideResource(R.dimen.max_avatar_size, 10)
-        overrideResource(
-            com.android.internal.R.string.config_supervisedUserCreationPackage,
-            SUPERVISED_USER_CREATION_APP_PACKAGE,
-        )
-        whenever(manager.getUserIcon(anyInt())).thenReturn(ICON)
-        whenever(manager.canAddMoreUsers(any())).thenReturn(true)
-    }
-
-    @Test
-    fun `onRecordSelected - user`() =
-        runBlocking(IMMEDIATE) {
-            val userInfos = createUserInfos(count = 3, includeGuest = false)
-            userRepository.setUserInfos(userInfos)
-            userRepository.setSelectedUserInfo(userInfos[0])
-            userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = true))
-
-            underTest.onRecordSelected(UserRecord(info = userInfos[1]), dialogShower)
-
-            verify(dialogShower).dismiss()
-            verify(activityManager).switchUser(userInfos[1].id)
-            Unit
-        }
-
-    @Test
-    fun `onRecordSelected - switch to guest user`() =
-        runBlocking(IMMEDIATE) {
-            val userInfos = createUserInfos(count = 3, includeGuest = true)
-            userRepository.setUserInfos(userInfos)
-            userRepository.setSelectedUserInfo(userInfos[0])
-            userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = true))
-
-            underTest.onRecordSelected(UserRecord(info = userInfos.last()))
-
-            verify(activityManager).switchUser(userInfos.last().id)
-            Unit
-        }
-
-    @Test
-    fun `onRecordSelected - enter guest mode`() =
-        runBlocking(IMMEDIATE) {
-            val userInfos = createUserInfos(count = 3, includeGuest = false)
-            userRepository.setUserInfos(userInfos)
-            userRepository.setSelectedUserInfo(userInfos[0])
-            userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = true))
-            val guestUserInfo = createUserInfo(id = 1337, name = "guest", isGuest = true)
-            whenever(manager.createGuest(any())).thenReturn(guestUserInfo)
-
-            underTest.onRecordSelected(UserRecord(isGuest = true), dialogShower)
-
-            verify(dialogShower).dismiss()
-            verify(manager).createGuest(any())
-            Unit
-        }
-
-    @Test
-    fun `onRecordSelected - action`() =
-        runBlocking(IMMEDIATE) {
-            val userInfos = createUserInfos(count = 3, includeGuest = true)
-            userRepository.setUserInfos(userInfos)
-            userRepository.setSelectedUserInfo(userInfos[0])
-            userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = true))
-
-            underTest.onRecordSelected(UserRecord(isAddSupervisedUser = true), dialogShower)
-
-            verify(dialogShower, never()).dismiss()
-            verify(activityStarter).startActivity(any(), anyBoolean())
-        }
-
-    @Test
-    fun `users - switcher enabled`() =
-        runBlocking(IMMEDIATE) {
-            val userInfos = createUserInfos(count = 3, includeGuest = true)
-            userRepository.setUserInfos(userInfos)
-            userRepository.setSelectedUserInfo(userInfos[0])
-            userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = true))
-
-            var value: List<UserModel>? = null
-            val job = underTest.users.onEach { value = it }.launchIn(this)
-            assertUsers(models = value, count = 3, includeGuest = true)
-
-            job.cancel()
-        }
-
-    @Test
-    fun `users - switches to second user`() =
-        runBlocking(IMMEDIATE) {
-            val userInfos = createUserInfos(count = 2, includeGuest = false)
-            userRepository.setUserInfos(userInfos)
-            userRepository.setSelectedUserInfo(userInfos[0])
-            userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = true))
-
-            var value: List<UserModel>? = null
-            val job = underTest.users.onEach { value = it }.launchIn(this)
-            userRepository.setSelectedUserInfo(userInfos[1])
-
-            assertUsers(models = value, count = 2, selectedIndex = 1)
-            job.cancel()
-        }
-
-    @Test
-    fun `users - switcher not enabled`() =
-        runBlocking(IMMEDIATE) {
-            val userInfos = createUserInfos(count = 2, includeGuest = false)
-            userRepository.setUserInfos(userInfos)
-            userRepository.setSelectedUserInfo(userInfos[0])
-            userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = false))
-
-            var value: List<UserModel>? = null
-            val job = underTest.users.onEach { value = it }.launchIn(this)
-            assertUsers(models = value, count = 1)
-
-            job.cancel()
-        }
-
-    @Test
-    fun selectedUser() =
-        runBlocking(IMMEDIATE) {
-            val userInfos = createUserInfos(count = 2, includeGuest = false)
-            userRepository.setUserInfos(userInfos)
-            userRepository.setSelectedUserInfo(userInfos[0])
-            userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = true))
-
-            var value: UserModel? = null
-            val job = underTest.selectedUser.onEach { value = it }.launchIn(this)
-            assertUser(value, id = 0, isSelected = true)
-
-            userRepository.setSelectedUserInfo(userInfos[1])
-            assertUser(value, id = 1, isSelected = true)
-
-            job.cancel()
-        }
-
-    @Test
-    fun `actions - device unlocked`() =
-        runBlocking(IMMEDIATE) {
-            val userInfos = createUserInfos(count = 2, includeGuest = false)
-
-            userRepository.setUserInfos(userInfos)
-            userRepository.setSelectedUserInfo(userInfos[0])
-            userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = true))
-            keyguardRepository.setKeyguardShowing(false)
-            var value: List<UserActionModel>? = null
-            val job = underTest.actions.onEach { value = it }.launchIn(this)
-
-            assertThat(value)
-                .isEqualTo(
-                    listOf(
-                        UserActionModel.ENTER_GUEST_MODE,
-                        UserActionModel.ADD_USER,
-                        UserActionModel.ADD_SUPERVISED_USER,
-                        UserActionModel.NAVIGATE_TO_USER_MANAGEMENT,
-                    )
-                )
-
-            job.cancel()
-        }
-
-    @Test
-    fun `actions - device unlocked user not primary - empty list`() =
-        runBlocking(IMMEDIATE) {
-            val userInfos = createUserInfos(count = 2, includeGuest = false)
-            userRepository.setUserInfos(userInfos)
-            userRepository.setSelectedUserInfo(userInfos[1])
-            userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = true))
-            keyguardRepository.setKeyguardShowing(false)
-            var value: List<UserActionModel>? = null
-            val job = underTest.actions.onEach { value = it }.launchIn(this)
-
-            assertThat(value).isEqualTo(emptyList<UserActionModel>())
-
-            job.cancel()
-        }
-
-    @Test
-    fun `actions - device unlocked user is guest - empty list`() =
-        runBlocking(IMMEDIATE) {
-            val userInfos = createUserInfos(count = 2, includeGuest = true)
-            assertThat(userInfos[1].isGuest).isTrue()
-            userRepository.setUserInfos(userInfos)
-            userRepository.setSelectedUserInfo(userInfos[1])
-            userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = true))
-            keyguardRepository.setKeyguardShowing(false)
-            var value: List<UserActionModel>? = null
-            val job = underTest.actions.onEach { value = it }.launchIn(this)
-
-            assertThat(value).isEqualTo(emptyList<UserActionModel>())
-
-            job.cancel()
-        }
-
-    @Test
-    fun `actions - device locked add from lockscreen set - full list`() =
-        runBlocking(IMMEDIATE) {
-            val userInfos = createUserInfos(count = 2, includeGuest = false)
-            userRepository.setUserInfos(userInfos)
-            userRepository.setSelectedUserInfo(userInfos[0])
-            userRepository.setSettings(
-                UserSwitcherSettingsModel(
-                    isUserSwitcherEnabled = true,
-                    isAddUsersFromLockscreen = true,
-                )
-            )
-            keyguardRepository.setKeyguardShowing(false)
-            var value: List<UserActionModel>? = null
-            val job = underTest.actions.onEach { value = it }.launchIn(this)
-
-            assertThat(value)
-                .isEqualTo(
-                    listOf(
-                        UserActionModel.ENTER_GUEST_MODE,
-                        UserActionModel.ADD_USER,
-                        UserActionModel.ADD_SUPERVISED_USER,
-                        UserActionModel.NAVIGATE_TO_USER_MANAGEMENT,
-                    )
-                )
-
-            job.cancel()
-        }
-
-    @Test
-    fun `actions - device locked - only guest action and manage user is shown`() =
-        runBlocking(IMMEDIATE) {
-            val userInfos = createUserInfos(count = 2, includeGuest = false)
-            userRepository.setUserInfos(userInfos)
-            userRepository.setSelectedUserInfo(userInfos[0])
-            userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = true))
-            keyguardRepository.setKeyguardShowing(true)
-            var value: List<UserActionModel>? = null
-            val job = underTest.actions.onEach { value = it }.launchIn(this)
-
-            assertThat(value)
-                .isEqualTo(
-                    listOf(
-                        UserActionModel.ENTER_GUEST_MODE,
-                        UserActionModel.NAVIGATE_TO_USER_MANAGEMENT
-                    )
-                )
-
-            job.cancel()
-        }
-
-    @Test
-    fun `executeAction - add user - dialog shown`() =
-        runBlocking(IMMEDIATE) {
-            val userInfos = createUserInfos(count = 2, includeGuest = false)
-            userRepository.setUserInfos(userInfos)
-            userRepository.setSelectedUserInfo(userInfos[0])
-            keyguardRepository.setKeyguardShowing(false)
-            var dialogRequest: ShowDialogRequestModel? = null
-            val job = underTest.dialogShowRequests.onEach { dialogRequest = it }.launchIn(this)
-            val dialogShower: UserSwitchDialogController.DialogShower = mock()
-
-            underTest.executeAction(UserActionModel.ADD_USER, dialogShower)
-            assertThat(dialogRequest)
-                .isEqualTo(
-                    ShowDialogRequestModel.ShowAddUserDialog(
-                        userHandle = userInfos[0].userHandle,
-                        isKeyguardShowing = false,
-                        showEphemeralMessage = false,
-                        dialogShower = dialogShower,
-                    )
-                )
-
-            underTest.onDialogShown()
-            assertThat(dialogRequest).isNull()
-
-            job.cancel()
-        }
-
-    @Test
-    fun `executeAction - add supervised user - starts activity`() =
-        runBlocking(IMMEDIATE) {
-            underTest.executeAction(UserActionModel.ADD_SUPERVISED_USER)
-
-            val intentCaptor = kotlinArgumentCaptor<Intent>()
-            verify(activityStarter).startActivity(intentCaptor.capture(), eq(true))
-            assertThat(intentCaptor.value.action)
-                .isEqualTo(UserManager.ACTION_CREATE_SUPERVISED_USER)
-            assertThat(intentCaptor.value.`package`).isEqualTo(SUPERVISED_USER_CREATION_APP_PACKAGE)
-        }
-
-    @Test
-    fun `executeAction - navigate to manage users`() =
-        runBlocking(IMMEDIATE) {
-            underTest.executeAction(UserActionModel.NAVIGATE_TO_USER_MANAGEMENT)
-
-            val intentCaptor = kotlinArgumentCaptor<Intent>()
-            verify(activityStarter).startActivity(intentCaptor.capture(), eq(true))
-            assertThat(intentCaptor.value.action).isEqualTo(Settings.ACTION_USER_SETTINGS)
-        }
-
-    @Test
-    fun `executeAction - guest mode`() =
-        runBlocking(IMMEDIATE) {
-            val userInfos = createUserInfos(count = 2, includeGuest = false)
-            userRepository.setUserInfos(userInfos)
-            userRepository.setSelectedUserInfo(userInfos[0])
-            userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = true))
-            val guestUserInfo = createUserInfo(id = 1337, name = "guest", isGuest = true)
-            whenever(manager.createGuest(any())).thenReturn(guestUserInfo)
-            val dialogRequests = mutableListOf<ShowDialogRequestModel?>()
-            val showDialogsJob =
-                underTest.dialogShowRequests
-                    .onEach {
-                        dialogRequests.add(it)
-                        if (it != null) {
-                            underTest.onDialogShown()
-                        }
-                    }
-                    .launchIn(this)
-            val dismissDialogsJob =
-                underTest.dialogDismissRequests
-                    .onEach {
-                        if (it != null) {
-                            underTest.onDialogDismissed()
-                        }
-                    }
-                    .launchIn(this)
-
-            underTest.executeAction(UserActionModel.ENTER_GUEST_MODE)
-
-            assertThat(dialogRequests)
-                .contains(
-                    ShowDialogRequestModel.ShowUserCreationDialog(isGuest = true),
-                )
-            verify(activityManager).switchUser(guestUserInfo.id)
-
-            showDialogsJob.cancel()
-            dismissDialogsJob.cancel()
-        }
-
-    @Test
-    fun `selectUser - already selected guest re-selected - exit guest dialog`() =
-        runBlocking(IMMEDIATE) {
-            val userInfos = createUserInfos(count = 2, includeGuest = true)
-            val guestUserInfo = userInfos[1]
-            assertThat(guestUserInfo.isGuest).isTrue()
-            userRepository.setUserInfos(userInfos)
-            userRepository.setSelectedUserInfo(guestUserInfo)
-            userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = true))
-            var dialogRequest: ShowDialogRequestModel? = null
-            val job = underTest.dialogShowRequests.onEach { dialogRequest = it }.launchIn(this)
-
-            underTest.selectUser(
-                newlySelectedUserId = guestUserInfo.id,
-                dialogShower = dialogShower,
-            )
-
-            assertThat(dialogRequest)
-                .isInstanceOf(ShowDialogRequestModel.ShowExitGuestDialog::class.java)
-            verify(dialogShower, never()).dismiss()
-            job.cancel()
-        }
-
-    @Test
-    fun `selectUser - currently guest non-guest selected - exit guest dialog`() =
-        runBlocking(IMMEDIATE) {
-            val userInfos = createUserInfos(count = 2, includeGuest = true)
-            val guestUserInfo = userInfos[1]
-            assertThat(guestUserInfo.isGuest).isTrue()
-            userRepository.setUserInfos(userInfos)
-            userRepository.setSelectedUserInfo(guestUserInfo)
-            userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = true))
-            var dialogRequest: ShowDialogRequestModel? = null
-            val job = underTest.dialogShowRequests.onEach { dialogRequest = it }.launchIn(this)
-
-            underTest.selectUser(newlySelectedUserId = userInfos[0].id, dialogShower = dialogShower)
-
-            assertThat(dialogRequest)
-                .isInstanceOf(ShowDialogRequestModel.ShowExitGuestDialog::class.java)
-            verify(dialogShower, never()).dismiss()
-            job.cancel()
-        }
-
-    @Test
-    fun `selectUser - not currently guest - switches users`() =
-        runBlocking(IMMEDIATE) {
-            val userInfos = createUserInfos(count = 2, includeGuest = false)
-            userRepository.setUserInfos(userInfos)
-            userRepository.setSelectedUserInfo(userInfos[0])
-            userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = true))
-            var dialogRequest: ShowDialogRequestModel? = null
-            val job = underTest.dialogShowRequests.onEach { dialogRequest = it }.launchIn(this)
-
-            underTest.selectUser(newlySelectedUserId = userInfos[1].id, dialogShower = dialogShower)
-
-            assertThat(dialogRequest).isNull()
-            verify(activityManager).switchUser(userInfos[1].id)
-            verify(dialogShower).dismiss()
-            job.cancel()
-        }
-
-    @Test
-    fun `Telephony call state changes - refreshes users`() =
-        runBlocking(IMMEDIATE) {
-            val refreshUsersCallCount = userRepository.refreshUsersCallCount
-
-            telephonyRepository.setCallState(1)
-
-            assertThat(userRepository.refreshUsersCallCount).isEqualTo(refreshUsersCallCount + 1)
-        }
-
-    @Test
-    fun `User switched broadcast`() =
-        runBlocking(IMMEDIATE) {
-            val userInfos = createUserInfos(count = 2, includeGuest = false)
-            userRepository.setUserInfos(userInfos)
-            userRepository.setSelectedUserInfo(userInfos[0])
-            userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = true))
-            val callback1: UserInteractor.UserCallback = mock()
-            val callback2: UserInteractor.UserCallback = mock()
-            underTest.addCallback(callback1)
-            underTest.addCallback(callback2)
-            val refreshUsersCallCount = userRepository.refreshUsersCallCount
-
-            userRepository.setSelectedUserInfo(userInfos[1])
-            fakeBroadcastDispatcher.registeredReceivers.forEach {
-                it.onReceive(
-                    context,
-                    Intent(Intent.ACTION_USER_SWITCHED)
-                        .putExtra(Intent.EXTRA_USER_HANDLE, userInfos[1].id),
-                )
-            }
-
-            verify(callback1).onUserStateChanged()
-            verify(callback2).onUserStateChanged()
-            assertThat(userRepository.secondaryUserId).isEqualTo(userInfos[1].id)
-            assertThat(userRepository.refreshUsersCallCount).isEqualTo(refreshUsersCallCount + 1)
-        }
-
-    @Test
-    fun `User info changed broadcast`() =
-        runBlocking(IMMEDIATE) {
-            val userInfos = createUserInfos(count = 2, includeGuest = false)
-            userRepository.setUserInfos(userInfos)
-            userRepository.setSelectedUserInfo(userInfos[0])
-            val refreshUsersCallCount = userRepository.refreshUsersCallCount
-
-            fakeBroadcastDispatcher.registeredReceivers.forEach {
-                it.onReceive(
-                    context,
-                    Intent(Intent.ACTION_USER_INFO_CHANGED),
-                )
-            }
-
-            assertThat(userRepository.refreshUsersCallCount).isEqualTo(refreshUsersCallCount + 1)
-        }
-
-    @Test
-    fun `System user unlocked broadcast - refresh users`() =
-        runBlocking(IMMEDIATE) {
-            val userInfos = createUserInfos(count = 2, includeGuest = false)
-            userRepository.setUserInfos(userInfos)
-            userRepository.setSelectedUserInfo(userInfos[0])
-            val refreshUsersCallCount = userRepository.refreshUsersCallCount
-
-            fakeBroadcastDispatcher.registeredReceivers.forEach {
-                it.onReceive(
-                    context,
-                    Intent(Intent.ACTION_USER_UNLOCKED)
-                        .putExtra(Intent.EXTRA_USER_HANDLE, UserHandle.USER_SYSTEM),
-                )
-            }
-
-            assertThat(userRepository.refreshUsersCallCount).isEqualTo(refreshUsersCallCount + 1)
-        }
-
-    @Test
-    fun `Non-system user unlocked broadcast - do not refresh users`() =
-        runBlocking(IMMEDIATE) {
-            val userInfos = createUserInfos(count = 2, includeGuest = false)
-            userRepository.setUserInfos(userInfos)
-            userRepository.setSelectedUserInfo(userInfos[0])
-            val refreshUsersCallCount = userRepository.refreshUsersCallCount
-
-            fakeBroadcastDispatcher.registeredReceivers.forEach {
-                it.onReceive(
-                    context,
-                    Intent(Intent.ACTION_USER_UNLOCKED).putExtra(Intent.EXTRA_USER_HANDLE, 1337),
-                )
-            }
-
-            assertThat(userRepository.refreshUsersCallCount).isEqualTo(refreshUsersCallCount)
-        }
-
-    @Test
-    fun userRecords() =
-        runBlocking(IMMEDIATE) {
-            val userInfos = createUserInfos(count = 3, includeGuest = false)
-            userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = true))
-            userRepository.setUserInfos(userInfos)
-            userRepository.setSelectedUserInfo(userInfos[0])
-            keyguardRepository.setKeyguardShowing(false)
-
-            testCoroutineScope.advanceUntilIdle()
-
-            assertRecords(
-                records = underTest.userRecords.value,
-                userIds = listOf(0, 1, 2),
-                selectedUserIndex = 0,
-                includeGuest = false,
-                expectedActions =
-                    listOf(
-                        UserActionModel.ENTER_GUEST_MODE,
-                        UserActionModel.ADD_USER,
-                        UserActionModel.ADD_SUPERVISED_USER,
-                        UserActionModel.NAVIGATE_TO_USER_MANAGEMENT,
-                    ),
-            )
-        }
-
-    @Test
-    fun selectedUserRecord() =
-        runBlocking(IMMEDIATE) {
-            val userInfos = createUserInfos(count = 3, includeGuest = true)
-            userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = true))
-            userRepository.setUserInfos(userInfos)
-            userRepository.setSelectedUserInfo(userInfos[0])
-            keyguardRepository.setKeyguardShowing(false)
-
-            assertRecordForUser(
-                record = underTest.selectedUserRecord.value,
-                id = 0,
-                hasPicture = true,
-                isCurrent = true,
-                isSwitchToEnabled = true,
-            )
-        }
-
-    private fun assertUsers(
-        models: List<UserModel>?,
-        count: Int,
-        selectedIndex: Int = 0,
-        includeGuest: Boolean = false,
-    ) {
-        checkNotNull(models)
-        assertThat(models.size).isEqualTo(count)
-        models.forEachIndexed { index, model ->
-            assertUser(
-                model = model,
-                id = index,
-                isSelected = index == selectedIndex,
-                isGuest = includeGuest && index == count - 1
-            )
-        }
-    }
-
-    private fun assertUser(
-        model: UserModel?,
-        id: Int,
-        isSelected: Boolean = false,
-        isGuest: Boolean = false,
-    ) {
-        checkNotNull(model)
-        assertThat(model.id).isEqualTo(id)
-        assertThat(model.name).isEqualTo(Text.Loaded(if (isGuest) "guest" else "user_$id"))
-        assertThat(model.isSelected).isEqualTo(isSelected)
-        assertThat(model.isSelectable).isTrue()
-        assertThat(model.isGuest).isEqualTo(isGuest)
-    }
-
-    private fun assertRecords(
-        records: List<UserRecord>,
-        userIds: List<Int>,
-        selectedUserIndex: Int = 0,
-        includeGuest: Boolean = false,
-        expectedActions: List<UserActionModel> = emptyList(),
-    ) {
-        assertThat(records.size >= userIds.size).isTrue()
-        userIds.indices.forEach { userIndex ->
-            val record = records[userIndex]
-            assertThat(record.info).isNotNull()
-            val isGuest = includeGuest && userIndex == userIds.size - 1
-            assertRecordForUser(
-                record = record,
-                id = userIds[userIndex],
-                hasPicture = !isGuest,
-                isCurrent = userIndex == selectedUserIndex,
-                isGuest = isGuest,
-                isSwitchToEnabled = true,
-            )
-        }
-
-        assertThat(records.size - userIds.size).isEqualTo(expectedActions.size)
-        (userIds.size until userIds.size + expectedActions.size).forEach { actionIndex ->
-            val record = records[actionIndex]
-            assertThat(record.info).isNull()
-            assertRecordForAction(
-                record = record,
-                type = expectedActions[actionIndex - userIds.size],
-            )
-        }
-    }
-
-    private fun assertRecordForUser(
-        record: UserRecord?,
-        id: Int? = null,
-        hasPicture: Boolean = false,
-        isCurrent: Boolean = false,
-        isGuest: Boolean = false,
-        isSwitchToEnabled: Boolean = false,
-    ) {
-        checkNotNull(record)
-        assertThat(record.info?.id).isEqualTo(id)
-        assertThat(record.picture != null).isEqualTo(hasPicture)
-        assertThat(record.isCurrent).isEqualTo(isCurrent)
-        assertThat(record.isGuest).isEqualTo(isGuest)
-        assertThat(record.isSwitchToEnabled).isEqualTo(isSwitchToEnabled)
-    }
-
-    private fun assertRecordForAction(
-        record: UserRecord,
-        type: UserActionModel,
-    ) {
-        assertThat(record.isGuest).isEqualTo(type == UserActionModel.ENTER_GUEST_MODE)
-        assertThat(record.isAddUser).isEqualTo(type == UserActionModel.ADD_USER)
-        assertThat(record.isAddSupervisedUser)
-            .isEqualTo(type == UserActionModel.ADD_SUPERVISED_USER)
-    }
-
-    private fun createUserInfos(
-        count: Int,
-        includeGuest: Boolean,
-    ): List<UserInfo> {
-        return (0 until count).map { index ->
-            val isGuest = includeGuest && index == count - 1
-            createUserInfo(
-                id = index,
-                name =
-                    if (isGuest) {
-                        "guest"
-                    } else {
-                        "user_$index"
-                    },
-                isPrimary = !isGuest && index == 0,
-                isGuest = isGuest,
-            )
-        }
-    }
-
-    private fun createUserInfo(
-        id: Int,
-        name: String,
-        isPrimary: Boolean = false,
-        isGuest: Boolean = false,
-    ): UserInfo {
-        return UserInfo(
-            id,
-            name,
-            /* iconPath= */ "",
-            /* flags= */ if (isPrimary) {
-                UserInfo.FLAG_PRIMARY or UserInfo.FLAG_ADMIN
-            } else {
-                0
-            },
-            if (isGuest) {
-                UserManager.USER_TYPE_FULL_GUEST
-            } else {
-                UserManager.USER_TYPE_FULL_SYSTEM
-            },
-        )
-    }
-
-    companion object {
-        private val IMMEDIATE = Dispatchers.Main.immediate
-        private val ICON = Bitmap.createBitmap(1, 1, Bitmap.Config.ARGB_8888)
-        private val GUEST_ICON: Drawable = mock()
-        private const val SUPERVISED_USER_CREATION_APP_PACKAGE = "supervisedUserCreation"
-    }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/UserInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/UserInteractorTest.kt
index 58f5531..463f517 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/UserInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/UserInteractorTest.kt
@@ -19,51 +19,90 @@
 
 import android.app.ActivityManager
 import android.app.admin.DevicePolicyManager
+import android.content.Intent
+import android.content.pm.UserInfo
+import android.graphics.Bitmap
+import android.graphics.drawable.Drawable
+import android.os.UserHandle
 import android.os.UserManager
+import android.provider.Settings
+import androidx.test.filters.SmallTest
+import com.android.internal.R.drawable.ic_account_circle
 import com.android.internal.logging.UiEventLogger
 import com.android.systemui.GuestResetOrExitSessionReceiver
 import com.android.systemui.GuestResumeSessionReceiver
+import com.android.systemui.R
 import com.android.systemui.SysuiTestCase
-import com.android.systemui.flags.FakeFeatureFlags
-import com.android.systemui.flags.Flags
+import com.android.systemui.common.shared.model.Text
 import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
 import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
 import com.android.systemui.plugins.ActivityStarter
 import com.android.systemui.qs.user.UserSwitchDialogController
 import com.android.systemui.statusbar.policy.DeviceProvisionedController
-import com.android.systemui.statusbar.policy.UserSwitcherController
 import com.android.systemui.telephony.data.repository.FakeTelephonyRepository
 import com.android.systemui.telephony.domain.interactor.TelephonyInteractor
+import com.android.systemui.user.data.model.UserSwitcherSettingsModel
 import com.android.systemui.user.data.repository.FakeUserRepository
+import com.android.systemui.user.data.source.UserRecord
+import com.android.systemui.user.domain.model.ShowDialogRequestModel
+import com.android.systemui.user.shared.model.UserActionModel
+import com.android.systemui.user.shared.model.UserModel
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.eq
+import com.android.systemui.util.mockito.kotlinArgumentCaptor
+import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.whenever
+import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.flow.launchIn
+import kotlinx.coroutines.flow.onEach
+import kotlinx.coroutines.runBlocking
 import kotlinx.coroutines.test.TestCoroutineScope
+import kotlinx.coroutines.test.advanceUntilIdle
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+import org.mockito.ArgumentMatchers.anyBoolean
+import org.mockito.ArgumentMatchers.anyInt
 import org.mockito.Mock
+import org.mockito.Mockito.never
+import org.mockito.Mockito.verify
 import org.mockito.MockitoAnnotations
 
-abstract class UserInteractorTest : SysuiTestCase() {
+@SmallTest
+@RunWith(JUnit4::class)
+class UserInteractorTest : SysuiTestCase() {
 
-    @Mock protected lateinit var controller: UserSwitcherController
-    @Mock protected lateinit var activityStarter: ActivityStarter
-    @Mock protected lateinit var manager: UserManager
-    @Mock protected lateinit var activityManager: ActivityManager
-    @Mock protected lateinit var deviceProvisionedController: DeviceProvisionedController
-    @Mock protected lateinit var devicePolicyManager: DevicePolicyManager
-    @Mock protected lateinit var uiEventLogger: UiEventLogger
-    @Mock protected lateinit var dialogShower: UserSwitchDialogController.DialogShower
+    @Mock private lateinit var activityStarter: ActivityStarter
+    @Mock private lateinit var manager: UserManager
+    @Mock private lateinit var activityManager: ActivityManager
+    @Mock private lateinit var deviceProvisionedController: DeviceProvisionedController
+    @Mock private lateinit var devicePolicyManager: DevicePolicyManager
+    @Mock private lateinit var uiEventLogger: UiEventLogger
+    @Mock private lateinit var dialogShower: UserSwitchDialogController.DialogShower
     @Mock private lateinit var resumeSessionReceiver: GuestResumeSessionReceiver
     @Mock private lateinit var resetOrExitSessionReceiver: GuestResetOrExitSessionReceiver
 
-    protected lateinit var underTest: UserInteractor
+    private lateinit var underTest: UserInteractor
 
-    protected lateinit var testCoroutineScope: TestCoroutineScope
-    protected lateinit var userRepository: FakeUserRepository
-    protected lateinit var keyguardRepository: FakeKeyguardRepository
-    protected lateinit var telephonyRepository: FakeTelephonyRepository
+    private lateinit var testCoroutineScope: TestCoroutineScope
+    private lateinit var userRepository: FakeUserRepository
+    private lateinit var keyguardRepository: FakeKeyguardRepository
+    private lateinit var telephonyRepository: FakeTelephonyRepository
 
-    abstract fun isRefactored(): Boolean
-
-    open fun setUp() {
+    @Before
+    fun setUp() {
         MockitoAnnotations.initMocks(this)
+        whenever(manager.getUserIcon(anyInt())).thenReturn(ICON)
+        whenever(manager.canAddMoreUsers(any())).thenReturn(true)
+
+        overrideResource(R.drawable.ic_account_circle, GUEST_ICON)
+        overrideResource(R.dimen.max_avatar_size, 10)
+        overrideResource(
+            com.android.internal.R.string.config_supervisedUserCreationPackage,
+            SUPERVISED_USER_CREATION_APP_PACKAGE,
+        )
 
         userRepository = FakeUserRepository()
         keyguardRepository = FakeKeyguardRepository()
@@ -79,16 +118,11 @@
             UserInteractor(
                 applicationContext = context,
                 repository = userRepository,
-                controller = controller,
                 activityStarter = activityStarter,
                 keyguardInteractor =
                     KeyguardInteractor(
                         repository = keyguardRepository,
                     ),
-                featureFlags =
-                    FakeFeatureFlags().apply {
-                        set(Flags.USER_INTERACTOR_AND_REPO_USE_CONTROLLER, !isRefactored())
-                    },
                 manager = manager,
                 applicationScope = testCoroutineScope,
                 telephonyInteractor =
@@ -117,7 +151,708 @@
             )
     }
 
+    @Test
+    fun `onRecordSelected - user`() =
+        runBlocking(IMMEDIATE) {
+            val userInfos = createUserInfos(count = 3, includeGuest = false)
+            userRepository.setUserInfos(userInfos)
+            userRepository.setSelectedUserInfo(userInfos[0])
+            userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = true))
+
+            underTest.onRecordSelected(UserRecord(info = userInfos[1]), dialogShower)
+
+            verify(dialogShower).dismiss()
+            verify(activityManager).switchUser(userInfos[1].id)
+            Unit
+        }
+
+    @Test
+    fun `onRecordSelected - switch to guest user`() =
+        runBlocking(IMMEDIATE) {
+            val userInfos = createUserInfos(count = 3, includeGuest = true)
+            userRepository.setUserInfos(userInfos)
+            userRepository.setSelectedUserInfo(userInfos[0])
+            userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = true))
+
+            underTest.onRecordSelected(UserRecord(info = userInfos.last()))
+
+            verify(activityManager).switchUser(userInfos.last().id)
+            Unit
+        }
+
+    @Test
+    fun `onRecordSelected - enter guest mode`() =
+        runBlocking(IMMEDIATE) {
+            val userInfos = createUserInfos(count = 3, includeGuest = false)
+            userRepository.setUserInfos(userInfos)
+            userRepository.setSelectedUserInfo(userInfos[0])
+            userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = true))
+            val guestUserInfo = createUserInfo(id = 1337, name = "guest", isGuest = true)
+            whenever(manager.createGuest(any())).thenReturn(guestUserInfo)
+
+            underTest.onRecordSelected(UserRecord(isGuest = true), dialogShower)
+
+            verify(dialogShower).dismiss()
+            verify(manager).createGuest(any())
+            Unit
+        }
+
+    @Test
+    fun `onRecordSelected - action`() =
+        runBlocking(IMMEDIATE) {
+            val userInfos = createUserInfos(count = 3, includeGuest = true)
+            userRepository.setUserInfos(userInfos)
+            userRepository.setSelectedUserInfo(userInfos[0])
+            userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = true))
+
+            underTest.onRecordSelected(UserRecord(isAddSupervisedUser = true), dialogShower)
+
+            verify(dialogShower, never()).dismiss()
+            verify(activityStarter).startActivity(any(), anyBoolean())
+        }
+
+    @Test
+    fun `users - switcher enabled`() =
+        runBlocking(IMMEDIATE) {
+            val userInfos = createUserInfos(count = 3, includeGuest = true)
+            userRepository.setUserInfos(userInfos)
+            userRepository.setSelectedUserInfo(userInfos[0])
+            userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = true))
+
+            var value: List<UserModel>? = null
+            val job = underTest.users.onEach { value = it }.launchIn(this)
+            assertUsers(models = value, count = 3, includeGuest = true)
+
+            job.cancel()
+        }
+
+    @Test
+    fun `users - switches to second user`() =
+        runBlocking(IMMEDIATE) {
+            val userInfos = createUserInfos(count = 2, includeGuest = false)
+            userRepository.setUserInfos(userInfos)
+            userRepository.setSelectedUserInfo(userInfos[0])
+            userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = true))
+
+            var value: List<UserModel>? = null
+            val job = underTest.users.onEach { value = it }.launchIn(this)
+            userRepository.setSelectedUserInfo(userInfos[1])
+
+            assertUsers(models = value, count = 2, selectedIndex = 1)
+            job.cancel()
+        }
+
+    @Test
+    fun `users - switcher not enabled`() =
+        runBlocking(IMMEDIATE) {
+            val userInfos = createUserInfos(count = 2, includeGuest = false)
+            userRepository.setUserInfos(userInfos)
+            userRepository.setSelectedUserInfo(userInfos[0])
+            userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = false))
+
+            var value: List<UserModel>? = null
+            val job = underTest.users.onEach { value = it }.launchIn(this)
+            assertUsers(models = value, count = 1)
+
+            job.cancel()
+        }
+
+    @Test
+    fun selectedUser() =
+        runBlocking(IMMEDIATE) {
+            val userInfos = createUserInfos(count = 2, includeGuest = false)
+            userRepository.setUserInfos(userInfos)
+            userRepository.setSelectedUserInfo(userInfos[0])
+            userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = true))
+
+            var value: UserModel? = null
+            val job = underTest.selectedUser.onEach { value = it }.launchIn(this)
+            assertUser(value, id = 0, isSelected = true)
+
+            userRepository.setSelectedUserInfo(userInfos[1])
+            assertUser(value, id = 1, isSelected = true)
+
+            job.cancel()
+        }
+
+    @Test
+    fun `actions - device unlocked`() =
+        runBlocking(IMMEDIATE) {
+            val userInfos = createUserInfos(count = 2, includeGuest = false)
+
+            userRepository.setUserInfos(userInfos)
+            userRepository.setSelectedUserInfo(userInfos[0])
+            userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = true))
+            keyguardRepository.setKeyguardShowing(false)
+            var value: List<UserActionModel>? = null
+            val job = underTest.actions.onEach { value = it }.launchIn(this)
+
+            assertThat(value)
+                .isEqualTo(
+                    listOf(
+                        UserActionModel.ENTER_GUEST_MODE,
+                        UserActionModel.ADD_USER,
+                        UserActionModel.ADD_SUPERVISED_USER,
+                        UserActionModel.NAVIGATE_TO_USER_MANAGEMENT,
+                    )
+                )
+
+            job.cancel()
+        }
+
+    @Test
+    fun `actions - device unlocked user not primary - empty list`() =
+        runBlocking(IMMEDIATE) {
+            val userInfos = createUserInfos(count = 2, includeGuest = false)
+            userRepository.setUserInfos(userInfos)
+            userRepository.setSelectedUserInfo(userInfos[1])
+            userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = true))
+            keyguardRepository.setKeyguardShowing(false)
+            var value: List<UserActionModel>? = null
+            val job = underTest.actions.onEach { value = it }.launchIn(this)
+
+            assertThat(value).isEqualTo(emptyList<UserActionModel>())
+
+            job.cancel()
+        }
+
+    @Test
+    fun `actions - device unlocked user is guest - empty list`() =
+        runBlocking(IMMEDIATE) {
+            val userInfos = createUserInfos(count = 2, includeGuest = true)
+            assertThat(userInfos[1].isGuest).isTrue()
+            userRepository.setUserInfos(userInfos)
+            userRepository.setSelectedUserInfo(userInfos[1])
+            userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = true))
+            keyguardRepository.setKeyguardShowing(false)
+            var value: List<UserActionModel>? = null
+            val job = underTest.actions.onEach { value = it }.launchIn(this)
+
+            assertThat(value).isEqualTo(emptyList<UserActionModel>())
+
+            job.cancel()
+        }
+
+    @Test
+    fun `actions - device locked add from lockscreen set - full list`() =
+        runBlocking(IMMEDIATE) {
+            val userInfos = createUserInfos(count = 2, includeGuest = false)
+            userRepository.setUserInfos(userInfos)
+            userRepository.setSelectedUserInfo(userInfos[0])
+            userRepository.setSettings(
+                UserSwitcherSettingsModel(
+                    isUserSwitcherEnabled = true,
+                    isAddUsersFromLockscreen = true,
+                )
+            )
+            keyguardRepository.setKeyguardShowing(false)
+            var value: List<UserActionModel>? = null
+            val job = underTest.actions.onEach { value = it }.launchIn(this)
+
+            assertThat(value)
+                .isEqualTo(
+                    listOf(
+                        UserActionModel.ENTER_GUEST_MODE,
+                        UserActionModel.ADD_USER,
+                        UserActionModel.ADD_SUPERVISED_USER,
+                        UserActionModel.NAVIGATE_TO_USER_MANAGEMENT,
+                    )
+                )
+
+            job.cancel()
+        }
+
+    @Test
+    fun `actions - device locked - only guest action and manage user is shown`() =
+        runBlocking(IMMEDIATE) {
+            val userInfos = createUserInfos(count = 2, includeGuest = false)
+            userRepository.setUserInfos(userInfos)
+            userRepository.setSelectedUserInfo(userInfos[0])
+            userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = true))
+            keyguardRepository.setKeyguardShowing(true)
+            var value: List<UserActionModel>? = null
+            val job = underTest.actions.onEach { value = it }.launchIn(this)
+
+            assertThat(value)
+                .isEqualTo(
+                    listOf(
+                        UserActionModel.ENTER_GUEST_MODE,
+                        UserActionModel.NAVIGATE_TO_USER_MANAGEMENT
+                    )
+                )
+
+            job.cancel()
+        }
+
+    @Test
+    fun `executeAction - add user - dialog shown`() =
+        runBlocking(IMMEDIATE) {
+            val userInfos = createUserInfos(count = 2, includeGuest = false)
+            userRepository.setUserInfos(userInfos)
+            userRepository.setSelectedUserInfo(userInfos[0])
+            keyguardRepository.setKeyguardShowing(false)
+            var dialogRequest: ShowDialogRequestModel? = null
+            val job = underTest.dialogShowRequests.onEach { dialogRequest = it }.launchIn(this)
+            val dialogShower: UserSwitchDialogController.DialogShower = mock()
+
+            underTest.executeAction(UserActionModel.ADD_USER, dialogShower)
+            assertThat(dialogRequest)
+                .isEqualTo(
+                    ShowDialogRequestModel.ShowAddUserDialog(
+                        userHandle = userInfos[0].userHandle,
+                        isKeyguardShowing = false,
+                        showEphemeralMessage = false,
+                        dialogShower = dialogShower,
+                    )
+                )
+
+            underTest.onDialogShown()
+            assertThat(dialogRequest).isNull()
+
+            job.cancel()
+        }
+
+    @Test
+    fun `executeAction - add supervised user - starts activity`() =
+        runBlocking(IMMEDIATE) {
+            underTest.executeAction(UserActionModel.ADD_SUPERVISED_USER)
+
+            val intentCaptor = kotlinArgumentCaptor<Intent>()
+            verify(activityStarter).startActivity(intentCaptor.capture(), eq(true))
+            assertThat(intentCaptor.value.action)
+                .isEqualTo(UserManager.ACTION_CREATE_SUPERVISED_USER)
+            assertThat(intentCaptor.value.`package`).isEqualTo(SUPERVISED_USER_CREATION_APP_PACKAGE)
+        }
+
+    @Test
+    fun `executeAction - navigate to manage users`() =
+        runBlocking(IMMEDIATE) {
+            underTest.executeAction(UserActionModel.NAVIGATE_TO_USER_MANAGEMENT)
+
+            val intentCaptor = kotlinArgumentCaptor<Intent>()
+            verify(activityStarter).startActivity(intentCaptor.capture(), eq(true))
+            assertThat(intentCaptor.value.action).isEqualTo(Settings.ACTION_USER_SETTINGS)
+        }
+
+    @Test
+    fun `executeAction - guest mode`() =
+        runBlocking(IMMEDIATE) {
+            val userInfos = createUserInfos(count = 2, includeGuest = false)
+            userRepository.setUserInfos(userInfos)
+            userRepository.setSelectedUserInfo(userInfos[0])
+            userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = true))
+            val guestUserInfo = createUserInfo(id = 1337, name = "guest", isGuest = true)
+            whenever(manager.createGuest(any())).thenReturn(guestUserInfo)
+            val dialogRequests = mutableListOf<ShowDialogRequestModel?>()
+            val showDialogsJob =
+                underTest.dialogShowRequests
+                    .onEach {
+                        dialogRequests.add(it)
+                        if (it != null) {
+                            underTest.onDialogShown()
+                        }
+                    }
+                    .launchIn(this)
+            val dismissDialogsJob =
+                underTest.dialogDismissRequests
+                    .onEach {
+                        if (it != null) {
+                            underTest.onDialogDismissed()
+                        }
+                    }
+                    .launchIn(this)
+
+            underTest.executeAction(UserActionModel.ENTER_GUEST_MODE)
+
+            assertThat(dialogRequests)
+                .contains(
+                    ShowDialogRequestModel.ShowUserCreationDialog(isGuest = true),
+                )
+            verify(activityManager).switchUser(guestUserInfo.id)
+
+            showDialogsJob.cancel()
+            dismissDialogsJob.cancel()
+        }
+
+    @Test
+    fun `selectUser - already selected guest re-selected - exit guest dialog`() =
+        runBlocking(IMMEDIATE) {
+            val userInfos = createUserInfos(count = 2, includeGuest = true)
+            val guestUserInfo = userInfos[1]
+            assertThat(guestUserInfo.isGuest).isTrue()
+            userRepository.setUserInfos(userInfos)
+            userRepository.setSelectedUserInfo(guestUserInfo)
+            userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = true))
+            var dialogRequest: ShowDialogRequestModel? = null
+            val job = underTest.dialogShowRequests.onEach { dialogRequest = it }.launchIn(this)
+
+            underTest.selectUser(
+                newlySelectedUserId = guestUserInfo.id,
+                dialogShower = dialogShower,
+            )
+
+            assertThat(dialogRequest)
+                .isInstanceOf(ShowDialogRequestModel.ShowExitGuestDialog::class.java)
+            verify(dialogShower, never()).dismiss()
+            job.cancel()
+        }
+
+    @Test
+    fun `selectUser - currently guest non-guest selected - exit guest dialog`() =
+        runBlocking(IMMEDIATE) {
+            val userInfos = createUserInfos(count = 2, includeGuest = true)
+            val guestUserInfo = userInfos[1]
+            assertThat(guestUserInfo.isGuest).isTrue()
+            userRepository.setUserInfos(userInfos)
+            userRepository.setSelectedUserInfo(guestUserInfo)
+            userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = true))
+            var dialogRequest: ShowDialogRequestModel? = null
+            val job = underTest.dialogShowRequests.onEach { dialogRequest = it }.launchIn(this)
+
+            underTest.selectUser(newlySelectedUserId = userInfos[0].id, dialogShower = dialogShower)
+
+            assertThat(dialogRequest)
+                .isInstanceOf(ShowDialogRequestModel.ShowExitGuestDialog::class.java)
+            verify(dialogShower, never()).dismiss()
+            job.cancel()
+        }
+
+    @Test
+    fun `selectUser - not currently guest - switches users`() =
+        runBlocking(IMMEDIATE) {
+            val userInfos = createUserInfos(count = 2, includeGuest = false)
+            userRepository.setUserInfos(userInfos)
+            userRepository.setSelectedUserInfo(userInfos[0])
+            userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = true))
+            var dialogRequest: ShowDialogRequestModel? = null
+            val job = underTest.dialogShowRequests.onEach { dialogRequest = it }.launchIn(this)
+
+            underTest.selectUser(newlySelectedUserId = userInfos[1].id, dialogShower = dialogShower)
+
+            assertThat(dialogRequest).isNull()
+            verify(activityManager).switchUser(userInfos[1].id)
+            verify(dialogShower).dismiss()
+            job.cancel()
+        }
+
+    @Test
+    fun `Telephony call state changes - refreshes users`() =
+        runBlocking(IMMEDIATE) {
+            val refreshUsersCallCount = userRepository.refreshUsersCallCount
+
+            telephonyRepository.setCallState(1)
+
+            assertThat(userRepository.refreshUsersCallCount).isEqualTo(refreshUsersCallCount + 1)
+        }
+
+    @Test
+    fun `User switched broadcast`() =
+        runBlocking(IMMEDIATE) {
+            val userInfos = createUserInfos(count = 2, includeGuest = false)
+            userRepository.setUserInfos(userInfos)
+            userRepository.setSelectedUserInfo(userInfos[0])
+            userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = true))
+            val callback1: UserInteractor.UserCallback = mock()
+            val callback2: UserInteractor.UserCallback = mock()
+            underTest.addCallback(callback1)
+            underTest.addCallback(callback2)
+            val refreshUsersCallCount = userRepository.refreshUsersCallCount
+
+            userRepository.setSelectedUserInfo(userInfos[1])
+            fakeBroadcastDispatcher.registeredReceivers.forEach {
+                it.onReceive(
+                    context,
+                    Intent(Intent.ACTION_USER_SWITCHED)
+                        .putExtra(Intent.EXTRA_USER_HANDLE, userInfos[1].id),
+                )
+            }
+
+            verify(callback1).onUserStateChanged()
+            verify(callback2).onUserStateChanged()
+            assertThat(userRepository.secondaryUserId).isEqualTo(userInfos[1].id)
+            assertThat(userRepository.refreshUsersCallCount).isEqualTo(refreshUsersCallCount + 1)
+        }
+
+    @Test
+    fun `User info changed broadcast`() =
+        runBlocking(IMMEDIATE) {
+            val userInfos = createUserInfos(count = 2, includeGuest = false)
+            userRepository.setUserInfos(userInfos)
+            userRepository.setSelectedUserInfo(userInfos[0])
+            val refreshUsersCallCount = userRepository.refreshUsersCallCount
+
+            fakeBroadcastDispatcher.registeredReceivers.forEach {
+                it.onReceive(
+                    context,
+                    Intent(Intent.ACTION_USER_INFO_CHANGED),
+                )
+            }
+
+            assertThat(userRepository.refreshUsersCallCount).isEqualTo(refreshUsersCallCount + 1)
+        }
+
+    @Test
+    fun `System user unlocked broadcast - refresh users`() =
+        runBlocking(IMMEDIATE) {
+            val userInfos = createUserInfos(count = 2, includeGuest = false)
+            userRepository.setUserInfos(userInfos)
+            userRepository.setSelectedUserInfo(userInfos[0])
+            val refreshUsersCallCount = userRepository.refreshUsersCallCount
+
+            fakeBroadcastDispatcher.registeredReceivers.forEach {
+                it.onReceive(
+                    context,
+                    Intent(Intent.ACTION_USER_UNLOCKED)
+                        .putExtra(Intent.EXTRA_USER_HANDLE, UserHandle.USER_SYSTEM),
+                )
+            }
+
+            assertThat(userRepository.refreshUsersCallCount).isEqualTo(refreshUsersCallCount + 1)
+        }
+
+    @Test
+    fun `Non-system user unlocked broadcast - do not refresh users`() =
+        runBlocking(IMMEDIATE) {
+            val userInfos = createUserInfos(count = 2, includeGuest = false)
+            userRepository.setUserInfos(userInfos)
+            userRepository.setSelectedUserInfo(userInfos[0])
+            val refreshUsersCallCount = userRepository.refreshUsersCallCount
+
+            fakeBroadcastDispatcher.registeredReceivers.forEach {
+                it.onReceive(
+                    context,
+                    Intent(Intent.ACTION_USER_UNLOCKED).putExtra(Intent.EXTRA_USER_HANDLE, 1337),
+                )
+            }
+
+            assertThat(userRepository.refreshUsersCallCount).isEqualTo(refreshUsersCallCount)
+        }
+
+    @Test
+    fun userRecords() =
+        runBlocking(IMMEDIATE) {
+            val userInfos = createUserInfos(count = 3, includeGuest = false)
+            userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = true))
+            userRepository.setUserInfos(userInfos)
+            userRepository.setSelectedUserInfo(userInfos[0])
+            keyguardRepository.setKeyguardShowing(false)
+
+            testCoroutineScope.advanceUntilIdle()
+
+            assertRecords(
+                records = underTest.userRecords.value,
+                userIds = listOf(0, 1, 2),
+                selectedUserIndex = 0,
+                includeGuest = false,
+                expectedActions =
+                    listOf(
+                        UserActionModel.ENTER_GUEST_MODE,
+                        UserActionModel.ADD_USER,
+                        UserActionModel.ADD_SUPERVISED_USER,
+                        UserActionModel.NAVIGATE_TO_USER_MANAGEMENT,
+                    ),
+            )
+        }
+
+    @Test
+    fun selectedUserRecord() =
+        runBlocking(IMMEDIATE) {
+            val userInfos = createUserInfos(count = 3, includeGuest = true)
+            userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = true))
+            userRepository.setUserInfos(userInfos)
+            userRepository.setSelectedUserInfo(userInfos[0])
+            keyguardRepository.setKeyguardShowing(false)
+
+            assertRecordForUser(
+                record = underTest.selectedUserRecord.value,
+                id = 0,
+                hasPicture = true,
+                isCurrent = true,
+                isSwitchToEnabled = true,
+            )
+        }
+
+    @Test
+    fun `users - secondary user - no guest user`() =
+        runBlocking(IMMEDIATE) {
+            val userInfos = createUserInfos(count = 3, includeGuest = true)
+            userRepository.setUserInfos(userInfos)
+            userRepository.setSelectedUserInfo(userInfos[1])
+            userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = true))
+
+            var res: List<UserModel>? = null
+            val job = underTest.users.onEach { res = it }.launchIn(this)
+            assertThat(res?.size == 2).isTrue()
+            assertThat(res?.find { it.isGuest }).isNull()
+            job.cancel()
+        }
+
+    @Test
+    fun `users - secondary user - no guest action`() =
+        runBlocking(IMMEDIATE) {
+            val userInfos = createUserInfos(count = 3, includeGuest = true)
+            userRepository.setUserInfos(userInfos)
+            userRepository.setSelectedUserInfo(userInfos[1])
+            userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = true))
+
+            var res: List<UserActionModel>? = null
+            val job = underTest.actions.onEach { res = it }.launchIn(this)
+            assertThat(res?.find { it == UserActionModel.ENTER_GUEST_MODE }).isNull()
+            job.cancel()
+        }
+
+    @Test
+    fun `users - secondary user - no guest user record`() =
+        runBlocking(IMMEDIATE) {
+            val userInfos = createUserInfos(count = 3, includeGuest = true)
+            userRepository.setUserInfos(userInfos)
+            userRepository.setSelectedUserInfo(userInfos[1])
+            userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = true))
+
+            var res: List<UserRecord>? = null
+            val job = underTest.userRecords.onEach { res = it }.launchIn(this)
+            assertThat(res?.find { it.isGuest }).isNull()
+            job.cancel()
+        }
+
+    private fun assertUsers(
+        models: List<UserModel>?,
+        count: Int,
+        selectedIndex: Int = 0,
+        includeGuest: Boolean = false,
+    ) {
+        checkNotNull(models)
+        assertThat(models.size).isEqualTo(count)
+        models.forEachIndexed { index, model ->
+            assertUser(
+                model = model,
+                id = index,
+                isSelected = index == selectedIndex,
+                isGuest = includeGuest && index == count - 1
+            )
+        }
+    }
+
+    private fun assertUser(
+        model: UserModel?,
+        id: Int,
+        isSelected: Boolean = false,
+        isGuest: Boolean = false,
+    ) {
+        checkNotNull(model)
+        assertThat(model.id).isEqualTo(id)
+        assertThat(model.name).isEqualTo(Text.Loaded(if (isGuest) "guest" else "user_$id"))
+        assertThat(model.isSelected).isEqualTo(isSelected)
+        assertThat(model.isSelectable).isTrue()
+        assertThat(model.isGuest).isEqualTo(isGuest)
+    }
+
+    private fun assertRecords(
+        records: List<UserRecord>,
+        userIds: List<Int>,
+        selectedUserIndex: Int = 0,
+        includeGuest: Boolean = false,
+        expectedActions: List<UserActionModel> = emptyList(),
+    ) {
+        assertThat(records.size >= userIds.size).isTrue()
+        userIds.indices.forEach { userIndex ->
+            val record = records[userIndex]
+            assertThat(record.info).isNotNull()
+            val isGuest = includeGuest && userIndex == userIds.size - 1
+            assertRecordForUser(
+                record = record,
+                id = userIds[userIndex],
+                hasPicture = !isGuest,
+                isCurrent = userIndex == selectedUserIndex,
+                isGuest = isGuest,
+                isSwitchToEnabled = true,
+            )
+        }
+
+        assertThat(records.size - userIds.size).isEqualTo(expectedActions.size)
+        (userIds.size until userIds.size + expectedActions.size).forEach { actionIndex ->
+            val record = records[actionIndex]
+            assertThat(record.info).isNull()
+            assertRecordForAction(
+                record = record,
+                type = expectedActions[actionIndex - userIds.size],
+            )
+        }
+    }
+
+    private fun assertRecordForUser(
+        record: UserRecord?,
+        id: Int? = null,
+        hasPicture: Boolean = false,
+        isCurrent: Boolean = false,
+        isGuest: Boolean = false,
+        isSwitchToEnabled: Boolean = false,
+    ) {
+        checkNotNull(record)
+        assertThat(record.info?.id).isEqualTo(id)
+        assertThat(record.picture != null).isEqualTo(hasPicture)
+        assertThat(record.isCurrent).isEqualTo(isCurrent)
+        assertThat(record.isGuest).isEqualTo(isGuest)
+        assertThat(record.isSwitchToEnabled).isEqualTo(isSwitchToEnabled)
+    }
+
+    private fun assertRecordForAction(
+        record: UserRecord,
+        type: UserActionModel,
+    ) {
+        assertThat(record.isGuest).isEqualTo(type == UserActionModel.ENTER_GUEST_MODE)
+        assertThat(record.isAddUser).isEqualTo(type == UserActionModel.ADD_USER)
+        assertThat(record.isAddSupervisedUser)
+            .isEqualTo(type == UserActionModel.ADD_SUPERVISED_USER)
+    }
+
+    private fun createUserInfos(
+        count: Int,
+        includeGuest: Boolean,
+    ): List<UserInfo> {
+        return (0 until count).map { index ->
+            val isGuest = includeGuest && index == count - 1
+            createUserInfo(
+                id = index,
+                name =
+                    if (isGuest) {
+                        "guest"
+                    } else {
+                        "user_$index"
+                    },
+                isPrimary = !isGuest && index == 0,
+                isGuest = isGuest,
+            )
+        }
+    }
+
+    private fun createUserInfo(
+        id: Int,
+        name: String,
+        isPrimary: Boolean = false,
+        isGuest: Boolean = false,
+    ): UserInfo {
+        return UserInfo(
+            id,
+            name,
+            /* iconPath= */ "",
+            /* flags= */ if (isPrimary) {
+                UserInfo.FLAG_PRIMARY or UserInfo.FLAG_ADMIN
+            } else {
+                0
+            },
+            if (isGuest) {
+                UserManager.USER_TYPE_FULL_GUEST
+            } else {
+                UserManager.USER_TYPE_FULL_SYSTEM
+            },
+        )
+    }
+
     companion object {
         private val IMMEDIATE = Dispatchers.Main.immediate
+        private val ICON = Bitmap.createBitmap(1, 1, Bitmap.Config.ARGB_8888)
+        private val GUEST_ICON: Drawable = mock()
+        private const val SUPERVISED_USER_CREATION_APP_PACKAGE = "supervisedUserCreation"
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/UserInteractorUnrefactoredTest.kt b/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/UserInteractorUnrefactoredTest.kt
deleted file mode 100644
index 6a17c8d..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/UserInteractorUnrefactoredTest.kt
+++ /dev/null
@@ -1,174 +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.user.domain.interactor
-
-import androidx.test.filters.SmallTest
-import com.android.systemui.user.shared.model.UserActionModel
-import com.android.systemui.util.mockito.any
-import com.android.systemui.util.mockito.eq
-import com.android.systemui.util.mockito.nullable
-import com.google.common.truth.Truth.assertThat
-import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.flow.launchIn
-import kotlinx.coroutines.flow.onEach
-import kotlinx.coroutines.runBlocking
-import org.junit.Before
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.junit.runners.JUnit4
-import org.mockito.Mockito.anyBoolean
-import org.mockito.Mockito.verify
-
-@SmallTest
-@RunWith(JUnit4::class)
-open class UserInteractorUnrefactoredTest : UserInteractorTest() {
-
-    override fun isRefactored(): Boolean {
-        return false
-    }
-
-    @Before
-    override fun setUp() {
-        super.setUp()
-    }
-
-    @Test
-    fun `actions - not actionable when locked and locked - no actions`() =
-        runBlocking(IMMEDIATE) {
-            userRepository.setActions(UserActionModel.values().toList())
-            userRepository.setActionableWhenLocked(false)
-            keyguardRepository.setKeyguardShowing(true)
-
-            var actions: List<UserActionModel>? = null
-            val job = underTest.actions.onEach { actions = it }.launchIn(this)
-
-            assertThat(actions).isEmpty()
-            job.cancel()
-        }
-
-    @Test
-    fun `actions - not actionable when locked and not locked`() =
-        runBlocking(IMMEDIATE) {
-            setActions()
-            userRepository.setActionableWhenLocked(false)
-            keyguardRepository.setKeyguardShowing(false)
-
-            var actions: List<UserActionModel>? = null
-            val job = underTest.actions.onEach { actions = it }.launchIn(this)
-
-            assertThat(actions)
-                .isEqualTo(
-                    listOf(
-                        UserActionModel.ENTER_GUEST_MODE,
-                        UserActionModel.ADD_USER,
-                        UserActionModel.ADD_SUPERVISED_USER,
-                        UserActionModel.NAVIGATE_TO_USER_MANAGEMENT,
-                    )
-                )
-            job.cancel()
-        }
-
-    @Test
-    fun `actions - actionable when locked and not locked`() =
-        runBlocking(IMMEDIATE) {
-            setActions()
-            userRepository.setActionableWhenLocked(true)
-            keyguardRepository.setKeyguardShowing(false)
-
-            var actions: List<UserActionModel>? = null
-            val job = underTest.actions.onEach { actions = it }.launchIn(this)
-
-            assertThat(actions)
-                .isEqualTo(
-                    listOf(
-                        UserActionModel.ENTER_GUEST_MODE,
-                        UserActionModel.ADD_USER,
-                        UserActionModel.ADD_SUPERVISED_USER,
-                        UserActionModel.NAVIGATE_TO_USER_MANAGEMENT,
-                    )
-                )
-            job.cancel()
-        }
-
-    @Test
-    fun `actions - actionable when locked and locked`() =
-        runBlocking(IMMEDIATE) {
-            setActions()
-            userRepository.setActionableWhenLocked(true)
-            keyguardRepository.setKeyguardShowing(true)
-
-            var actions: List<UserActionModel>? = null
-            val job = underTest.actions.onEach { actions = it }.launchIn(this)
-
-            assertThat(actions)
-                .isEqualTo(
-                    listOf(
-                        UserActionModel.ENTER_GUEST_MODE,
-                        UserActionModel.ADD_USER,
-                        UserActionModel.ADD_SUPERVISED_USER,
-                        UserActionModel.NAVIGATE_TO_USER_MANAGEMENT,
-                    )
-                )
-            job.cancel()
-        }
-
-    @Test
-    fun selectUser() {
-        val userId = 3
-
-        underTest.selectUser(userId)
-
-        verify(controller).onUserSelected(eq(userId), nullable())
-    }
-
-    @Test
-    fun `executeAction - guest`() {
-        underTest.executeAction(UserActionModel.ENTER_GUEST_MODE)
-
-        verify(controller).createAndSwitchToGuestUser(nullable())
-    }
-
-    @Test
-    fun `executeAction - add user`() {
-        underTest.executeAction(UserActionModel.ADD_USER)
-
-        verify(controller).showAddUserDialog(nullable())
-    }
-
-    @Test
-    fun `executeAction - add supervised user`() {
-        underTest.executeAction(UserActionModel.ADD_SUPERVISED_USER)
-
-        verify(controller).startSupervisedUserActivity()
-    }
-
-    @Test
-    fun `executeAction - manage users`() {
-        underTest.executeAction(UserActionModel.NAVIGATE_TO_USER_MANAGEMENT)
-
-        verify(activityStarter).startActivity(any(), anyBoolean())
-    }
-
-    private fun setActions() {
-        userRepository.setActions(UserActionModel.values().toList())
-    }
-
-    companion object {
-        private val IMMEDIATE = Dispatchers.Main.immediate
-    }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/user/ui/viewmodel/UserSwitcherViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/user/ui/viewmodel/UserSwitcherViewModelTest.kt
index 116023a..db13680 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/user/ui/viewmodel/UserSwitcherViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/user/ui/viewmodel/UserSwitcherViewModelTest.kt
@@ -19,7 +19,7 @@
 
 import android.app.ActivityManager
 import android.app.admin.DevicePolicyManager
-import android.graphics.drawable.Drawable
+import android.content.pm.UserInfo
 import android.os.UserManager
 import androidx.test.filters.SmallTest
 import com.android.internal.logging.UiEventLogger
@@ -27,32 +27,37 @@
 import com.android.systemui.GuestResumeSessionReceiver
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.common.shared.model.Text
-import com.android.systemui.flags.FakeFeatureFlags
-import com.android.systemui.flags.Flags
 import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
 import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
 import com.android.systemui.plugins.ActivityStarter
 import com.android.systemui.power.data.repository.FakePowerRepository
 import com.android.systemui.power.domain.interactor.PowerInteractor
 import com.android.systemui.statusbar.policy.DeviceProvisionedController
-import com.android.systemui.statusbar.policy.UserSwitcherController
 import com.android.systemui.telephony.data.repository.FakeTelephonyRepository
 import com.android.systemui.telephony.domain.interactor.TelephonyInteractor
+import com.android.systemui.user.data.model.UserSwitcherSettingsModel
 import com.android.systemui.user.data.repository.FakeUserRepository
 import com.android.systemui.user.domain.interactor.GuestUserInteractor
 import com.android.systemui.user.domain.interactor.RefreshUsersScheduler
 import com.android.systemui.user.domain.interactor.UserInteractor
 import com.android.systemui.user.legacyhelper.ui.LegacyUserUiHelper
 import com.android.systemui.user.shared.model.UserActionModel
-import com.android.systemui.user.shared.model.UserModel
-import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.whenever
 import com.google.common.truth.Truth.assertThat
-import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.flow.launchIn
-import kotlinx.coroutines.flow.onEach
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.Job
+import kotlinx.coroutines.SupervisorJob
+import kotlinx.coroutines.cancelAndJoin
+import kotlinx.coroutines.flow.toList
+import kotlinx.coroutines.launch
 import kotlinx.coroutines.runBlocking
-import kotlinx.coroutines.test.TestCoroutineScope
-import kotlinx.coroutines.yield
+import kotlinx.coroutines.test.TestDispatcher
+import kotlinx.coroutines.test.TestResult
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.UnconfinedTestDispatcher
+import kotlinx.coroutines.test.runTest
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -60,11 +65,11 @@
 import org.mockito.Mock
 import org.mockito.MockitoAnnotations
 
+@OptIn(ExperimentalCoroutinesApi::class)
 @SmallTest
 @RunWith(JUnit4::class)
 class UserSwitcherViewModelTest : SysuiTestCase() {
 
-    @Mock private lateinit var controller: UserSwitcherController
     @Mock private lateinit var activityStarter: ActivityStarter
     @Mock private lateinit var activityManager: ActivityManager
     @Mock private lateinit var manager: UserManager
@@ -80,28 +85,47 @@
     private lateinit var keyguardRepository: FakeKeyguardRepository
     private lateinit var powerRepository: FakePowerRepository
 
+    private lateinit var testDispatcher: TestDispatcher
+    private lateinit var testScope: TestScope
+    private lateinit var injectedScope: CoroutineScope
+
     @Before
     fun setUp() {
         MockitoAnnotations.initMocks(this)
+        whenever(manager.canAddMoreUsers(any())).thenReturn(true)
+        whenever(manager.getUserSwitchability(any()))
+            .thenReturn(UserManager.SWITCHABILITY_STATUS_OK)
+        overrideResource(
+            com.android.internal.R.string.config_supervisedUserCreationPackage,
+            SUPERVISED_USER_CREATION_PACKAGE,
+        )
 
+        testDispatcher = UnconfinedTestDispatcher()
+        testScope = TestScope(testDispatcher)
+        injectedScope = CoroutineScope(testScope.coroutineContext + SupervisorJob())
         userRepository = FakeUserRepository()
+        runBlocking {
+            userRepository.setSettings(
+                UserSwitcherSettingsModel(
+                    isUserSwitcherEnabled = true,
+                )
+            )
+        }
+
         keyguardRepository = FakeKeyguardRepository()
         powerRepository = FakePowerRepository()
-        val featureFlags = FakeFeatureFlags()
-        featureFlags.set(Flags.USER_INTERACTOR_AND_REPO_USE_CONTROLLER, true)
-        val scope = TestCoroutineScope()
         val refreshUsersScheduler =
             RefreshUsersScheduler(
-                applicationScope = scope,
-                mainDispatcher = IMMEDIATE,
+                applicationScope = injectedScope,
+                mainDispatcher = testDispatcher,
                 repository = userRepository,
             )
         val guestUserInteractor =
             GuestUserInteractor(
                 applicationContext = context,
-                applicationScope = scope,
-                mainDispatcher = IMMEDIATE,
-                backgroundDispatcher = IMMEDIATE,
+                applicationScope = injectedScope,
+                mainDispatcher = testDispatcher,
+                backgroundDispatcher = testDispatcher,
                 manager = manager,
                 repository = userRepository,
                 deviceProvisionedController = deviceProvisionedController,
@@ -118,21 +142,19 @@
                         UserInteractor(
                             applicationContext = context,
                             repository = userRepository,
-                            controller = controller,
                             activityStarter = activityStarter,
                             keyguardInteractor =
                                 KeyguardInteractor(
                                     repository = keyguardRepository,
                                 ),
-                            featureFlags = featureFlags,
                             manager = manager,
-                            applicationScope = scope,
+                            applicationScope = injectedScope,
                             telephonyInteractor =
                                 TelephonyInteractor(
                                     repository = FakeTelephonyRepository(),
                                 ),
                             broadcastDispatcher = fakeBroadcastDispatcher,
-                            backgroundDispatcher = IMMEDIATE,
+                            backgroundDispatcher = testDispatcher,
                             activityManager = activityManager,
                             refreshUsersScheduler = refreshUsersScheduler,
                             guestUserInteractor = guestUserInteractor,
@@ -141,222 +163,216 @@
                         PowerInteractor(
                             repository = powerRepository,
                         ),
-                    featureFlags = featureFlags,
                     guestUserInteractor = guestUserInteractor,
                 )
                 .create(UserSwitcherViewModel::class.java)
     }
 
     @Test
-    fun users() =
-        runBlocking(IMMEDIATE) {
-            userRepository.setUsers(
+    fun users() = selfCancelingTest {
+        val userInfos =
+            listOf(
+                UserInfo(
+                    /* id= */ 0,
+                    /* name= */ "zero",
+                    /* iconPath= */ "",
+                    /* flags= */ UserInfo.FLAG_PRIMARY or UserInfo.FLAG_ADMIN,
+                    UserManager.USER_TYPE_FULL_SYSTEM,
+                ),
+                UserInfo(
+                    /* id= */ 1,
+                    /* name= */ "one",
+                    /* iconPath= */ "",
+                    /* flags= */ 0,
+                    UserManager.USER_TYPE_FULL_SYSTEM,
+                ),
+                UserInfo(
+                    /* id= */ 2,
+                    /* name= */ "two",
+                    /* iconPath= */ "",
+                    /* flags= */ 0,
+                    UserManager.USER_TYPE_FULL_SYSTEM,
+                ),
+            )
+        userRepository.setUserInfos(userInfos)
+        userRepository.setSelectedUserInfo(userInfos[0])
+
+        val userViewModels = mutableListOf<List<UserViewModel>>()
+        val job = launch(testDispatcher) { underTest.users.toList(userViewModels) }
+
+        assertThat(userViewModels.last()).hasSize(3)
+        assertUserViewModel(
+            viewModel = userViewModels.last()[0],
+            viewKey = 0,
+            name = "zero",
+            isSelectionMarkerVisible = true,
+        )
+        assertUserViewModel(
+            viewModel = userViewModels.last()[1],
+            viewKey = 1,
+            name = "one",
+            isSelectionMarkerVisible = false,
+        )
+        assertUserViewModel(
+            viewModel = userViewModels.last()[2],
+            viewKey = 2,
+            name = "two",
+            isSelectionMarkerVisible = false,
+        )
+        job.cancel()
+    }
+
+    @Test
+    fun `maximumUserColumns - few users`() = selfCancelingTest {
+        setUsers(count = 2)
+        val values = mutableListOf<Int>()
+        val job = launch(testDispatcher) { underTest.maximumUserColumns.toList(values) }
+
+        assertThat(values.last()).isEqualTo(4)
+
+        job.cancel()
+    }
+
+    @Test
+    fun `maximumUserColumns - many users`() = selfCancelingTest {
+        setUsers(count = 5)
+        val values = mutableListOf<Int>()
+        val job = launch(testDispatcher) { underTest.maximumUserColumns.toList(values) }
+
+        assertThat(values.last()).isEqualTo(3)
+        job.cancel()
+    }
+
+    @Test
+    fun `isOpenMenuButtonVisible - has actions - true`() = selfCancelingTest {
+        setUsers(2)
+
+        val isVisible = mutableListOf<Boolean>()
+        val job = launch(testDispatcher) { underTest.isOpenMenuButtonVisible.toList(isVisible) }
+
+        assertThat(isVisible.last()).isTrue()
+        job.cancel()
+    }
+
+    @Test
+    fun `isOpenMenuButtonVisible - no actions - false`() = selfCancelingTest {
+        val userInfos = setUsers(2)
+        userRepository.setSelectedUserInfo(userInfos[1])
+        keyguardRepository.setKeyguardShowing(true)
+        whenever(manager.canAddMoreUsers(any())).thenReturn(false)
+
+        val isVisible = mutableListOf<Boolean>()
+        val job = launch(testDispatcher) { underTest.isOpenMenuButtonVisible.toList(isVisible) }
+
+        assertThat(isVisible.last()).isFalse()
+        job.cancel()
+    }
+
+    @Test
+    fun menu() = selfCancelingTest {
+        val isMenuVisible = mutableListOf<Boolean>()
+        val job = launch(testDispatcher) { underTest.isMenuVisible.toList(isMenuVisible) }
+        assertThat(isMenuVisible.last()).isFalse()
+
+        underTest.onOpenMenuButtonClicked()
+        assertThat(isMenuVisible.last()).isTrue()
+
+        underTest.onMenuClosed()
+        assertThat(isMenuVisible.last()).isFalse()
+
+        job.cancel()
+    }
+
+    @Test
+    fun `menu actions`() = selfCancelingTest {
+        setUsers(2)
+        val actions = mutableListOf<List<UserActionViewModel>>()
+        val job = launch(testDispatcher) { underTest.menu.toList(actions) }
+
+        assertThat(actions.last().map { it.viewKey })
+            .isEqualTo(
                 listOf(
-                    UserModel(
-                        id = 0,
-                        name = Text.Loaded("zero"),
-                        image = USER_IMAGE,
-                        isSelected = true,
-                        isSelectable = true,
-                        isGuest = false,
-                    ),
-                    UserModel(
-                        id = 1,
-                        name = Text.Loaded("one"),
-                        image = USER_IMAGE,
-                        isSelected = false,
-                        isSelectable = true,
-                        isGuest = false,
-                    ),
-                    UserModel(
-                        id = 2,
-                        name = Text.Loaded("two"),
-                        image = USER_IMAGE,
-                        isSelected = false,
-                        isSelectable = false,
-                        isGuest = false,
-                    ),
+                    UserActionModel.ENTER_GUEST_MODE.ordinal.toLong(),
+                    UserActionModel.ADD_USER.ordinal.toLong(),
+                    UserActionModel.ADD_SUPERVISED_USER.ordinal.toLong(),
+                    UserActionModel.NAVIGATE_TO_USER_MANAGEMENT.ordinal.toLong(),
                 )
             )
 
-            var userViewModels: List<UserViewModel>? = null
-            val job = underTest.users.onEach { userViewModels = it }.launchIn(this)
-
-            assertThat(userViewModels).hasSize(3)
-            assertUserViewModel(
-                viewModel = userViewModels?.get(0),
-                viewKey = 0,
-                name = "zero",
-                isSelectionMarkerVisible = true,
-                alpha = LegacyUserUiHelper.USER_SWITCHER_USER_VIEW_SELECTABLE_ALPHA,
-                isClickable = true,
-            )
-            assertUserViewModel(
-                viewModel = userViewModels?.get(1),
-                viewKey = 1,
-                name = "one",
-                isSelectionMarkerVisible = false,
-                alpha = LegacyUserUiHelper.USER_SWITCHER_USER_VIEW_SELECTABLE_ALPHA,
-                isClickable = true,
-            )
-            assertUserViewModel(
-                viewModel = userViewModels?.get(2),
-                viewKey = 2,
-                name = "two",
-                isSelectionMarkerVisible = false,
-                alpha = LegacyUserUiHelper.USER_SWITCHER_USER_VIEW_NOT_SELECTABLE_ALPHA,
-                isClickable = false,
-            )
-            job.cancel()
-        }
+        job.cancel()
+    }
 
     @Test
-    fun `maximumUserColumns - few users`() =
-        runBlocking(IMMEDIATE) {
-            setUsers(count = 2)
-            var value: Int? = null
-            val job = underTest.maximumUserColumns.onEach { value = it }.launchIn(this)
+    fun `isFinishRequested - finishes when user is switched`() = selfCancelingTest {
+        val userInfos = setUsers(count = 2)
+        val isFinishRequested = mutableListOf<Boolean>()
+        val job = launch(testDispatcher) { underTest.isFinishRequested.toList(isFinishRequested) }
+        assertThat(isFinishRequested.last()).isFalse()
 
-            assertThat(value).isEqualTo(4)
-            job.cancel()
-        }
+        userRepository.setSelectedUserInfo(userInfos[1])
+
+        assertThat(isFinishRequested.last()).isTrue()
+
+        job.cancel()
+    }
 
     @Test
-    fun `maximumUserColumns - many users`() =
-        runBlocking(IMMEDIATE) {
-            setUsers(count = 5)
-            var value: Int? = null
-            val job = underTest.maximumUserColumns.onEach { value = it }.launchIn(this)
+    fun `isFinishRequested - finishes when the screen turns off`() = selfCancelingTest {
+        setUsers(count = 2)
+        powerRepository.setInteractive(true)
+        val isFinishRequested = mutableListOf<Boolean>()
+        val job = launch(testDispatcher) { underTest.isFinishRequested.toList(isFinishRequested) }
+        assertThat(isFinishRequested.last()).isFalse()
 
-            assertThat(value).isEqualTo(3)
-            job.cancel()
-        }
+        powerRepository.setInteractive(false)
+
+        assertThat(isFinishRequested.last()).isTrue()
+
+        job.cancel()
+    }
 
     @Test
-    fun `isOpenMenuButtonVisible - has actions - true`() =
-        runBlocking(IMMEDIATE) {
-            userRepository.setActions(UserActionModel.values().toList())
+    fun `isFinishRequested - finishes when cancel button is clicked`() = selfCancelingTest {
+        setUsers(count = 2)
+        powerRepository.setInteractive(true)
+        val isFinishRequested = mutableListOf<Boolean>()
+        val job = launch(testDispatcher) { underTest.isFinishRequested.toList(isFinishRequested) }
+        assertThat(isFinishRequested.last()).isFalse()
 
-            var isVisible: Boolean? = null
-            val job = underTest.isOpenMenuButtonVisible.onEach { isVisible = it }.launchIn(this)
+        underTest.onCancelButtonClicked()
 
-            assertThat(isVisible).isTrue()
-            job.cancel()
-        }
+        assertThat(isFinishRequested.last()).isTrue()
 
-    @Test
-    fun `isOpenMenuButtonVisible - no actions - false`() =
-        runBlocking(IMMEDIATE) {
-            userRepository.setActions(emptyList())
+        underTest.onFinished()
 
-            var isVisible: Boolean? = null
-            val job = underTest.isOpenMenuButtonVisible.onEach { isVisible = it }.launchIn(this)
+        assertThat(isFinishRequested.last()).isFalse()
 
-            assertThat(isVisible).isFalse()
-            job.cancel()
-        }
+        job.cancel()
+    }
 
-    @Test
-    fun menu() =
-        runBlocking(IMMEDIATE) {
-            userRepository.setActions(UserActionModel.values().toList())
-            var isMenuVisible: Boolean? = null
-            val job = underTest.isMenuVisible.onEach { isMenuVisible = it }.launchIn(this)
-            assertThat(isMenuVisible).isFalse()
-
-            underTest.onOpenMenuButtonClicked()
-            assertThat(isMenuVisible).isTrue()
-
-            underTest.onMenuClosed()
-            assertThat(isMenuVisible).isFalse()
-
-            job.cancel()
-        }
-
-    @Test
-    fun `menu actions`() =
-        runBlocking(IMMEDIATE) {
-            userRepository.setActions(UserActionModel.values().toList())
-            var actions: List<UserActionViewModel>? = null
-            val job = underTest.menu.onEach { actions = it }.launchIn(this)
-
-            assertThat(actions?.map { it.viewKey })
-                .isEqualTo(
-                    listOf(
-                        UserActionModel.ENTER_GUEST_MODE.ordinal.toLong(),
-                        UserActionModel.ADD_USER.ordinal.toLong(),
-                        UserActionModel.ADD_SUPERVISED_USER.ordinal.toLong(),
-                        UserActionModel.NAVIGATE_TO_USER_MANAGEMENT.ordinal.toLong(),
-                    )
-                )
-
-            job.cancel()
-        }
-
-    @Test
-    fun `isFinishRequested - finishes when user is switched`() =
-        runBlocking(IMMEDIATE) {
-            setUsers(count = 2)
-            var isFinishRequested: Boolean? = null
-            val job = underTest.isFinishRequested.onEach { isFinishRequested = it }.launchIn(this)
-            assertThat(isFinishRequested).isFalse()
-
-            userRepository.setSelectedUser(1)
-            yield()
-            assertThat(isFinishRequested).isTrue()
-
-            job.cancel()
-        }
-
-    @Test
-    fun `isFinishRequested - finishes when the screen turns off`() =
-        runBlocking(IMMEDIATE) {
-            setUsers(count = 2)
-            powerRepository.setInteractive(true)
-            var isFinishRequested: Boolean? = null
-            val job = underTest.isFinishRequested.onEach { isFinishRequested = it }.launchIn(this)
-            assertThat(isFinishRequested).isFalse()
-
-            powerRepository.setInteractive(false)
-            yield()
-            assertThat(isFinishRequested).isTrue()
-
-            job.cancel()
-        }
-
-    @Test
-    fun `isFinishRequested - finishes when cancel button is clicked`() =
-        runBlocking(IMMEDIATE) {
-            setUsers(count = 2)
-            powerRepository.setInteractive(true)
-            var isFinishRequested: Boolean? = null
-            val job = underTest.isFinishRequested.onEach { isFinishRequested = it }.launchIn(this)
-            assertThat(isFinishRequested).isFalse()
-
-            underTest.onCancelButtonClicked()
-            yield()
-            assertThat(isFinishRequested).isTrue()
-
-            underTest.onFinished()
-            yield()
-            assertThat(isFinishRequested).isFalse()
-
-            job.cancel()
-        }
-
-    private suspend fun setUsers(count: Int) {
-        userRepository.setUsers(
+    private suspend fun setUsers(count: Int): List<UserInfo> {
+        val userInfos =
             (0 until count).map { index ->
-                UserModel(
-                    id = index,
-                    name = Text.Loaded("$index"),
-                    image = USER_IMAGE,
-                    isSelected = index == 0,
-                    isSelectable = true,
-                    isGuest = false,
+                UserInfo(
+                    /* id= */ index,
+                    /* name= */ "$index",
+                    /* iconPath= */ "",
+                    /* flags= */ if (index == 0) {
+                        // This is the primary user.
+                        UserInfo.FLAG_PRIMARY or UserInfo.FLAG_ADMIN
+                    } else {
+                        // This isn't the primary user.
+                        0
+                    },
+                    UserManager.USER_TYPE_FULL_SYSTEM,
                 )
             }
-        )
+        userRepository.setUserInfos(userInfos)
+
+        if (userInfos.isNotEmpty()) {
+            userRepository.setSelectedUserInfo(userInfos[0])
+        }
+        return userInfos
     }
 
     private fun assertUserViewModel(
@@ -364,19 +380,25 @@
         viewKey: Int,
         name: String,
         isSelectionMarkerVisible: Boolean,
-        alpha: Float,
-        isClickable: Boolean,
     ) {
         checkNotNull(viewModel)
         assertThat(viewModel.viewKey).isEqualTo(viewKey)
         assertThat(viewModel.name).isEqualTo(Text.Loaded(name))
         assertThat(viewModel.isSelectionMarkerVisible).isEqualTo(isSelectionMarkerVisible)
-        assertThat(viewModel.alpha).isEqualTo(alpha)
-        assertThat(viewModel.onClicked != null).isEqualTo(isClickable)
+        assertThat(viewModel.alpha)
+            .isEqualTo(LegacyUserUiHelper.USER_SWITCHER_USER_VIEW_SELECTABLE_ALPHA)
+        assertThat(viewModel.onClicked).isNotNull()
     }
 
+    private fun selfCancelingTest(
+        block: suspend TestScope.() -> Unit,
+    ): TestResult =
+        testScope.runTest {
+            block()
+            injectedScope.coroutineContext[Job.Key]?.cancelAndJoin()
+        }
+
     companion object {
-        private val IMMEDIATE = Dispatchers.Main.immediate
-        private val USER_IMAGE = mock<Drawable>()
+        private const val SUPERVISED_USER_CREATION_PACKAGE = "com.some.package"
     }
 }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/user/data/repository/FakeUserRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/user/data/repository/FakeUserRepository.kt
index 4df8aa4..b7c8cbf 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/user/data/repository/FakeUserRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/user/data/repository/FakeUserRepository.kt
@@ -20,26 +20,15 @@
 import android.content.pm.UserInfo
 import android.os.UserHandle
 import com.android.systemui.user.data.model.UserSwitcherSettingsModel
-import com.android.systemui.user.shared.model.UserActionModel
-import com.android.systemui.user.shared.model.UserModel
 import java.util.concurrent.atomic.AtomicBoolean
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.asStateFlow
 import kotlinx.coroutines.flow.filterNotNull
-import kotlinx.coroutines.flow.map
 import kotlinx.coroutines.yield
 
 class FakeUserRepository : UserRepository {
 
-    private val _users = MutableStateFlow<List<UserModel>>(emptyList())
-    override val users: Flow<List<UserModel>> = _users.asStateFlow()
-    override val selectedUser: Flow<UserModel> =
-        users.map { models -> models.first { model -> model.isSelected } }
-
-    private val _actions = MutableStateFlow<List<UserActionModel>>(emptyList())
-    override val actions: Flow<List<UserActionModel>> = _actions.asStateFlow()
-
     private val _userSwitcherSettings = MutableStateFlow(UserSwitcherSettingsModel())
     override val userSwitcherSettings: Flow<UserSwitcherSettingsModel> =
         _userSwitcherSettings.asStateFlow()
@@ -52,9 +41,6 @@
 
     override var lastSelectedNonGuestUserId: Int = UserHandle.USER_SYSTEM
 
-    private val _isActionableWhenLocked = MutableStateFlow(false)
-    override val isActionableWhenLocked: Flow<Boolean> = _isActionableWhenLocked.asStateFlow()
-
     private var _isGuestUserAutoCreated: Boolean = false
     override val isGuestUserAutoCreated: Boolean
         get() = _isGuestUserAutoCreated
@@ -100,35 +86,6 @@
         yield()
     }
 
-    fun setUsers(models: List<UserModel>) {
-        _users.value = models
-    }
-
-    suspend fun setSelectedUser(userId: Int) {
-        check(_users.value.find { it.id == userId } != null) {
-            "Cannot select a user with ID $userId - no user with that ID found!"
-        }
-
-        setUsers(
-            _users.value.map { model ->
-                when {
-                    model.isSelected && model.id != userId -> model.copy(isSelected = false)
-                    !model.isSelected && model.id == userId -> model.copy(isSelected = true)
-                    else -> model
-                }
-            }
-        )
-        yield()
-    }
-
-    fun setActions(models: List<UserActionModel>) {
-        _actions.value = models
-    }
-
-    fun setActionableWhenLocked(value: Boolean) {
-        _isActionableWhenLocked.value = value
-    }
-
     fun setGuestUserAutoCreated(value: Boolean) {
         _isGuestUserAutoCreated = value
     }
diff --git a/services/autofill/java/com/android/server/autofill/RemoteAugmentedAutofillService.java b/services/autofill/java/com/android/server/autofill/RemoteAugmentedAutofillService.java
index 8525e36..592045c 100644
--- a/services/autofill/java/com/android/server/autofill/RemoteAugmentedAutofillService.java
+++ b/services/autofill/java/com/android/server/autofill/RemoteAugmentedAutofillService.java
@@ -245,6 +245,7 @@
         });
     }
 
+    @SuppressWarnings("ReturnValueIgnored")
     private void maybeRequestShowInlineSuggestions(int sessionId,
             @Nullable InlineSuggestionsRequest request,
             @Nullable List<Dataset> inlineSuggestionsData, @Nullable Bundle clientState,
diff --git a/services/backup/java/com/android/server/backup/BackupAndRestoreFeatureFlags.java b/services/backup/java/com/android/server/backup/BackupAndRestoreFeatureFlags.java
new file mode 100644
index 0000000..042bcbd
--- /dev/null
+++ b/services/backup/java/com/android/server/backup/BackupAndRestoreFeatureFlags.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.backup;
+
+import android.Manifest;
+import android.annotation.RequiresPermission;
+import android.provider.DeviceConfig;
+
+/**
+ * Retrieves values of feature flags.
+ *
+ * <p>These flags are intended to be configured server-side and their values should be set in {@link
+ * DeviceConfig} by a service that periodically syncs with the server.
+ *
+ * <p>This class must ensure that the namespace, flag name, and default value passed into {@link
+ * DeviceConfig} matches what's declared on the server. The namespace is shared for all backup and
+ * restore flags.
+ */
+public class BackupAndRestoreFeatureFlags {
+    private static final String NAMESPACE = "backup_and_restore";
+
+    private BackupAndRestoreFeatureFlags() {}
+
+    /** Retrieves the value of the flag "backup_transport_future_timeout_millis". */
+    @RequiresPermission(Manifest.permission.READ_DEVICE_CONFIG)
+    public static long getBackupTransportFutureTimeoutMillis() {
+        return DeviceConfig.getLong(
+                NAMESPACE,
+                /* name= */ "backup_transport_future_timeout_millis",
+                /* defaultValue= */ 600000); // 10 minutes
+    }
+
+    /** Retrieves the value of the flag "backup_transport_callback_timeout_millis". */
+    @RequiresPermission(Manifest.permission.READ_DEVICE_CONFIG)
+    public static long getBackupTransportCallbackTimeoutMillis() {
+        return DeviceConfig.getLong(
+                NAMESPACE,
+                /* name= */ "backup_transport_callback_timeout_millis",
+                /* defaultValue= */ 300000); // 5 minutes
+    }
+}
diff --git a/services/backup/java/com/android/server/backup/OperationStorage.java b/services/backup/java/com/android/server/backup/OperationStorage.java
index 466f647..8f73436 100644
--- a/services/backup/java/com/android/server/backup/OperationStorage.java
+++ b/services/backup/java/com/android/server/backup/OperationStorage.java
@@ -153,4 +153,4 @@
      * @return a set of operation tokens for operations in that state.
      */
     Set<Integer> operationTokensForOpState(@OpState int state);
-};
+}
diff --git a/services/backup/java/com/android/server/backup/internal/LifecycleOperationStorage.java b/services/backup/java/com/android/server/backup/internal/LifecycleOperationStorage.java
index 6908c60..a94167e 100644
--- a/services/backup/java/com/android/server/backup/internal/LifecycleOperationStorage.java
+++ b/services/backup/java/com/android/server/backup/internal/LifecycleOperationStorage.java
@@ -353,4 +353,4 @@
             op.callback.handleCancel(cancelAll);
         }
     }
-};
+}
diff --git a/services/backup/java/com/android/server/backup/transport/BackupTransportClient.java b/services/backup/java/com/android/server/backup/transport/BackupTransportClient.java
index 40d7cad..21005bb 100644
--- a/services/backup/java/com/android/server/backup/transport/BackupTransportClient.java
+++ b/services/backup/java/com/android/server/backup/transport/BackupTransportClient.java
@@ -30,6 +30,7 @@
 
 import com.android.internal.backup.IBackupTransport;
 import com.android.internal.infra.AndroidFuture;
+import com.android.server.backup.BackupAndRestoreFeatureFlags;
 
 import java.util.ArrayDeque;
 import java.util.HashSet;
@@ -385,7 +386,8 @@
 
     private <T> T getFutureResult(AndroidFuture<T> future) {
         try {
-            return future.get(600, TimeUnit.SECONDS);
+            return future.get(BackupAndRestoreFeatureFlags.getBackupTransportFutureTimeoutMillis(),
+                    TimeUnit.MILLISECONDS);
         } catch (InterruptedException | ExecutionException | TimeoutException
                  | CancellationException e) {
             Slog.w(TAG, "Failed to get result from transport:", e);
diff --git a/services/backup/java/com/android/server/backup/transport/TransportStatusCallback.java b/services/backup/java/com/android/server/backup/transport/TransportStatusCallback.java
index fb98825..deaa86c 100644
--- a/services/backup/java/com/android/server/backup/transport/TransportStatusCallback.java
+++ b/services/backup/java/com/android/server/backup/transport/TransportStatusCallback.java
@@ -23,13 +23,13 @@
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.backup.ITransportStatusCallback;
+import com.android.server.backup.BackupAndRestoreFeatureFlags;
 
 public class TransportStatusCallback extends ITransportStatusCallback.Stub {
     private static final String TAG = "TransportStatusCallback";
-    private static final int TIMEOUT_MILLIS = 300 * 1000; // 5 minutes.
     private static final int OPERATION_STATUS_DEFAULT = 0;
 
-    private final int mOperationTimeout;
+    private final long mOperationTimeout;
 
     @GuardedBy("this")
     private int mOperationStatus = OPERATION_STATUS_DEFAULT;
@@ -37,7 +37,7 @@
     private boolean mHasCompletedOperation = false;
 
     public TransportStatusCallback() {
-        mOperationTimeout = TIMEOUT_MILLIS;
+        mOperationTimeout = BackupAndRestoreFeatureFlags.getBackupTransportCallbackTimeoutMillis();
     }
 
     @VisibleForTesting
diff --git a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
index be21075..fbde9e0 100644
--- a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
+++ b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
@@ -227,6 +227,12 @@
         return mParams.getName();
     }
 
+    /** Returns the policy specified for this policy type */
+    public @VirtualDeviceParams.DevicePolicy int getDevicePolicy(
+            @VirtualDeviceParams.PolicyType int policyType) {
+        return mParams.getDevicePolicy(policyType);
+    }
+
     /** Returns the unique device ID of this device. */
     @Override // Binder call
     public int getDeviceId() {
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 c400a74..a8797a0 100644
--- a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java
+++ b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java
@@ -233,6 +233,13 @@
         mLocalService.onAppsOnVirtualDeviceChanged();
     }
 
+    @VisibleForTesting
+    void addVirtualDevice(VirtualDeviceImpl virtualDevice) {
+        synchronized (mVirtualDeviceManagerLock) {
+            mVirtualDevices.put(virtualDevice.getAssociationId(), virtualDevice);
+        }
+    }
+
     class VirtualDeviceManagerImpl extends IVirtualDeviceManager.Stub implements
             VirtualDeviceImpl.PendingTrampolineCallback {
 
@@ -358,6 +365,12 @@
             return virtualDevices;
         }
 
+        @Override // BinderCall
+        @VirtualDeviceParams.DevicePolicy
+        public int getDevicePolicy(int deviceId, @VirtualDeviceParams.PolicyType int policyType) {
+            return mLocalService.getDevicePolicy(deviceId, policyType);
+        }
+
         @Nullable
         private AssociationInfo getAssociationInfo(String packageName, int associationId) {
             final int callingUserId = getCallingUserHandle().getIdentifier();
@@ -439,6 +452,20 @@
         }
 
         @Override
+        @VirtualDeviceParams.DevicePolicy
+        public int getDevicePolicy(int deviceId, @VirtualDeviceParams.PolicyType int policyType) {
+            synchronized (mVirtualDeviceManagerLock) {
+                for (int i = 0; i < mVirtualDevices.size(); i++) {
+                    final VirtualDeviceImpl device = mVirtualDevices.valueAt(i);
+                    if (device.getDeviceId() == deviceId) {
+                        return device.getDevicePolicy(policyType);
+                    }
+                }
+            }
+            return VirtualDeviceParams.DEVICE_POLICY_DEFAULT;
+        }
+
+        @Override
         public void onVirtualDisplayCreated(int displayId) {
             final VirtualDisplayListener[] listeners;
             synchronized (mVirtualDeviceManagerLock) {
diff --git a/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureManagerService.java b/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureManagerService.java
index 0f101b0..08ee6d7 100644
--- a/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureManagerService.java
+++ b/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureManagerService.java
@@ -658,7 +658,6 @@
                 int sessionId, int flags, @NonNull IResultReceiver result) {
             Objects.requireNonNull(activityToken);
             Objects.requireNonNull(shareableActivityToken);
-            Objects.requireNonNull(sessionId);
             final int userId = UserHandle.getCallingUserId();
 
             final ActivityPresentationInfo activityPresentationInfo = getAmInternal()
@@ -677,7 +676,6 @@
 
         @Override
         public void finishSession(int sessionId) {
-            Objects.requireNonNull(sessionId);
             final int userId = UserHandle.getCallingUserId();
 
             synchronized (mLock) {
diff --git a/services/core/java/com/android/server/GestureLauncherService.java b/services/core/java/com/android/server/GestureLauncherService.java
index e529010..7d2e276 100644
--- a/services/core/java/com/android/server/GestureLauncherService.java
+++ b/services/core/java/com/android/server/GestureLauncherService.java
@@ -466,7 +466,8 @@
     public static boolean isEmergencyGestureSettingEnabled(Context context, int userId) {
         return isEmergencyGestureEnabled(context.getResources())
                 && Settings.Secure.getIntForUser(context.getContentResolver(),
-                Settings.Secure.EMERGENCY_GESTURE_ENABLED, 1, userId) != 0;
+                Settings.Secure.EMERGENCY_GESTURE_ENABLED,
+                isDefaultEmergencyGestureEnabled(context.getResources()) ? 1 : 0, userId) != 0;
     }
 
     /**
@@ -513,6 +514,11 @@
         return resources.getBoolean(com.android.internal.R.bool.config_emergencyGestureEnabled);
     }
 
+    private static boolean isDefaultEmergencyGestureEnabled(Resources resources) {
+        return resources.getBoolean(
+                com.android.internal.R.bool.config_defaultEmergencyGestureEnabled);
+    }
+
     /**
      * Whether GestureLauncherService should be enabled according to system properties.
      */
diff --git a/services/core/java/com/android/server/SystemServiceManager.java b/services/core/java/com/android/server/SystemServiceManager.java
index c3cd135..a05b84b 100644
--- a/services/core/java/com/android/server/SystemServiceManager.java
+++ b/services/core/java/com/android/server/SystemServiceManager.java
@@ -351,12 +351,24 @@
      * Starts the given user.
      */
     public void onUserStarting(@NonNull TimingsTraceAndSlog t, @UserIdInt int userId) {
-        EventLog.writeEvent(EventLogTags.SSM_USER_STARTING, userId);
-
         final TargetUser targetUser = newTargetUser(userId);
         synchronized (mTargetUsers) {
+            // On Automotive / Headless System User Mode, the system user will be started twice:
+            // - Once by some external or local service that switches the system user to
+            //   the background.
+            // - Once by the ActivityManagerService, when the system is marked ready.
+            // These two events are not synchronized and the order of execution is
+            // non-deterministic. To avoid starting the system user twice, verify whether
+            // the system user has already been started by checking the mTargetUsers.
+            // TODO(b/242195409): this workaround shouldn't be necessary once we move
+            // the headless-user start logic to UserManager-land.
+            if (userId == UserHandle.USER_SYSTEM && mTargetUsers.contains(userId)) {
+                Slog.e(TAG, "Skipping starting system user twice");
+                return;
+            }
             mTargetUsers.put(userId, targetUser);
         }
+        EventLog.writeEvent(EventLogTags.SSM_USER_STARTING, userId);
         onUser(t, USER_STARTING, /* prevUser= */ null, targetUser);
     }
 
diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java
index 7fb3765..2c9da4c 100644
--- a/services/core/java/com/android/server/am/ActiveServices.java
+++ b/services/core/java/com/android/server/am/ActiveServices.java
@@ -23,18 +23,29 @@
 import static android.app.ActivityManager.PROCESS_STATE_HEAVY_WEIGHT;
 import static android.app.ActivityManager.PROCESS_STATE_RECEIVER;
 import static android.app.ActivityManager.PROCESS_STATE_TOP;
+import static android.app.ForegroundServiceTypePolicy.FGS_TYPE_POLICY_CHECK_DEPRECATED;
+import static android.app.ForegroundServiceTypePolicy.FGS_TYPE_POLICY_CHECK_DISABLED;
+import static android.app.ForegroundServiceTypePolicy.FGS_TYPE_POLICY_CHECK_OK;
+import static android.app.ForegroundServiceTypePolicy.FGS_TYPE_POLICY_CHECK_PERMISSION_DENIED_ENFORCED;
+import static android.app.ForegroundServiceTypePolicy.FGS_TYPE_POLICY_CHECK_PERMISSION_DENIED_PERMISSIVE;
+import static android.app.ForegroundServiceTypePolicy.FGS_TYPE_POLICY_CHECK_UNKNOWN;
+import static android.content.pm.PackageManager.PERMISSION_DENIED;
 import static android.content.pm.PackageManager.PERMISSION_GRANTED;
 import static android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_MANIFEST;
+import static android.os.PowerExemptionManager.REASON_ACTIVE_DEVICE_ADMIN;
 import static android.os.PowerExemptionManager.REASON_ACTIVITY_STARTER;
 import static android.os.PowerExemptionManager.REASON_ACTIVITY_VISIBILITY_GRACE_PERIOD;
 import static android.os.PowerExemptionManager.REASON_ALLOWLISTED_PACKAGE;
 import static android.os.PowerExemptionManager.REASON_BACKGROUND_ACTIVITY_PERMISSION;
 import static android.os.PowerExemptionManager.REASON_BACKGROUND_FGS_PERMISSION;
+import static android.os.PowerExemptionManager.REASON_CARRIER_PRIVILEGED_APP;
 import static android.os.PowerExemptionManager.REASON_COMPANION_DEVICE_MANAGER;
 import static android.os.PowerExemptionManager.REASON_CURRENT_INPUT_METHOD;
 import static android.os.PowerExemptionManager.REASON_DENIED;
 import static android.os.PowerExemptionManager.REASON_DEVICE_DEMO_MODE;
 import static android.os.PowerExemptionManager.REASON_DEVICE_OWNER;
+import static android.os.PowerExemptionManager.REASON_DISALLOW_APPS_CONTROL;
+import static android.os.PowerExemptionManager.REASON_DPO_PROTECTED_APP;
 import static android.os.PowerExemptionManager.REASON_FGS_BINDING;
 import static android.os.PowerExemptionManager.REASON_INSTR_BACKGROUND_ACTIVITY_PERMISSION;
 import static android.os.PowerExemptionManager.REASON_INSTR_BACKGROUND_FGS_PERMISSION;
@@ -45,10 +56,12 @@
 import static android.os.PowerExemptionManager.REASON_PROC_STATE_PERSISTENT_UI;
 import static android.os.PowerExemptionManager.REASON_PROC_STATE_TOP;
 import static android.os.PowerExemptionManager.REASON_PROFILE_OWNER;
+import static android.os.PowerExemptionManager.REASON_ROLE_EMERGENCY;
 import static android.os.PowerExemptionManager.REASON_SERVICE_LAUNCH;
 import static android.os.PowerExemptionManager.REASON_START_ACTIVITY_FLAG;
 import static android.os.PowerExemptionManager.REASON_SYSTEM_ALERT_WINDOW_PERMISSION;
 import static android.os.PowerExemptionManager.REASON_SYSTEM_ALLOW_LISTED;
+import static android.os.PowerExemptionManager.REASON_SYSTEM_MODULE;
 import static android.os.PowerExemptionManager.REASON_SYSTEM_UID;
 import static android.os.PowerExemptionManager.REASON_TEMP_ALLOWED_WHILE_IN_USE;
 import static android.os.PowerExemptionManager.REASON_UID_VISIBLE;
@@ -63,6 +76,9 @@
 import static android.os.Process.ZYGOTE_POLICY_FLAG_EMPTY;
 
 import static com.android.internal.messages.nano.SystemMessageProto.SystemMessage.NOTE_FOREGROUND_SERVICE_BG_LAUNCH;
+import static com.android.internal.util.FrameworkStatsLog.FOREGROUND_SERVICE_STATE_CHANGED__STATE__DENIED;
+import static com.android.internal.util.FrameworkStatsLog.FOREGROUND_SERVICE_STATE_CHANGED__STATE__ENTER;
+import static com.android.internal.util.FrameworkStatsLog.FOREGROUND_SERVICE_STATE_CHANGED__STATE__EXIT;
 import static com.android.internal.util.FrameworkStatsLog.SERVICE_REQUEST_EVENT_REPORTED;
 import static com.android.internal.util.FrameworkStatsLog.SERVICE_REQUEST_EVENT_REPORTED__PROC_START_TYPE__PROCESS_START_TYPE_COLD;
 import static com.android.internal.util.FrameworkStatsLog.SERVICE_REQUEST_EVENT_REPORTED__PROC_START_TYPE__PROCESS_START_TYPE_HOT;
@@ -94,6 +110,11 @@
 import android.app.AppGlobals;
 import android.app.AppOpsManager;
 import android.app.ForegroundServiceStartNotAllowedException;
+import android.app.ForegroundServiceTypeNotAllowedException;
+import android.app.ForegroundServiceTypePolicy;
+import android.app.ForegroundServiceTypePolicy.ForegroundServicePolicyCheckCode;
+import android.app.ForegroundServiceTypePolicy.ForegroundServiceTypePermission;
+import android.app.ForegroundServiceTypePolicy.ForegroundServiceTypePolicyInfo;
 import android.app.IApplicationThread;
 import android.app.IForegroundServiceObserver;
 import android.app.IServiceConnection;
@@ -116,12 +137,14 @@
 import android.content.IIntentSender;
 import android.content.Intent;
 import android.content.IntentSender;
+import android.content.ServiceConnection;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageManager;
 import android.content.pm.PackageManagerInternal;
 import android.content.pm.ParceledListSlice;
 import android.content.pm.ResolveInfo;
 import android.content.pm.ServiceInfo;
+import android.content.pm.ServiceInfo.ForegroundServiceType;
 import android.net.Uri;
 import android.os.Binder;
 import android.os.Build;
@@ -287,6 +310,12 @@
     final ArrayList<ServiceRecord> mPendingFgsNotifications = new ArrayList<>();
 
     /**
+     * Map of ForegroundServiceDelegation to the delegation ServiceRecord. The delegation
+     * ServiceRecord has flag isFgsDelegate set to true.
+     */
+    final ArrayMap<ForegroundServiceDelegation, ServiceRecord> mFgsDelegations = new ArrayMap<>();
+
+    /**
      * Whether there is a rate limit that suppresses immediate re-deferral of new FGS
      * notifications from each app.  On by default, disabled only by shell command for
      * test-suite purposes.  To disable the behavior more generally, use the usual
@@ -576,6 +605,7 @@
         getAppStateTracker().addBackgroundRestrictedAppListener(new BackgroundRestrictedListener());
         mAppWidgetManagerInternal = LocalServices.getService(AppWidgetManagerInternal.class);
         setAllowListWhileInUsePermissionInFgs();
+        initSystemExemptedFgsTypePermission();
     }
 
     private AppStateTracker getAppStateTracker() {
@@ -757,8 +787,8 @@
                 Slog.w(TAG, msg);
                 showFgsBgRestrictedNotificationLocked(r);
                 logFGSStateChangeLocked(r,
-                        FrameworkStatsLog.FOREGROUND_SERVICE_STATE_CHANGED__STATE__DENIED,
-                        0, FGS_STOP_REASON_UNKNOWN);
+                        FOREGROUND_SERVICE_STATE_CHANGED__STATE__DENIED,
+                        0, FGS_STOP_REASON_UNKNOWN, FGS_TYPE_POLICY_CHECK_UNKNOWN);
                 if (CompatChanges.isChangeEnabled(FGS_START_EXCEPTION_CHANGE_ID, callingUid)) {
                     throw new ForegroundServiceStartNotAllowedException(msg);
                 }
@@ -1911,6 +1941,7 @@
                     ignoreForeground = true;
                 }
 
+                int fgsTypeCheckCode = FGS_TYPE_POLICY_CHECK_UNKNOWN;
                 if (!ignoreForeground) {
                     if (r.mStartForegroundCount == 0) {
                         /*
@@ -1969,13 +2000,49 @@
                         updateServiceForegroundLocked(psr, true);
                         ignoreForeground = true;
                         logFGSStateChangeLocked(r,
-                                FrameworkStatsLog.FOREGROUND_SERVICE_STATE_CHANGED__STATE__DENIED,
-                                0, FGS_STOP_REASON_UNKNOWN);
+                                FOREGROUND_SERVICE_STATE_CHANGED__STATE__DENIED,
+                                0, FGS_STOP_REASON_UNKNOWN, FGS_TYPE_POLICY_CHECK_UNKNOWN);
                         if (CompatChanges.isChangeEnabled(FGS_START_EXCEPTION_CHANGE_ID,
                                 r.appInfo.uid)) {
                             throw new ForegroundServiceStartNotAllowedException(msg);
                         }
                     }
+
+                    if (!ignoreForeground) {
+                        Pair<Integer, RuntimeException> fgsTypeResult = null;
+                        if (foregroundServiceType == ServiceInfo.FOREGROUND_SERVICE_TYPE_NONE) {
+                            fgsTypeResult = validateForegroundServiceType(r,
+                                    foregroundServiceType,
+                                    ServiceInfo.FOREGROUND_SERVICE_TYPE_NONE);
+                        } else {
+                            int fgsTypes = foregroundServiceType;
+                            // If the service has declared some unknown types which might be coming
+                            // from future releases, and if it also comes with the "specialUse",
+                            // then it'll be deemed as the "specialUse" and we ignore this
+                            // unknown type. Otherwise, it'll be treated as an invalid type.
+                            int defaultFgsTypes = (foregroundServiceType
+                                    & ServiceInfo.FOREGROUND_SERVICE_TYPE_SPECIAL_USE) != 0
+                                    ? ServiceInfo.FOREGROUND_SERVICE_TYPE_SPECIAL_USE
+                                    : ServiceInfo.FOREGROUND_SERVICE_TYPE_NONE;
+                            for (int serviceType = Integer.highestOneBit(fgsTypes);
+                                    serviceType != 0;
+                                    serviceType = Integer.highestOneBit(fgsTypes)) {
+                                fgsTypeResult = validateForegroundServiceType(r,
+                                        serviceType, defaultFgsTypes);
+                                fgsTypes &= ~serviceType;
+                                if (fgsTypeResult.first != FGS_TYPE_POLICY_CHECK_OK) {
+                                    break;
+                                }
+                            }
+                        }
+                        fgsTypeCheckCode = fgsTypeResult.first;
+                        if (fgsTypeResult.second != null) {
+                            logFGSStateChangeLocked(r,
+                                    FOREGROUND_SERVICE_STATE_CHANGED__STATE__DENIED,
+                                    0, FGS_STOP_REASON_UNKNOWN, fgsTypeResult.first);
+                            throw fgsTypeResult.second;
+                        }
+                    }
                 }
 
                 // Apps under strict background restrictions simply don't get to have foreground
@@ -2044,8 +2111,8 @@
                         registerAppOpCallbackLocked(r);
                         mAm.updateForegroundServiceUsageStats(r.name, r.userId, true);
                         logFGSStateChangeLocked(r,
-                                FrameworkStatsLog.FOREGROUND_SERVICE_STATE_CHANGED__STATE__ENTER,
-                                0, FGS_STOP_REASON_UNKNOWN);
+                                FOREGROUND_SERVICE_STATE_CHANGED__STATE__ENTER,
+                                0, FGS_STOP_REASON_UNKNOWN, fgsTypeCheckCode);
                         updateNumForegroundServicesLocked();
                     }
                     // Even if the service is already a FGS, we need to update the notification,
@@ -2126,10 +2193,11 @@
                         AppOpsManager.OP_START_FOREGROUND, r.appInfo.uid, r.packageName, null);
                 unregisterAppOpCallbackLocked(r);
                 logFGSStateChangeLocked(r,
-                        FrameworkStatsLog.FOREGROUND_SERVICE_STATE_CHANGED__STATE__EXIT,
+                        FOREGROUND_SERVICE_STATE_CHANGED__STATE__EXIT,
                         r.mFgsExitTime > r.mFgsEnterTime
                                 ? (int) (r.mFgsExitTime - r.mFgsEnterTime) : 0,
-                        FGS_STOP_REASON_STOP_FOREGROUND);
+                        FGS_STOP_REASON_STOP_FOREGROUND,
+                        FGS_TYPE_POLICY_CHECK_UNKNOWN);
                 r.mFgsNotificationWasDeferred = false;
                 signalForegroundServiceObserversLocked(r);
                 resetFgsRestrictionLocked(r);
@@ -2165,6 +2233,118 @@
         return now < eligible;
     }
 
+    /**
+     * Validate if the given service can start a foreground service with given type.
+     *
+     * @return A pair, where the first parameter is the result code and second is the exception
+     *         object if it fails to start a foreground service with given type.
+     */
+    @NonNull
+    private Pair<Integer, RuntimeException> validateForegroundServiceType(ServiceRecord r,
+            @ForegroundServiceType int type,
+            @ForegroundServiceType int defaultToType) {
+        final ForegroundServiceTypePolicy policy = ForegroundServiceTypePolicy.getDefaultPolicy();
+        final ForegroundServiceTypePolicyInfo policyInfo =
+                policy.getForegroundServiceTypePolicyInfo(type, defaultToType);
+        final @ForegroundServicePolicyCheckCode int code = policy.checkForegroundServiceTypePolicy(
+                mAm.mContext, r.packageName, r.app.uid, r.app.getPid(),
+                r.mAllowWhileInUsePermissionInFgs, policyInfo);
+        RuntimeException exception = null;
+        switch (code) {
+            case FGS_TYPE_POLICY_CHECK_DEPRECATED: {
+                final String msg = "Starting FGS with type "
+                        + ServiceInfo.foregroundServiceTypeToLabel(type)
+                        + " code=" + code
+                        + " callerApp=" + r.app
+                        + " targetSDK=" + r.app.info.targetSdkVersion;
+                Slog.wtfQuiet(TAG, msg);
+                Slog.w(TAG, msg);
+            } break;
+            case FGS_TYPE_POLICY_CHECK_DISABLED: {
+                exception = new ForegroundServiceTypeNotAllowedException(
+                        "Starting FGS with type "
+                        + ServiceInfo.foregroundServiceTypeToLabel(type)
+                        + " callerApp=" + r.app
+                        + " targetSDK=" + r.app.info.targetSdkVersion
+                        + " has been prohibited");
+            } break;
+            case FGS_TYPE_POLICY_CHECK_PERMISSION_DENIED_PERMISSIVE: {
+                final String msg = "Starting FGS with type "
+                        + ServiceInfo.foregroundServiceTypeToLabel(type)
+                        + " code=" + code
+                        + " callerApp=" + r.app
+                        + " targetSDK=" + r.app.info.targetSdkVersion
+                        + " requiredPermissions=" + policyInfo.toPermissionString();
+                Slog.wtfQuiet(TAG, msg);
+                Slog.w(TAG, msg);
+            } break;
+            case FGS_TYPE_POLICY_CHECK_PERMISSION_DENIED_ENFORCED: {
+                exception = new SecurityException("Starting FGS with type "
+                        + ServiceInfo.foregroundServiceTypeToLabel(type)
+                        + " callerApp=" + r.app
+                        + " targetSDK=" + r.app.info.targetSdkVersion
+                        + " requires permissions: "
+                        + policyInfo.toPermissionString());
+            } break;
+            case FGS_TYPE_POLICY_CHECK_OK:
+            default:
+                break;
+        }
+        return Pair.create(code, exception);
+    }
+
+    private class SystemExemptedFgsTypePermission extends ForegroundServiceTypePermission {
+        SystemExemptedFgsTypePermission() {
+            super("System exempted");
+        }
+
+        @Override
+        public int checkPermission(@NonNull Context context, int callerUid, int callerPid,
+                @NonNull String packageName, boolean allowWhileInUse) {
+            final AppRestrictionController appRestrictionController = mAm.mAppRestrictionController;
+            @ReasonCode int reason = appRestrictionController
+                    .getPotentialSystemExemptionReason(callerUid);
+            if (reason == REASON_DENIED) {
+                reason = appRestrictionController
+                        .getPotentialSystemExemptionReason(callerUid, packageName);
+                if (reason == REASON_DENIED) {
+                    reason = appRestrictionController
+                            .getPotentialUserAllowedExemptionReason(callerUid, packageName);
+                }
+            }
+            switch (reason) {
+                case REASON_SYSTEM_UID:
+                case REASON_SYSTEM_ALLOW_LISTED:
+                case REASON_DEVICE_DEMO_MODE:
+                case REASON_DISALLOW_APPS_CONTROL:
+                case REASON_DEVICE_OWNER:
+                case REASON_PROFILE_OWNER:
+                case REASON_PROC_STATE_PERSISTENT:
+                case REASON_PROC_STATE_PERSISTENT_UI:
+                case REASON_SYSTEM_MODULE:
+                case REASON_CARRIER_PRIVILEGED_APP:
+                case REASON_DPO_PROTECTED_APP:
+                case REASON_ACTIVE_DEVICE_ADMIN:
+                case REASON_ROLE_EMERGENCY:
+                case REASON_ALLOWLISTED_PACKAGE:
+                    return PERMISSION_GRANTED;
+                default:
+                    return PERMISSION_DENIED;
+            }
+        }
+    }
+
+    private void initSystemExemptedFgsTypePermission() {
+        final ForegroundServiceTypePolicy policy = ForegroundServiceTypePolicy.getDefaultPolicy();
+        final ForegroundServiceTypePolicyInfo policyInfo =
+                policy.getForegroundServiceTypePolicyInfo(
+                       ServiceInfo.FOREGROUND_SERVICE_TYPE_SYSTEM_EXEMPTED,
+                       ServiceInfo.FOREGROUND_SERVICE_TYPE_NONE);
+        if (policyInfo != null) {
+            policyInfo.setCustomPermission(new SystemExemptedFgsTypePermission());
+        }
+    }
+
     ServiceNotificationPolicy applyForegroundServiceNotificationLocked(Notification notification,
             final String tag, final int id, final String pkg, final int userId) {
         // By nature of the FGS API, all FGS notifications have a null tag
@@ -2870,7 +3050,7 @@
         ServiceLookupResult res = retrieveServiceLocked(service, instanceName,
                 isSdkSandboxService, sdkSandboxClientAppUid, sdkSandboxClientAppPackage,
                 resolvedType, callingPackage, callingPid, callingUid, userId, true, callerFg,
-                isBindExternal, allowInstant);
+                isBindExternal, allowInstant, null /* fgsDelegateOptions */);
         if (res == null) {
             return 0;
         }
@@ -3328,7 +3508,7 @@
             boolean allowInstant) {
         return retrieveServiceLocked(service, instanceName, false, 0, null, resolvedType,
                 callingPackage, callingPid, callingUid, userId, createIfNeeded, callingFromFg,
-                isBindExternal, allowInstant);
+                isBindExternal, allowInstant, null /* fgsDelegateOptions */);
     }
 
     private ServiceLookupResult retrieveServiceLocked(Intent service,
@@ -3336,7 +3516,7 @@
             String sdkSandboxClientAppPackage, String resolvedType,
             String callingPackage, int callingPid, int callingUid, int userId,
             boolean createIfNeeded, boolean callingFromFg, boolean isBindExternal,
-            boolean allowInstant) {
+            boolean allowInstant, ForegroundServiceDelegationOptions fgsDelegateOptions) {
         if (isSdkSandboxService && instanceName == null) {
             throw new IllegalArgumentException("No instanceName provided for sdk sandbox process");
         }
@@ -3399,6 +3579,53 @@
                 }
             }
         }
+
+        if (r == null && fgsDelegateOptions != null) {
+            // Create a ServiceRecord for FGS delegate.
+            final ServiceInfo sInfo = new ServiceInfo();
+            ApplicationInfo aInfo = null;
+            try {
+                aInfo = AppGlobals.getPackageManager().getApplicationInfo(
+                        fgsDelegateOptions.mClientPackageName,
+                        ActivityManagerService.STOCK_PM_FLAGS,
+                        userId);
+            } catch (RemoteException ex) {
+            // pm is in same process, this will never happen.
+            }
+            if (aInfo == null) {
+                throw new SecurityException("startForegroundServiceDelegate failed, "
+                        + "could not resolve client package " + callingPackage);
+            }
+            if (aInfo.uid != fgsDelegateOptions.mClientUid) {
+                throw new SecurityException("startForegroundServiceDelegate failed, "
+                        + "uid:" + aInfo.uid
+                        + " does not match clientUid:" + fgsDelegateOptions.mClientUid);
+            }
+            sInfo.applicationInfo = aInfo;
+            sInfo.packageName = aInfo.packageName;
+            sInfo.mForegroundServiceType = fgsDelegateOptions.mForegroundServiceTypes;
+            sInfo.processName = aInfo.processName;
+            final ComponentName cn = service.getComponent();
+            sInfo.name = cn.getClassName();
+            if (createIfNeeded) {
+                final Intent.FilterComparison filter =
+                        new Intent.FilterComparison(service.cloneFilter());
+                final ServiceRestarter res = new ServiceRestarter();
+                r = new ServiceRecord(mAm, cn /* name */, cn /* instanceName */,
+                        sInfo.applicationInfo.packageName, sInfo.applicationInfo.uid, filter, sInfo,
+                        callingFromFg, res, null /* sdkSandboxProcessName */,
+                        INVALID_UID /* sdkSandboxClientAppUid */,
+                        null /* sdkSandboxClientAppPackage */);
+                res.setService(r);
+                smap.mServicesByInstanceName.put(cn, r);
+                smap.mServicesByIntent.put(filter, r);
+                if (DEBUG_SERVICE) Slog.v(TAG_SERVICE, "Retrieve created new service: " + r);
+                r.mRecentCallingPackage = callingPackage;
+                r.mRecentCallingUid = callingUid;
+            }
+            return new ServiceLookupResult(r, resolution.getAlias());
+        }
+
         if (r == null) {
             try {
                 int flags = ActivityManagerService.STOCK_PM_FLAGS
@@ -4777,10 +5004,11 @@
             unregisterAppOpCallbackLocked(r);
             r.mFgsExitTime = SystemClock.uptimeMillis();
             logFGSStateChangeLocked(r,
-                    FrameworkStatsLog.FOREGROUND_SERVICE_STATE_CHANGED__STATE__EXIT,
+                    FOREGROUND_SERVICE_STATE_CHANGED__STATE__EXIT,
                     r.mFgsExitTime > r.mFgsEnterTime
                             ? (int) (r.mFgsExitTime - r.mFgsEnterTime) : 0,
-                    FGS_STOP_REASON_STOP_SERVICE);
+                    FGS_STOP_REASON_STOP_SERVICE,
+                    FGS_TYPE_POLICY_CHECK_UNKNOWN);
             mAm.updateForegroundServiceUsageStats(r.name, r.userId, false);
         }
 
@@ -4809,16 +5037,31 @@
                 // Bump the process to the top of LRU list
                 mAm.updateLruProcessLocked(r.app, false, null);
                 updateServiceForegroundLocked(r.app.mServices, false);
-                try {
-                    oomAdjusted |= bumpServiceExecutingLocked(r, false, "destroy",
-                            oomAdjusted ? 0 : OomAdjuster.OOM_ADJ_REASON_UNBIND_SERVICE);
-                    mDestroyingServices.add(r);
-                    r.destroying = true;
-                    r.app.getThread().scheduleStopService(r);
-                } catch (Exception e) {
-                    Slog.w(TAG, "Exception when destroying service "
-                            + r.shortInstanceName, e);
-                    serviceProcessGoneLocked(r, enqueueOomAdj);
+                if (r.mIsFgsDelegate) {
+                    if (r.mFgsDelegation.mConnection != null) {
+                        mAm.mHandler.post(() -> {
+                            r.mFgsDelegation.mConnection.onServiceDisconnected(
+                                    r.mFgsDelegation.mOptions.getComponentName());
+                        });
+                    }
+                    for (int i = mFgsDelegations.size() - 1; i >= 0; i--) {
+                        if (mFgsDelegations.valueAt(i) == r) {
+                            mFgsDelegations.removeAt(i);
+                            break;
+                        }
+                    }
+                } else {
+                    try {
+                        oomAdjusted |= bumpServiceExecutingLocked(r, false, "destroy",
+                                oomAdjusted ? 0 : OomAdjuster.OOM_ADJ_REASON_UNBIND_SERVICE);
+                        mDestroyingServices.add(r);
+                        r.destroying = true;
+                        r.app.getThread().scheduleStopService(r);
+                    } catch (Exception e) {
+                        Slog.w(TAG, "Exception when destroying service "
+                                + r.shortInstanceName, e);
+                        serviceProcessGoneLocked(r, enqueueOomAdj);
+                    }
                 }
             } else {
                 if (DEBUG_SERVICE) Slog.v(
@@ -7020,17 +7263,20 @@
      * @param r ServiceRecord
      * @param state one of ENTER/EXIT/DENIED event.
      * @param durationMs Only meaningful for EXIT event, the duration from ENTER and EXIT state.
+     * @param fgsStopReason why was this FGS stopped.
+     * @param fgsTypeCheckCode The FGS type policy check result.
      */
     private void logFGSStateChangeLocked(ServiceRecord r, int state, int durationMs,
-            @FgsStopReason int fgsStopReason) {
+            @FgsStopReason int fgsStopReason,
+            @ForegroundServicePolicyCheckCode int fgsTypeCheckCode) {
         if (!ActivityManagerUtils.shouldSamplePackageForAtom(
                 r.packageName, mAm.mConstants.mFgsAtomSampleRate)) {
             return;
         }
         boolean allowWhileInUsePermissionInFgs;
         @PowerExemptionManager.ReasonCode int fgsStartReasonCode;
-        if (state == FrameworkStatsLog.FOREGROUND_SERVICE_STATE_CHANGED__STATE__ENTER
-                || state == FrameworkStatsLog.FOREGROUND_SERVICE_STATE_CHANGED__STATE__EXIT) {
+        if (state == FOREGROUND_SERVICE_STATE_CHANGED__STATE__ENTER
+                || state == FOREGROUND_SERVICE_STATE_CHANGED__STATE__EXIT) {
             allowWhileInUsePermissionInFgs = r.mAllowWhileInUsePermissionInFgsAtEntering;
             fgsStartReasonCode = r.mAllowStartForegroundAtEntering;
         } else {
@@ -7056,14 +7302,20 @@
                 r.mStartForegroundCount,
                 ActivityManagerUtils.hashComponentNameForAtom(r.shortInstanceName),
                 r.mFgsHasNotificationPermission,
-                r.foregroundServiceType);
+                r.foregroundServiceType,
+                fgsTypeCheckCode,
+                r.mIsFgsDelegate,
+                r.mFgsDelegation != null ? r.mFgsDelegation.mOptions.mClientUid : INVALID_UID,
+                r.mFgsDelegation != null ? r.mFgsDelegation.mOptions.mDelegationService
+                        : ForegroundServiceDelegationOptions.DELEGATION_SERVICE_DEFAULT
+        );
 
         int event = 0;
-        if (state == FrameworkStatsLog.FOREGROUND_SERVICE_STATE_CHANGED__STATE__ENTER) {
+        if (state == FOREGROUND_SERVICE_STATE_CHANGED__STATE__ENTER) {
             event = EventLogTags.AM_FOREGROUND_SERVICE_START;
-        } else if (state == FrameworkStatsLog.FOREGROUND_SERVICE_STATE_CHANGED__STATE__EXIT) {
+        } else if (state == FOREGROUND_SERVICE_STATE_CHANGED__STATE__EXIT) {
             event = EventLogTags.AM_FOREGROUND_SERVICE_STOP;
-        } else if (state == FrameworkStatsLog.FOREGROUND_SERVICE_STATE_CHANGED__STATE__DENIED) {
+        } else if (state == FOREGROUND_SERVICE_STATE_CHANGED__STATE__DENIED) {
             event = EventLogTags.AM_FOREGROUND_SERVICE_DENIED;
         } else {
             // Unknown event.
@@ -7120,4 +7372,163 @@
                 return "UNKNOWN";
         }
     }
+
+    /**
+     * Start a foreground service delegate. The delegate is not an actual service component, it is
+     * merely a delegate that promotes the client process into foreground service process state.
+     *
+     * @param options an ForegroundServiceDelegationOptions object.
+     * @param connection callback if the delegate is started successfully.
+     * @return true if delegate is started, false otherwise.
+     * @throw SecurityException if PackageManaager can not resolve
+     *        {@link ForegroundServiceDelegationOptions#mClientPackageName} or the resolved
+     *        package's UID is not same as {@link ForegroundServiceDelegationOptions#mClientUid}
+     */
+    boolean startForegroundServiceDelegateLocked(
+            @NonNull ForegroundServiceDelegationOptions options,
+            @Nullable ServiceConnection connection) {
+        Slog.v(TAG, "startForegroundServiceDelegateLocked " + options.getDescription());
+        final ComponentName cn = options.getComponentName();
+        for (int i = mFgsDelegations.size() - 1; i >= 0; i--) {
+            ForegroundServiceDelegation delegation = mFgsDelegations.keyAt(i);
+            if (delegation.mOptions.isSameDelegate(options)) {
+                Slog.e(TAG, "startForegroundServiceDelegate " + options.getDescription()
+                        + " already exists, multiple connections are not allowed");
+                return false;
+            }
+        }
+        final int callingPid = options.mClientPid;
+        final int callingUid = options.mClientUid;
+        final int userId = UserHandle.getUserId(callingUid);
+        final String callingPackage = options.mClientPackageName;
+
+        if (!canStartForegroundServiceLocked(callingPid, callingUid, callingPackage)) {
+            Slog.d(TAG, "startForegroundServiceDelegateLocked aborted,"
+                    + " app is in the background");
+            return false;
+        }
+
+        IApplicationThread caller = options.mClientAppThread;
+        ProcessRecord callerApp;
+        if (caller != null) {
+            callerApp = mAm.getRecordForAppLOSP(caller);
+        } else {
+            synchronized (mAm.mPidsSelfLocked) {
+                callerApp = mAm.mPidsSelfLocked.get(callingPid);
+                caller = callerApp.getThread();
+            }
+        }
+        if (callerApp == null) {
+            throw new SecurityException(
+                    "Unable to find app for caller " + caller
+                            + " (pid=" + callingPid
+                            + ") when startForegroundServiceDelegateLocked " + cn);
+        }
+
+        Intent intent = new Intent();
+        intent.setComponent(cn);
+        ServiceLookupResult res = retrieveServiceLocked(intent, null /*instanceName */,
+                false /* isSdkSandboxService */, INVALID_UID /* sdkSandboxClientAppUid */,
+                null /* sdkSandboxClientAppPackage */, null /* resolvedType */, callingPackage,
+                callingPid, callingUid, userId, true /* createIfNeeded */,
+                false /* callingFromFg */, false /* isBindExternal */, false /* allowInstant */ ,
+                options);
+        if (res == null || res.record == null) {
+            Slog.d(TAG,
+                    "startForegroundServiceDelegateLocked retrieveServiceLocked returns null");
+            return false;
+        }
+
+        final ServiceRecord r = res.record;
+        r.setProcess(callerApp, caller, callingPid, null);
+        r.mIsFgsDelegate = true;
+        final ForegroundServiceDelegation delegation =
+                new ForegroundServiceDelegation(options, connection);
+        r.mFgsDelegation = delegation;
+        mFgsDelegations.put(delegation, r);
+        r.isForeground = true;
+        r.mFgsEnterTime = SystemClock.uptimeMillis();
+        r.foregroundServiceType = options.mForegroundServiceTypes;
+        setFgsRestrictionLocked(callingPackage, callingPid, callingUid, intent, r, userId,
+                false, false);
+        final ProcessServiceRecord psr = callerApp.mServices;
+        final boolean newService = psr.startService(r);
+        // updateOomAdj.
+        updateServiceForegroundLocked(psr, /* oomAdj= */ true);
+
+        synchronized (mAm.mProcessStats.mLock) {
+            final ServiceState stracker = r.getTracker();
+            if (stracker != null) {
+                stracker.setForeground(true,
+                        mAm.mProcessStats.getMemFactorLocked(),
+                        SystemClock.uptimeMillis());
+            }
+        }
+
+        mAm.mBatteryStatsService.noteServiceStartRunning(callingUid, callingPackage,
+                cn.getClassName());
+        mAm.mAppOpsService.startOperation(AppOpsManager.getToken(mAm.mAppOpsService),
+                AppOpsManager.OP_START_FOREGROUND, r.appInfo.uid, r.packageName, null,
+                true, false, null, false,
+                AppOpsManager.ATTRIBUTION_FLAGS_NONE, AppOpsManager.ATTRIBUTION_CHAIN_ID_NONE);
+        registerAppOpCallbackLocked(r);
+        logFGSStateChangeLocked(r,
+                FrameworkStatsLog.FOREGROUND_SERVICE_STATE_CHANGED__STATE__ENTER,
+                0, FGS_STOP_REASON_UNKNOWN, FGS_TYPE_POLICY_CHECK_UNKNOWN);
+        // Notify the caller.
+        if (connection != null) {
+            mAm.mHandler.post(() -> {
+                connection.onServiceConnected(cn, delegation.mBinder);
+            });
+        }
+        signalForegroundServiceObserversLocked(r);
+        return true;
+    }
+
+    /**
+     * Stop the foreground service delegate. This removes the process out of foreground service
+     * process state.
+     *
+     * @param options an ForegroundServiceDelegationOptions object.
+     */
+    void stopForegroundServiceDelegateLocked(@NonNull ForegroundServiceDelegationOptions options) {
+        ServiceRecord r = null;
+        for (int i = mFgsDelegations.size() - 1; i >= 0; i--) {
+            if (mFgsDelegations.keyAt(i).mOptions.isSameDelegate(options)) {
+                Slog.d(TAG, "stopForegroundServiceDelegateLocked " + options.getDescription());
+                r = mFgsDelegations.valueAt(i);
+                break;
+            }
+        }
+        if (r != null) {
+            bringDownServiceLocked(r, false);
+        } else {
+            Slog.e(TAG, "stopForegroundServiceDelegateLocked delegate does not exist "
+                    + options.getDescription());
+        }
+    }
+
+    /**
+     * Stop the foreground service delegate by its ServiceConnection.
+     * This removes the process out of foreground service process state.
+     *
+     * @param connection an ServiceConnection object.
+     */
+    void stopForegroundServiceDelegateLocked(@NonNull ServiceConnection connection) {
+        ServiceRecord r = null;
+        for (int i = mFgsDelegations.size() - 1; i >= 0; i--) {
+            final ForegroundServiceDelegation d = mFgsDelegations.keyAt(i);
+            if (d.mConnection == connection) {
+                Slog.d(TAG, "stopForegroundServiceDelegateLocked "
+                        + d.mOptions.getDescription());
+                r = mFgsDelegations.valueAt(i);
+                break;
+            }
+        }
+        if (r != null) {
+            bringDownServiceLocked(r, false);
+        } else {
+            Slog.e(TAG, "stopForegroundServiceDelegateLocked delegate does not exist");
+        }
+    }
 }
diff --git a/services/core/java/com/android/server/am/ActivityManagerConstants.java b/services/core/java/com/android/server/am/ActivityManagerConstants.java
index 16fe121..003f7f0 100644
--- a/services/core/java/com/android/server/am/ActivityManagerConstants.java
+++ b/services/core/java/com/android/server/am/ActivityManagerConstants.java
@@ -249,6 +249,18 @@
     private static final String KEY_MAX_PHANTOM_PROCESSES = "max_phantom_processes";
 
     /**
+     * Enables proactive killing of cached apps
+     */
+    private static final String KEY_PROACTIVE_KILLS_ENABLED = "proactive_kills_enabled";
+
+    /**
+      * Trim LRU cached app when swap falls below this minimum percentage.
+      *
+      * Depends on KEY_PROACTIVE_KILLS_ENABLED
+      */
+    private static final String KEY_LOW_SWAP_THRESHOLD_PERCENT = "low_swap_threshold_percent";
+
+    /**
      * Default value for mFlagBackgroundActivityStartsEnabled if not explicitly set in
      * Settings.Global. This allows it to be set experimentally unless it has been
      * enabled/disabled in developer options. Defaults to false.
@@ -874,6 +886,10 @@
      */
     private static final long DEFAULT_MIN_ASSOC_LOG_DURATION = 5 * 60 * 1000; // 5 mins
 
+    private static final boolean DEFAULT_PROACTIVE_KILLS_ENABLED = false;
+
+    private static final float DEFAULT_LOW_SWAP_THRESHOLD_PERCENT = 0.10f;
+
     private static final String KEY_MIN_ASSOC_LOG_DURATION = "min_assoc_log_duration";
 
     public static long MIN_ASSOC_LOG_DURATION = DEFAULT_MIN_ASSOC_LOG_DURATION;
@@ -904,6 +920,8 @@
     public static boolean BINDER_HEAVY_HITTER_AUTO_SAMPLER_ENABLED;
     public static int BINDER_HEAVY_HITTER_AUTO_SAMPLER_BATCHSIZE;
     public static float BINDER_HEAVY_HITTER_AUTO_SAMPLER_THRESHOLD;
+    public static boolean PROACTIVE_KILLS_ENABLED = DEFAULT_PROACTIVE_KILLS_ENABLED;
+    public static float LOW_SWAP_THRESHOLD_PERCENT = DEFAULT_LOW_SWAP_THRESHOLD_PERCENT;
 
     private final OnPropertiesChangedListener mOnDeviceConfigChangedListener =
             new OnPropertiesChangedListener() {
@@ -1040,6 +1058,12 @@
                             case KEY_MAX_SERVICE_CONNECTIONS_PER_PROCESS:
                                 updateMaxServiceConnectionsPerProcess();
                                 break;
+                            case KEY_PROACTIVE_KILLS_ENABLED:
+                                updateProactiveKillsEnabled();
+                                break;
+                            case KEY_LOW_SWAP_THRESHOLD_PERCENT:
+                                updateLowSwapThresholdPercent();
+                                break;
                             default:
                                 break;
                         }
@@ -1660,6 +1684,20 @@
         CUR_TRIM_CACHED_PROCESSES = (MAX_CACHED_PROCESSES-rawMaxEmptyProcesses)/3;
     }
 
+    private void updateProactiveKillsEnabled() {
+        PROACTIVE_KILLS_ENABLED = DeviceConfig.getBoolean(
+                DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+                KEY_PROACTIVE_KILLS_ENABLED,
+                DEFAULT_PROACTIVE_KILLS_ENABLED);
+    }
+
+    private void updateLowSwapThresholdPercent() {
+        LOW_SWAP_THRESHOLD_PERCENT = DeviceConfig.getFloat(
+                DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+                KEY_LOW_SWAP_THRESHOLD_PERCENT,
+                DEFAULT_LOW_SWAP_THRESHOLD_PERCENT);
+    }
+
     private void updateMinAssocLogDuration() {
         MIN_ASSOC_LOG_DURATION = DeviceConfig.getLong(
                 DeviceConfig.NAMESPACE_ACTIVITY_MANAGER, KEY_MIN_ASSOC_LOG_DURATION,
@@ -1860,6 +1898,10 @@
         pw.print("="); pw.println(mNetworkAccessTimeoutMs);
         pw.print("  "); pw.print(KEY_MAX_SERVICE_CONNECTIONS_PER_PROCESS);
         pw.print("="); pw.println(mMaxServiceConnectionsPerProcess);
+        pw.print("  "); pw.print(KEY_PROACTIVE_KILLS_ENABLED);
+        pw.print("="); pw.println(PROACTIVE_KILLS_ENABLED);
+        pw.print("  "); pw.print(KEY_LOW_SWAP_THRESHOLD_PERCENT);
+        pw.print("="); pw.println(LOW_SWAP_THRESHOLD_PERCENT);
 
         pw.println();
         if (mOverrideMaxCachedProcesses >= 0) {
diff --git a/services/core/java/com/android/server/am/ActivityManagerLocal.java b/services/core/java/com/android/server/am/ActivityManagerLocal.java
index 1d2c36b..9f2cc7f 100644
--- a/services/core/java/com/android/server/am/ActivityManagerLocal.java
+++ b/services/core/java/com/android/server/am/ActivityManagerLocal.java
@@ -17,6 +17,7 @@
 package com.android.server.am;
 
 import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.annotation.SuppressLint;
 import android.annotation.SystemApi;
 import android.content.Context;
@@ -92,4 +93,28 @@
             int clientAppUid, @NonNull String clientAppPackage, @NonNull String processName,
             @Context.BindServiceFlags int flags)
             throws RemoteException;
+
+    /**
+     * Start a foreground service delegate.
+     * @param options foreground service delegate options.
+     * @param connection a service connection served as callback to caller.
+     * @return true if delegate is started successfully, false otherwise.
+     * @hide
+     */
+    boolean startForegroundServiceDelegate(@NonNull ForegroundServiceDelegationOptions options,
+            @Nullable ServiceConnection connection);
+
+    /**
+     * Stop a foreground service delegate.
+     * @param options the foreground service delegate options.
+     * @hide
+     */
+    void stopForegroundServiceDelegate(@NonNull ForegroundServiceDelegationOptions options);
+
+    /**
+     * Stop a foreground service delegate by service connection.
+     * @param connection service connection used to start delegate previously.
+     * @hide
+     */
+    void stopForegroundServiceDelegate(@NonNull ServiceConnection connection);
 }
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 7d64077..9ea9e34 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -378,12 +378,10 @@
 import com.android.internal.util.FrameworkStatsLog;
 import com.android.internal.util.MemInfoReader;
 import com.android.internal.util.Preconditions;
-import com.android.internal.util.function.DecFunction;
 import com.android.internal.util.function.HeptFunction;
 import com.android.internal.util.function.HexFunction;
 import com.android.internal.util.function.QuadFunction;
 import com.android.internal.util.function.QuintFunction;
-import com.android.internal.util.function.TriFunction;
 import com.android.internal.util.function.UndecFunction;
 import com.android.server.AlarmManagerInternal;
 import com.android.server.BootReceiver;
@@ -8402,13 +8400,10 @@
 
         // On Automotive / Headless System User Mode, at this point the system user has already been
         // started and unlocked, and some of the tasks we do here have already been done. So skip
-        // those in that case.
+        // those in that case. The duplicate system user start is guarded in SystemServiceManager.
         // TODO(b/242195409): this workaround shouldn't be necessary once we move the headless-user
-        // start logic to UserManager-land
-        final boolean bootingSystemUser = currentUserId == UserHandle.USER_SYSTEM;
-        if (bootingSystemUser) {
-            mUserController.onSystemUserStarting();
-        }
+        // start logic to UserManager-land.
+        mUserController.onSystemUserStarting();
 
         synchronized (this) {
             // Only start up encryption-aware persistent apps; once user is
@@ -8438,7 +8433,15 @@
                 t.traceEnd();
             }
 
-            if (bootingSystemUser) {
+            // Some systems - like automotive - will explicitly unlock system user then switch
+            // to a secondary user. Hence, we don't want to send duplicate broadcasts for
+            // the system user here.
+            // TODO(b/242195409): this workaround shouldn't be necessary once we move
+            // the headless-user start logic to UserManager-land.
+            final boolean isBootingSystemUser = (currentUserId == UserHandle.USER_SYSTEM)
+                    && !UserManager.isHeadlessSystemUserMode();
+
+            if (isBootingSystemUser) {
                 t.traceBegin("startHomeOnAllDisplays");
                 mAtmInternal.startHomeOnAllDisplays(currentUserId, "systemReady");
                 t.traceEnd();
@@ -8449,7 +8452,7 @@
             t.traceEnd();
 
 
-            if (bootingSystemUser) {
+            if (isBootingSystemUser) {
                 t.traceBegin("sendUserStartBroadcast");
                 final int callingUid = Binder.getCallingUid();
                 final int callingPid = Binder.getCallingPid();
@@ -8490,7 +8493,7 @@
             mAtmInternal.resumeTopActivities(false /* scheduleIdle */);
             t.traceEnd();
 
-            if (bootingSystemUser) {
+            if (isBootingSystemUser) {
                 t.traceBegin("sendUserSwitchBroadcasts");
                 mUserController.sendUserSwitchBroadcasts(-1, currentUserId);
                 t.traceEnd();
@@ -13506,9 +13509,19 @@
             // Don't enforce the flag check if we're EITHER registering for only protected
             // broadcasts, or the receiver is null (a sticky broadcast). Sticky broadcasts should
             // not be used generally, so we will be marking them as exported by default
-            final boolean requireExplicitFlagForDynamicReceivers = CompatChanges.isChangeEnabled(
+            boolean requireExplicitFlagForDynamicReceivers = CompatChanges.isChangeEnabled(
                     DYNAMIC_RECEIVER_EXPLICIT_EXPORT_REQUIRED, callingUid)
                     && mConstants.mEnforceReceiverExportedFlagRequirement;
+            // STOPSHIP(b/259139792): Allow apps that are currently targeting U and in process of
+            // updating their receivers to be exempt from this requirement until their receivers
+            // are flagged.
+            if (requireExplicitFlagForDynamicReceivers) {
+                if ("com.google.android.apps.messaging".equals(callerPackage)) {
+                    // Note, a versionCode check for this package is not performed because it could
+                    // cause breakage with a subsequent update outside the system image.
+                    requireExplicitFlagForDynamicReceivers = false;
+                }
+            }
             if (!onlyProtectedBroadcasts) {
                 if (receiver == null && !explicitExportStateDefined) {
                     // sticky broadcast, no flag specified (flag isn't required)
@@ -18112,6 +18125,30 @@
             mUidObserverController.register(observer, which, cutpoint, callingPackage,
                     Binder.getCallingUid());
         }
+
+        @Override
+        public boolean startForegroundServiceDelegate(
+                @NonNull ForegroundServiceDelegationOptions options,
+                @Nullable ServiceConnection connection) {
+            synchronized (ActivityManagerService.this) {
+                return mServices.startForegroundServiceDelegateLocked(options, connection);
+            }
+        }
+
+        @Override
+        public void stopForegroundServiceDelegate(
+                @NonNull ForegroundServiceDelegationOptions options) {
+            synchronized (ActivityManagerService.this) {
+                mServices.stopForegroundServiceDelegateLocked(options);
+            }
+        }
+
+        @Override
+        public void stopForegroundServiceDelegate(@NonNull ServiceConnection connection) {
+            synchronized (ActivityManagerService.this) {
+                mServices.stopForegroundServiceDelegateLocked(connection);
+            }
+        }
     }
 
     long inputDispatchingTimedOut(int pid, final boolean aboveSystem, TimeoutRecord timeoutRecord) {
@@ -18301,6 +18338,59 @@
     }
 
     /**
+     * Start/stop foreground service delegate on a app's process.
+     * This interface is intended for the shell command to use.
+     */
+    void setForegroundServiceDelegate(String packageName, int uid, boolean isStart,
+            @ForegroundServiceDelegationOptions.DelegationService int delegateService,
+            String clientInstanceName) {
+        final int callingUid = Binder.getCallingUid();
+        if (callingUid != SYSTEM_UID && callingUid != ROOT_UID && callingUid != SHELL_UID) {
+            throw new SecurityException(
+                    "No permission to start/stop foreground service delegate");
+        }
+        final long callingId = Binder.clearCallingIdentity();
+        try {
+            boolean foundPid = false;
+            synchronized (this) {
+                ArrayList<ForegroundServiceDelegationOptions> delegates = new ArrayList<>();
+                synchronized (mPidsSelfLocked) {
+                    for (int i = 0; i < mPidsSelfLocked.size(); i++) {
+                        final ProcessRecord p = mPidsSelfLocked.valueAt(i);
+                        final IApplicationThread thread = p.getThread();
+                        if (p.uid == uid && thread != null) {
+                            foundPid = true;
+                            int pid = mPidsSelfLocked.keyAt(i);
+                            ForegroundServiceDelegationOptions options =
+                                    new ForegroundServiceDelegationOptions(pid, uid, packageName,
+                                            null /* clientAppThread */,
+                                            false /* isSticky */,
+                                            clientInstanceName, 0 /* foregroundServiceType */,
+                                            delegateService);
+                            delegates.add(options);
+                        }
+                    }
+                }
+                for (int i = delegates.size() - 1; i >= 0; i--) {
+                    final ForegroundServiceDelegationOptions options = delegates.get(i);
+                    if (isStart) {
+                        ((ActivityManagerLocal) mInternal).startForegroundServiceDelegate(options,
+                                null /* connection */);
+                    } else {
+                        ((ActivityManagerLocal) mInternal).stopForegroundServiceDelegate(options);
+                    }
+                }
+            }
+            if (!foundPid) {
+                Slog.e(TAG, "setForegroundServiceDelegate can not find process for packageName:"
+                        + packageName + " uid:" + uid);
+            }
+        } finally {
+            Binder.restoreCallingIdentity(callingId);
+        }
+    }
+
+    /**
      * Force the settings cache to be loaded
      */
     void refreshSettingsCache() {
@@ -18904,19 +18994,20 @@
         }
 
         @Override
-        public SyncNotedAppOp startProxyOperation(int code,
+        public SyncNotedAppOp startProxyOperation(@NonNull IBinder clientId, int code,
                 @NonNull AttributionSource attributionSource, boolean startIfModeDefault,
                 boolean shouldCollectAsyncNotedOp, String message, boolean shouldCollectMessage,
                 boolean skipProxyOperation, @AttributionFlags int proxyAttributionFlags,
                 @AttributionFlags int proxiedAttributionFlags, int attributionChainId,
-                @NonNull DecFunction<Integer, AttributionSource, Boolean, Boolean, String, Boolean,
-                        Boolean, Integer, Integer, Integer, SyncNotedAppOp> superImpl) {
+                @NonNull UndecFunction<IBinder, Integer, AttributionSource,
+                        Boolean, Boolean, String, Boolean, Boolean, Integer, Integer, Integer,
+                        SyncNotedAppOp> superImpl) {
             if (attributionSource.getUid() == mTargetUid && isTargetOp(code)) {
                 final int shellUid = UserHandle.getUid(UserHandle.getUserId(
                         attributionSource.getUid()), Process.SHELL_UID);
                 final long identity = Binder.clearCallingIdentity();
                 try {
-                    return superImpl.apply(code, new AttributionSource(shellUid,
+                    return superImpl.apply(clientId, code, new AttributionSource(shellUid,
                             "com.android.shell", attributionSource.getAttributionTag(),
                             attributionSource.getToken(), attributionSource.getNext()),
                             startIfModeDefault, shouldCollectAsyncNotedOp, message,
@@ -18926,21 +19017,22 @@
                     Binder.restoreCallingIdentity(identity);
                 }
             }
-            return superImpl.apply(code, attributionSource, startIfModeDefault,
+            return superImpl.apply(clientId, code, attributionSource, startIfModeDefault,
                     shouldCollectAsyncNotedOp, message, shouldCollectMessage, skipProxyOperation,
                     proxyAttributionFlags, proxiedAttributionFlags, attributionChainId);
         }
 
         @Override
-        public void finishProxyOperation(int code, @NonNull AttributionSource attributionSource,
-                boolean skipProxyOperation, @NonNull TriFunction<Integer, AttributionSource,
-                        Boolean, Void> superImpl) {
+        public void finishProxyOperation(@NonNull IBinder clientId, int code,
+                @NonNull AttributionSource attributionSource, boolean skipProxyOperation,
+                @NonNull QuadFunction<IBinder, Integer, AttributionSource, Boolean,
+                        Void> superImpl) {
             if (attributionSource.getUid() == mTargetUid && isTargetOp(code)) {
                 final int shellUid = UserHandle.getUid(UserHandle.getUserId(
                         attributionSource.getUid()), Process.SHELL_UID);
                 final long identity = Binder.clearCallingIdentity();
                 try {
-                    superImpl.apply(code, new AttributionSource(shellUid,
+                    superImpl.apply(clientId, code, new AttributionSource(shellUid,
                             "com.android.shell", attributionSource.getAttributionTag(),
                             attributionSource.getToken(), attributionSource.getNext()),
                             skipProxyOperation);
@@ -18948,7 +19040,7 @@
                     Binder.restoreCallingIdentity(identity);
                 }
             }
-            superImpl.apply(code, attributionSource, skipProxyOperation);
+            superImpl.apply(clientId, code, attributionSource, skipProxyOperation);
         }
 
         private boolean isTargetOp(int code) {
diff --git a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
index e4f947d..10f5a36 100644
--- a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
+++ b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
@@ -369,6 +369,8 @@
                     return runResetDropboxRateLimiter();
                 case "list-secondary-displays-for-starting-users":
                     return runListSecondaryDisplaysForStartingUsers(pw);
+                case "set-foreground-service-delegate":
+                    return runSetForegroundServiceDelegate(pw);
                 default:
                     return handleDefaultCommands(cmd);
             }
@@ -3592,6 +3594,45 @@
         return 0;
     }
 
+    int runSetForegroundServiceDelegate(PrintWriter pw) throws RemoteException {
+        int userId = UserHandle.USER_CURRENT;
+
+        String opt;
+        while ((opt = getNextOption()) != null) {
+            if (opt.equals("--user")) {
+                userId = UserHandle.parseUserArg(getNextArgRequired());
+            } else {
+                getErrPrintWriter().println("Error: Unknown option: " + opt);
+                return -1;
+            }
+        }
+        final String packageName = getNextArgRequired();
+        final String action = getNextArgRequired();
+        boolean isStart = true;
+        if ("start".equals(action)) {
+            isStart = true;
+        } else if ("stop".equals(action)) {
+            isStart = false;
+        } else {
+            pw.println("Error: action is either start or stop");
+            return -1;
+        }
+
+        int uid = INVALID_UID;
+        try {
+            final PackageManager pm = mInternal.mContext.getPackageManager();
+            uid = pm.getPackageUidAsUser(packageName,
+                    PackageManager.PackageInfoFlags.of(MATCH_ANY_USER), userId);
+        } catch (PackageManager.NameNotFoundException e) {
+            pw.println("Error: userId:" + userId + " package:" + packageName + " is not found");
+            return -1;
+        }
+        mInternal.setForegroundServiceDelegate(packageName, uid, isStart,
+                ForegroundServiceDelegationOptions.DELEGATION_SERVICE_SPECIAL_USE,
+                "FgsDelegate");
+        return 0;
+    }
+
     int runResetDropboxRateLimiter() throws RemoteException {
         mInternal.resetDropboxRateLimiter();
         return 0;
@@ -3968,6 +4009,8 @@
             pw.println("  list-secondary-displays-for-starting-users");
             pw.println("         Lists the id of displays that can be used to start users on "
                     + "background.");
+            pw.println("  set-foreground-service-delegate [--user <USER_ID>] <PACKAGE> start|stop");
+            pw.println("         Start/stop an app's foreground service delegate.");
             pw.println();
             Intent.printIntentArgsHelp(pw, "");
         }
diff --git a/services/core/java/com/android/server/am/AppFGSTracker.java b/services/core/java/com/android/server/am/AppFGSTracker.java
index 50515cd..1f98aba 100644
--- a/services/core/java/com/android/server/am/AppFGSTracker.java
+++ b/services/core/java/com/android/server/am/AppFGSTracker.java
@@ -16,10 +16,10 @@
 
 package com.android.server.am;
 
+import static android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPES_MAX_INDEX;
 import static android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_LOCATION;
 import static android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_MEDIA_PLAYBACK;
 import static android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_NONE;
-import static android.content.pm.ServiceInfo.NUM_OF_FOREGROUND_SERVICE_TYPES;
 import static android.content.pm.ServiceInfo.foregroundServiceTypeToLabel;
 import static android.os.PowerExemptionManager.REASON_DENIED;
 
@@ -645,7 +645,7 @@
 
         PackageDurations(int uid, String packageName,
                 MaxTrackingDurationConfig maxTrackingDurationConfig, AppFGSTracker tracker) {
-            super(uid, packageName, NUM_OF_FOREGROUND_SERVICE_TYPES + 1, TAG,
+            super(uid, packageName, FOREGROUND_SERVICE_TYPES_MAX_INDEX + 1, TAG,
                     maxTrackingDurationConfig);
             mEvents[DEFAULT_INDEX] = new LinkedList<>();
             mTracker = tracker;
diff --git a/services/core/java/com/android/server/am/AppRestrictionController.java b/services/core/java/com/android/server/am/AppRestrictionController.java
index ba1c3b3..6abf6d8 100644
--- a/services/core/java/com/android/server/am/AppRestrictionController.java
+++ b/services/core/java/com/android/server/am/AppRestrictionController.java
@@ -2784,6 +2784,37 @@
      */
     @ReasonCode
     int getBackgroundRestrictionExemptionReason(int uid) {
+        @ReasonCode int reason = getPotentialSystemExemptionReason(uid);
+        if (reason != REASON_DENIED) {
+            return reason;
+        }
+        final String[] packages = mInjector.getPackageManager().getPackagesForUid(uid);
+        if (packages != null) {
+            // Check each packages to see if any of them is in the "fixed" exemption cases.
+            for (String pkg : packages) {
+                reason = getPotentialSystemExemptionReason(uid, pkg);
+                if (reason != REASON_DENIED) {
+                    return reason;
+                }
+            }
+            // Loop the packages again, and check the user-configurable exemptions.
+            for (String pkg : packages) {
+                reason = getPotentialUserAllowedExemptionReason(uid, pkg);
+                if (reason != REASON_DENIED) {
+                    return reason;
+                }
+            }
+        }
+        return REASON_DENIED;
+    }
+
+    /**
+     * @param uid The uid to check.
+     * @return The potential exemption reason of the given uid. The caller must decide
+     * whether or not it should be exempted.
+     */
+    @ReasonCode
+    int getPotentialSystemExemptionReason(int uid) {
         if (UserHandle.isCore(uid)) {
             return REASON_SYSTEM_UID;
         }
@@ -2811,37 +2842,51 @@
         } else if (uidProcState <= PROCESS_STATE_PERSISTENT_UI) {
             return REASON_PROC_STATE_PERSISTENT_UI;
         }
-        final String[] packages = mInjector.getPackageManager().getPackagesForUid(uid);
-        if (packages != null) {
-            final AppOpsManager appOpsManager = mInjector.getAppOpsManager();
-            final PackageManagerInternal pm = mInjector.getPackageManagerInternal();
-            final AppStandbyInternal appStandbyInternal = mInjector.getAppStandbyInternal();
-            // Check each packages to see if any of them is in the "fixed" exemption cases.
-            for (String pkg : packages) {
-                if (isSystemModule(pkg)) {
-                    return REASON_SYSTEM_MODULE;
-                } else if (isCarrierApp(pkg)) {
-                    return REASON_CARRIER_PRIVILEGED_APP;
-                } else if (isExemptedFromSysConfig(pkg)) {
-                    return REASON_SYSTEM_ALLOW_LISTED;
-                } else if (mConstantsObserver.mBgRestrictionExemptedPackages.contains(pkg)) {
-                    return REASON_SYSTEM_ALLOW_LISTED;
-                } else if (pm.isPackageStateProtected(pkg, userId)) {
-                    return REASON_DPO_PROTECTED_APP;
-                } else if (appStandbyInternal.isActiveDeviceAdmin(pkg, userId)) {
-                    return REASON_ACTIVE_DEVICE_ADMIN;
-                }
-            }
-            // Loop the packages again, and check the user-configurable exemptions.
-            for (String pkg : packages) {
-                if (appOpsManager.checkOpNoThrow(AppOpsManager.OP_ACTIVATE_VPN,
-                        uid, pkg) == AppOpsManager.MODE_ALLOWED) {
-                    return REASON_OP_ACTIVATE_VPN;
-                } else if (appOpsManager.checkOpNoThrow(AppOpsManager.OP_ACTIVATE_PLATFORM_VPN,
-                        uid, pkg) == AppOpsManager.MODE_ALLOWED) {
-                    return REASON_OP_ACTIVATE_PLATFORM_VPN;
-                }
-            }
+        return REASON_DENIED;
+    }
+
+    /**
+     * @param uid The uid to check.
+     * @param pkgName The package name to check.
+     * @return The potential system-fixed exemption reason of the given uid/package. The caller
+     * must decide whether or not it should be exempted.
+     */
+    @ReasonCode
+    int getPotentialSystemExemptionReason(int uid, String pkg) {
+        final PackageManagerInternal pm = mInjector.getPackageManagerInternal();
+        final AppStandbyInternal appStandbyInternal = mInjector.getAppStandbyInternal();
+        final int userId = UserHandle.getUserId(uid);
+        if (isSystemModule(pkg)) {
+            return REASON_SYSTEM_MODULE;
+        } else if (isCarrierApp(pkg)) {
+            return REASON_CARRIER_PRIVILEGED_APP;
+        } else if (isExemptedFromSysConfig(pkg)) {
+            return REASON_SYSTEM_ALLOW_LISTED;
+        } else if (mConstantsObserver.mBgRestrictionExemptedPackages.contains(pkg)) {
+            return REASON_SYSTEM_ALLOW_LISTED;
+        } else if (pm.isPackageStateProtected(pkg, userId)) {
+            return REASON_DPO_PROTECTED_APP;
+        } else if (appStandbyInternal.isActiveDeviceAdmin(pkg, userId)) {
+            return REASON_ACTIVE_DEVICE_ADMIN;
+        }
+        return REASON_DENIED;
+    }
+
+    /**
+     * @param uid The uid to check.
+     * @param pkgName The package name to check.
+     * @return The potential user-allowed exemption reason of the given uid/package. The caller
+     * must decide whether or not it should be exempted.
+     */
+    @ReasonCode
+    int getPotentialUserAllowedExemptionReason(int uid, String pkg) {
+        final AppOpsManager appOpsManager = mInjector.getAppOpsManager();
+        if (appOpsManager.checkOpNoThrow(AppOpsManager.OP_ACTIVATE_VPN,
+                uid, pkg) == AppOpsManager.MODE_ALLOWED) {
+            return REASON_OP_ACTIVATE_VPN;
+        } else if (appOpsManager.checkOpNoThrow(AppOpsManager.OP_ACTIVATE_PLATFORM_VPN,
+                uid, pkg) == AppOpsManager.MODE_ALLOWED) {
+            return REASON_OP_ACTIVATE_PLATFORM_VPN;
         }
         if (isRoleHeldByUid(RoleManager.ROLE_DIALER, uid)) {
             return REASON_ROLE_DIALER;
@@ -2852,6 +2897,7 @@
         if (isOnDeviceIdleAllowlist(uid)) {
             return REASON_ALLOWLISTED_PACKAGE;
         }
+        final ActivityManagerInternal am = mInjector.getActivityManagerInternal();
         if (am.isAssociatedCompanionApp(UserHandle.getUserId(uid), uid)) {
             return REASON_COMPANION_DEVICE_MANAGER;
         }
diff --git a/services/core/java/com/android/server/am/BroadcastQueueImpl.java b/services/core/java/com/android/server/am/BroadcastQueueImpl.java
index 454b284..fb7e0be 100644
--- a/services/core/java/com/android/server/am/BroadcastQueueImpl.java
+++ b/services/core/java/com/android/server/am/BroadcastQueueImpl.java
@@ -51,7 +51,6 @@
 import android.content.IIntentReceiver;
 import android.content.Intent;
 import android.content.pm.ActivityInfo;
-import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageManager;
 import android.content.pm.ResolveInfo;
 import android.content.pm.UserInfo;
@@ -1639,9 +1638,8 @@
             }
             Slog.w(TAG, "Receiver during timeout of " + r + " : " + curReceiver);
             logBroadcastReceiverDiscardLocked(r);
-            String anrMessage =
-                    "Broadcast of " + r.intent.toString() + ", waited " + timeoutDurationMs + "ms";
-            TimeoutRecord timeoutRecord = TimeoutRecord.forBroadcastReceiver(anrMessage);
+            TimeoutRecord timeoutRecord = TimeoutRecord.forBroadcastReceiver(r.intent,
+                    timeoutDurationMs);
             if (curReceiver != null && curReceiver instanceof BroadcastFilter) {
                 BroadcastFilter bf = (BroadcastFilter) curReceiver;
                 if (bf.receiverList.pid != 0
diff --git a/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java b/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java
index ce78d1b..a7d8433 100644
--- a/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java
+++ b/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java
@@ -239,7 +239,7 @@
             }
             case MSG_DELIVERY_TIMEOUT_SOFT: {
                 synchronized (mService) {
-                    deliveryTimeoutSoftLocked((BroadcastProcessQueue) msg.obj);
+                    deliveryTimeoutSoftLocked((BroadcastProcessQueue) msg.obj, msg.arg1);
                 }
                 return true;
             }
@@ -746,9 +746,10 @@
         if (mService.mProcessesReady && !r.timeoutExempt && !assumeDelivered) {
             queue.lastCpuDelayTime = queue.app.getCpuDelayTime();
 
-            final long timeout = r.isForeground() ? mFgConstants.TIMEOUT : mBgConstants.TIMEOUT;
-            mLocalHandler.sendMessageDelayed(
-                    Message.obtain(mLocalHandler, MSG_DELIVERY_TIMEOUT_SOFT, queue), timeout);
+            final int softTimeoutMillis = (int) (r.isForeground() ? mFgConstants.TIMEOUT
+                    : mBgConstants.TIMEOUT);
+            mLocalHandler.sendMessageDelayed(Message.obtain(mLocalHandler,
+                    MSG_DELIVERY_TIMEOUT_SOFT, softTimeoutMillis, 0, queue), softTimeoutMillis);
         }
 
         if (r.allowBackgroundActivityStarts) {
@@ -835,15 +836,17 @@
         r.resultTo = null;
     }
 
-    private void deliveryTimeoutSoftLocked(@NonNull BroadcastProcessQueue queue) {
+    private void deliveryTimeoutSoftLocked(@NonNull BroadcastProcessQueue queue,
+            int softTimeoutMillis) {
         if (queue.app != null) {
             // Instead of immediately triggering an ANR, extend the timeout by
             // the amount of time the process was runnable-but-waiting; we're
             // only willing to do this once before triggering an hard ANR
             final long cpuDelayTime = queue.app.getCpuDelayTime() - queue.lastCpuDelayTime;
-            final long timeout = MathUtils.constrain(cpuDelayTime, 0, mConstants.TIMEOUT);
+            final long hardTimeoutMillis = MathUtils.constrain(cpuDelayTime, 0, softTimeoutMillis);
             mLocalHandler.sendMessageDelayed(
-                    Message.obtain(mLocalHandler, MSG_DELIVERY_TIMEOUT_HARD, queue), timeout);
+                    Message.obtain(mLocalHandler, MSG_DELIVERY_TIMEOUT_HARD, queue),
+                    hardTimeoutMillis);
         } else {
             deliveryTimeoutHardLocked(queue);
         }
@@ -904,8 +907,7 @@
         if (deliveryState == BroadcastRecord.DELIVERY_TIMEOUT) {
             r.anrCount++;
             if (app != null && !app.isDebugging()) {
-                mService.appNotResponding(queue.app, TimeoutRecord
-                        .forBroadcastReceiver("Broadcast of " + r.toShortString()));
+                mService.appNotResponding(queue.app, TimeoutRecord.forBroadcastReceiver(r.intent));
             }
         } else {
             mLocalHandler.removeMessages(MSG_DELIVERY_TIMEOUT_SOFT, queue);
diff --git a/services/core/java/com/android/server/am/CachedAppOptimizer.java b/services/core/java/com/android/server/am/CachedAppOptimizer.java
index 2d7b0dc..b98639e 100644
--- a/services/core/java/com/android/server/am/CachedAppOptimizer.java
+++ b/services/core/java/com/android/server/am/CachedAppOptimizer.java
@@ -37,6 +37,7 @@
 import android.provider.Settings;
 import android.text.TextUtils;
 import android.util.EventLog;
+import android.util.IntArray;
 import android.util.Pair;
 import android.util.Slog;
 import android.util.SparseArray;
@@ -845,7 +846,7 @@
     /**
      * Retrieves the free swap percentage.
      */
-    static private native double getFreeSwapPercent();
+    static native double getFreeSwapPercent();
 
     /**
      * Retrieves the total used physical ZRAM
@@ -2110,15 +2111,28 @@
 
         @GuardedBy({"mAm"})
         @Override
-        public void onBlockingFileLock(int pid) {
+        public void onBlockingFileLock(IntArray pids) {
             if (DEBUG_FREEZER) {
-                Slog.d(TAG_AM, "Process (pid=" + pid + ") holds blocking file lock");
+                Slog.d(TAG_AM, "Blocking file lock found: " + pids);
             }
             synchronized (mProcLock) {
+                int pid = pids.get(0);
                 ProcessRecord app = mFrozenProcesses.get(pid);
+                ProcessRecord pr;
                 if (app != null) {
-                    Slog.i(TAG_AM, app.processName + " (" + pid + ") holds blocking file lock");
-                    unfreezeAppLSP(app, OomAdjuster.OOM_ADJ_REASON_NONE);
+                    for (int i = 1; i < pids.size(); i++) {
+                        int blocked = pids.get(i);
+                        synchronized (mAm.mPidsSelfLocked) {
+                            pr = mAm.mPidsSelfLocked.get(blocked);
+                        }
+                        if (pr != null && pr.mState.getCurAdj() < ProcessList.CACHED_APP_MIN_ADJ) {
+                            Slog.d(TAG_AM, app.processName + " (" + pid + ") blocks "
+                                    + pr.processName + " (" + blocked + ")");
+                            // Found at least one blocked non-cached process
+                            unfreezeAppLSP(app, OomAdjuster.OOM_ADJ_REASON_NONE);
+                            break;
+                        }
+                    }
                 }
             }
         }
diff --git a/services/core/java/com/android/server/am/ForegroundServiceDelegation.java b/services/core/java/com/android/server/am/ForegroundServiceDelegation.java
new file mode 100644
index 0000000..a051d17
--- /dev/null
+++ b/services/core/java/com/android/server/am/ForegroundServiceDelegation.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.am;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.ServiceConnection;
+import android.os.Binder;
+import android.os.IBinder;
+
+/**
+ * A foreground service delegate which has client options and connection callback.
+ */
+public class ForegroundServiceDelegation {
+    public final IBinder mBinder = new Binder();
+    @NonNull
+    public final ForegroundServiceDelegationOptions mOptions;
+    @Nullable
+    public final ServiceConnection mConnection;
+
+    public ForegroundServiceDelegation(@NonNull ForegroundServiceDelegationOptions options,
+            @Nullable ServiceConnection connection) {
+        mOptions = options;
+        mConnection = connection;
+    }
+}
diff --git a/services/core/java/com/android/server/am/ForegroundServiceDelegationOptions.java b/services/core/java/com/android/server/am/ForegroundServiceDelegationOptions.java
new file mode 100644
index 0000000..5eb5a55
--- /dev/null
+++ b/services/core/java/com/android/server/am/ForegroundServiceDelegationOptions.java
@@ -0,0 +1,262 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.am;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.app.IApplicationThread;
+import android.content.ComponentName;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * A service module such as MediaSessionService, VOIP, Camera, Microphone, Location can ask
+ * ActivityManagerService to start a foreground service delegate on behalf of the actual app,
+ * by which the client app's process state can be promoted to FOREGROUND_SERVICE process state which
+ * is higher than the app's actual process state if the app is in the background. This can help to
+ * keep the app in the memory and extra run-time.
+ * The app does not need to define an actual service component nor add it into manifest file.
+ */
+public class ForegroundServiceDelegationOptions {
+
+    public static final int DELEGATION_SERVICE_DEFAULT = 0;
+    public static final int DELEGATION_SERVICE_DATA_SYNC = 1;
+    public static final int DELEGATION_SERVICE_MEDIA_PLAYBACK = 2;
+    public static final int DELEGATION_SERVICE_PHONE_CALL = 3;
+    public static final int DELEGATION_SERVICE_LOCATION = 4;
+    public static final int DELEGATION_SERVICE_CONNECTED_DEVICE = 5;
+    public static final int DELEGATION_SERVICE_MEDIA_PROJECTION = 6;
+    public static final int DELEGATION_SERVICE_CAMERA = 7;
+    public static final int DELEGATION_SERVICE_MICROPHONE = 8;
+    public static final int DELEGATION_SERVICE_HEALTH = 9;
+    public static final int DELEGATION_SERVICE_REMOTE_MESSAGING = 10;
+    public static final int DELEGATION_SERVICE_SYSTEM_EXEMPTED = 11;
+    public static final int DELEGATION_SERVICE_SPECIAL_USE = 12;
+
+    @IntDef(flag = false, prefix = { "DELEGATION_SERVICE_" }, value = {
+            DELEGATION_SERVICE_DEFAULT,
+            DELEGATION_SERVICE_DATA_SYNC,
+            DELEGATION_SERVICE_MEDIA_PLAYBACK,
+            DELEGATION_SERVICE_PHONE_CALL,
+            DELEGATION_SERVICE_LOCATION,
+            DELEGATION_SERVICE_CONNECTED_DEVICE,
+            DELEGATION_SERVICE_MEDIA_PROJECTION,
+            DELEGATION_SERVICE_CAMERA,
+            DELEGATION_SERVICE_MICROPHONE,
+            DELEGATION_SERVICE_HEALTH,
+            DELEGATION_SERVICE_REMOTE_MESSAGING,
+            DELEGATION_SERVICE_SYSTEM_EXEMPTED,
+            DELEGATION_SERVICE_SPECIAL_USE,
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface DelegationService {}
+
+    // The actual app's PID
+    public final int mClientPid;
+    // The actual app's UID
+    public final int mClientUid;
+    // The actual app's package name
+    @NonNull
+    public final String mClientPackageName;
+    // The actual app's app thread
+    @Nullable
+    public final IApplicationThread mClientAppThread;
+    public final boolean mSticky; // Is it a sticky service
+
+    // The delegation service's instance name which is to identify the delegate.
+    @NonNull
+    public String mClientInstanceName;
+    // The foreground service types it consists of.
+    public final int mForegroundServiceTypes;
+    /**
+     * The service's name such as MediaSessionService, VOIP, Camera, Microphone, Location. This is
+     * the internal module's name which actually starts the FGS delegate on behalf of the client
+     * app.
+     */
+    public final @DelegationService int mDelegationService;
+
+    public ForegroundServiceDelegationOptions(int clientPid,
+            int clientUid,
+            @NonNull String clientPackageName,
+            @NonNull IApplicationThread clientAppThread,
+            boolean isSticky,
+            @NonNull String clientInstanceName,
+            int foregroundServiceTypes,
+            @DelegationService int delegationService) {
+        mClientPid = clientPid;
+        mClientUid = clientUid;
+        mClientPackageName = clientPackageName;
+        mClientAppThread = clientAppThread;
+        mSticky = isSticky;
+        mClientInstanceName = clientInstanceName;
+        mForegroundServiceTypes = foregroundServiceTypes;
+        mDelegationService = delegationService;
+    }
+
+    /**
+     * A service delegates a foreground service state to a clientUID using a instanceName.
+     * This delegation is uniquely identified by
+     * mDelegationService/mClientUid/mClientPid/mClientInstanceName
+     */
+    public boolean isSameDelegate(ForegroundServiceDelegationOptions that) {
+        return this.mDelegationService == that.mDelegationService
+                && this.mClientUid == that.mClientUid
+                && this.mClientPid == that.mClientPid
+                && this.mClientInstanceName.equals(that.mClientInstanceName);
+    }
+
+    /**
+     * Construct a component name for this delegate.
+     */
+    public ComponentName getComponentName() {
+        return new ComponentName(mClientPackageName, serviceCodeToString(mDelegationService)
+                + ":" + mClientInstanceName);
+    }
+
+    /**
+     * Get string description of this delegate options.
+     */
+    public String getDescription() {
+        StringBuilder sb = new StringBuilder(128);
+        sb.append("ForegroundServiceDelegate{")
+                .append("package:")
+                .append(mClientPackageName)
+                .append(",")
+                .append("service:")
+                .append(serviceCodeToString(mDelegationService))
+                .append(",")
+                .append("uid:")
+                .append(mClientUid)
+                .append(",")
+                .append("pid:")
+                .append(mClientPid)
+                .append(",")
+                .append("instance:")
+                .append(mClientInstanceName)
+                .append("}");
+        return sb.toString();
+    }
+
+    /**
+     * Map the integer service code to string name.
+     * @param serviceCode
+     * @return
+     */
+    public static String serviceCodeToString(@DelegationService int serviceCode) {
+        switch (serviceCode) {
+            case DELEGATION_SERVICE_DEFAULT:
+                return "DEFAULT";
+            case DELEGATION_SERVICE_DATA_SYNC:
+                return "DATA_SYNC";
+            case DELEGATION_SERVICE_MEDIA_PLAYBACK:
+                return "MEDIA_PLAYBACK";
+            case DELEGATION_SERVICE_PHONE_CALL:
+                return "PHONE_CALL";
+            case DELEGATION_SERVICE_LOCATION:
+                return "LOCATION";
+            case DELEGATION_SERVICE_CONNECTED_DEVICE:
+                return "CONNECTED_DEVICE";
+            case DELEGATION_SERVICE_MEDIA_PROJECTION:
+                return "MEDIA_PROJECTION";
+            case DELEGATION_SERVICE_CAMERA:
+                return "CAMERA";
+            case DELEGATION_SERVICE_MICROPHONE:
+                return "MICROPHONE";
+            case DELEGATION_SERVICE_HEALTH:
+                return "HEALTH";
+            case DELEGATION_SERVICE_REMOTE_MESSAGING:
+                return "REMOTE_MESSAGING";
+            case DELEGATION_SERVICE_SYSTEM_EXEMPTED:
+                return "SYSTEM_EXEMPTED";
+            case DELEGATION_SERVICE_SPECIAL_USE:
+                return "SPECIAL_USE";
+            default:
+                return "(unknown:" + serviceCode + ")";
+        }
+    }
+
+    public static class Builder {
+        int mClientPid; // The actual app PID
+        int mClientUid; // The actual app UID
+        String mClientPackageName; // The actual app's package name
+        int mClientNotificationId; // The actual app's notification
+        IApplicationThread mClientAppThread; // The actual app's app thread
+        boolean mSticky; // Is it a sticky service
+        String mClientInstanceName; // The delegation service instance name
+        int mForegroundServiceTypes; // The foreground service types it consists of
+        @DelegationService int mDelegationService; // The internal service's name, i.e. VOIP
+
+        public Builder setClientPid(int clientPid) {
+            mClientPid = clientPid;
+            return this;
+        }
+
+        public Builder setClientUid(int clientUid) {
+            mClientUid = clientUid;
+            return this;
+        }
+
+        public Builder setClientPackageName(@NonNull String clientPackageName) {
+            mClientPackageName = clientPackageName;
+            return this;
+        }
+
+        public Builder setClientNotificationId(int clientNotificationId) {
+            mClientNotificationId = clientNotificationId;
+            return this;
+        }
+
+        public Builder setClientAppThread(@NonNull IApplicationThread clientAppThread) {
+            mClientAppThread = clientAppThread;
+            return this;
+        }
+
+        public Builder setClientInstanceName(@NonNull String clientInstanceName) {
+            mClientInstanceName = clientInstanceName;
+            return this;
+        }
+
+        public Builder setSticky(boolean isSticky) {
+            mSticky = isSticky;
+            return this;
+        }
+
+        public Builder setForegroundServiceTypes(int foregroundServiceTypes) {
+            mForegroundServiceTypes = foregroundServiceTypes;
+            return this;
+        }
+
+        public Builder setDelegationService(@DelegationService int delegationService) {
+            mDelegationService = delegationService;
+            return this;
+        }
+
+        public ForegroundServiceDelegationOptions build() {
+            return new ForegroundServiceDelegationOptions(mClientPid,
+                mClientUid,
+                mClientPackageName,
+                mClientAppThread,
+                mSticky,
+                mClientInstanceName,
+                mForegroundServiceTypes,
+                mDelegationService
+            );
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/am/OomAdjuster.java b/services/core/java/com/android/server/am/OomAdjuster.java
index 68e5a5d..eb2b7d4 100644
--- a/services/core/java/com/android/server/am/OomAdjuster.java
+++ b/services/core/java/com/android/server/am/OomAdjuster.java
@@ -1118,6 +1118,12 @@
 
     private long mNextNoKillDebugMessageTime;
 
+    private double mLastFreeSwapPercent = 1.00;
+
+    private static double getFreeSwapPercent() {
+        return CachedAppOptimizer.getFreeSwapPercent();
+    }
+
     @GuardedBy({"mService", "mProcLock"})
     private boolean updateAndTrimProcessLSP(final long now, final long nowElapsed,
             final long oldTime, final ActiveUids activeUids, @OomAdjReason int oomAdjReason) {
@@ -1142,6 +1148,11 @@
         int numEmpty = 0;
         int numTrimming = 0;
 
+        boolean proactiveKillsEnabled = mConstants.PROACTIVE_KILLS_ENABLED;
+        double lowSwapThresholdPercent = mConstants.LOW_SWAP_THRESHOLD_PERCENT;
+        double freeSwapPercent =  proactiveKillsEnabled ? getFreeSwapPercent() : 1.00;
+        ProcessRecord lruCachedApp = null;
+
         for (int i = numLru - 1; i >= 0; i--) {
             ProcessRecord app = lruList.get(i);
             final ProcessStateRecord state = app.mState;
@@ -1179,6 +1190,8 @@
                                     ApplicationExitInfo.REASON_OTHER,
                                     ApplicationExitInfo.SUBREASON_TOO_MANY_CACHED,
                                     true);
+                        } else if (proactiveKillsEnabled) {
+                            lruCachedApp = app;
                         }
                         break;
                     case PROCESS_STATE_CACHED_EMPTY:
@@ -1198,6 +1211,8 @@
                                         ApplicationExitInfo.REASON_OTHER,
                                         ApplicationExitInfo.SUBREASON_TOO_MANY_EMPTY,
                                         true);
+                            } else if (proactiveKillsEnabled) {
+                                lruCachedApp = app;
                             }
                         }
                         break;
@@ -1229,6 +1244,20 @@
             }
         }
 
+        if (proactiveKillsEnabled                               // Proactive kills enabled?
+                && doKillExcessiveProcesses                     // Should kill excessive processes?
+                && freeSwapPercent < lowSwapThresholdPercent    // Swap below threshold?
+                && lruCachedApp != null                         // If no cached app, let LMKD decide
+                // If swap is non-decreasing, give reclaim a chance to catch up
+                && freeSwapPercent < mLastFreeSwapPercent) {
+            lruCachedApp.killLocked("swap low and too many cached",
+                    ApplicationExitInfo.REASON_OTHER,
+                    ApplicationExitInfo.SUBREASON_TOO_MANY_CACHED,
+                    true);
+        }
+
+        mLastFreeSwapPercent = freeSwapPercent;
+
         return mService.mAppProfiler.updateLowMemStateLSP(numCached, numEmpty, numTrimming);
     }
 
diff --git a/services/core/java/com/android/server/am/ServiceRecord.java b/services/core/java/com/android/server/am/ServiceRecord.java
index 4b82ad8..457bf91 100644
--- a/services/core/java/com/android/server/am/ServiceRecord.java
+++ b/services/core/java/com/android/server/am/ServiceRecord.java
@@ -206,6 +206,10 @@
     // Last time mAllowWhileInUsePermissionInFgs or mAllowStartForeground is set.
     long mLastSetFgsRestrictionTime;
 
+    // This is a service record of a FGS delegate (not a service record of a real service)
+    boolean mIsFgsDelegate;
+    @Nullable ForegroundServiceDelegation mFgsDelegation;
+
     String stringName;      // caching of toString
 
     private int lastStartId;    // identifier of most recent start request.
@@ -502,6 +506,9 @@
                     pw.print(" foregroundId="); pw.print(foregroundId);
                     pw.print(" foregroundNoti="); pw.println(foregroundNoti);
         }
+        if (mIsFgsDelegate) {
+            pw.print(prefix); pw.print("isFgsDelegate="); pw.println(mIsFgsDelegate);
+        }
         pw.print(prefix); pw.print("createTime=");
                 TimeUtils.formatDuration(createRealTime, nowReal, pw);
                 pw.print(" startingBgTimeout=");
@@ -634,7 +641,9 @@
                     serviceInfo.applicationInfo.uid,
                     serviceInfo.applicationInfo.longVersionCode,
                     serviceInfo.processName, serviceInfo.name);
-            tracker.applyNewOwner(this);
+            if (tracker != null) {
+                tracker.applyNewOwner(this);
+            }
         }
         return tracker;
     }
diff --git a/services/core/java/com/android/server/am/UserController.java b/services/core/java/com/android/server/am/UserController.java
index 9213327..4d86140 100644
--- a/services/core/java/com/android/server/am/UserController.java
+++ b/services/core/java/com/android/server/am/UserController.java
@@ -1090,7 +1090,7 @@
             // TODO(b/239982558): for now we're just updating the user's visibility, but most likely
             // we'll need to remove this call and handle that as part of the user state workflow
             // instead.
-            userManagerInternal.unassignUserFromDisplay(userId);
+            userManagerInternal.unassignUserFromDisplayOnStop(userId);
 
             final boolean visibilityChanged;
             boolean visibleBefore;
@@ -1650,13 +1650,30 @@
                 return false;
             }
 
-            if (!userInfo.preCreated) {
-                // TODO(b/244644281): UMI should return whether the user is visible. And if fails,
-                // the user should not be in the mediator's started users structure
-                mInjector.getUserManagerInternal().assignUserToDisplay(userId,
-                        userInfo.profileGroupId, foreground, displayId);
+            t.traceBegin("assignUserToDisplayOnStart");
+            int result = mInjector.getUserManagerInternal().assignUserToDisplayOnStart(userId,
+                    userInfo.profileGroupId, foreground, displayId);
+            t.traceEnd();
+
+            boolean visible;
+            switch (result) {
+                case UserManagerInternal.USER_ASSIGNMENT_RESULT_SUCCESS_VISIBLE:
+                    visible = true;
+                    break;
+                case UserManagerInternal.USER_ASSIGNMENT_RESULT_SUCCESS_INVISIBLE:
+                    visible = false;
+                    break;
+                default:
+                    Slogf.wtf(TAG, "Wrong result from assignUserToDisplayOnStart(): %d", result);
+                    // Fall through
+                case UserManagerInternal.USER_ASSIGNMENT_RESULT_FAILURE:
+                    Slogf.e(TAG, "%s user(%d) / display (%d) assignment failed: %s",
+                            (foreground ? "fg" : "bg"), userId, displayId,
+                            UserManagerInternal.userAssignmentResultToString(result));
+                    return false;
             }
 
+
             // TODO(b/239982558): might need something similar for bg users on secondary display
             if (foreground && isUserSwitchUiEnabled()) {
                 t.traceBegin("startFreezingScreen");
@@ -1751,19 +1768,6 @@
             }
             t.traceEnd();
 
-            // Need to call UM when user is on background, as there are some cases where the user
-            // cannot be started in background on a secondary display (for example, if user is a
-            // profile).
-            // TODO(b/253103846): it's also explicitly checking if the user is the USER_SYSTEM, as
-            // the UM call would return true during boot (when CarService / BootUserInitializer
-            // calls AM.startUserInBackground() because the system user is still the current user.
-            // TODO(b/244644281): another fragility of this check is that it must wait to call
-            // UMI.isUserVisible() until the user state is check, as that method checks if the
-            // profile of the current user is started. We should fix that dependency so the logic
-            // belongs to just one place (like UserDisplayAssigner)
-            boolean visible = foreground
-                    || userId != UserHandle.USER_SYSTEM
-                            && mInjector.getUserManagerInternal().isUserVisible(userId);
             if (visible) {
                 synchronized (mLock) {
                     addVisibleUserLocked(userId);
@@ -1816,8 +1820,8 @@
                 // user that was started in the background before), so it's necessary to explicitly
                 // notify the services (while when the user starts from BOOTING, USER_START_MSG
                 // takes care of that.
-                mHandler.sendMessage(mHandler.obtainMessage(USER_VISIBILITY_CHANGED_MSG, userId,
-                        visible ? 1 : 0));
+                mHandler.sendMessage(
+                        mHandler.obtainMessage(USER_VISIBILITY_CHANGED_MSG, userId, 1));
             }
 
             t.traceBegin("sendMessages");
diff --git a/services/core/java/com/android/server/app/GameManagerService.java b/services/core/java/com/android/server/app/GameManagerService.java
index 31d707d..64f2aa3 100644
--- a/services/core/java/com/android/server/app/GameManagerService.java
+++ b/services/core/java/com/android/server/app/GameManagerService.java
@@ -1314,16 +1314,9 @@
 
     void onUserSwitching(TargetUser from, TargetUser to) {
         final int toUserId = to.getUserIdentifier();
-        if (from != null) {
-            synchronized (mLock) {
-                final int fromUserId = from.getUserIdentifier();
-                if (mSettings.containsKey(fromUserId)) {
-                    sendUserMessage(fromUserId, REMOVE_SETTINGS, "ON_USER_SWITCHING",
-                            0 /*delayMillis*/);
-                }
-            }
-        }
-
+        // we want to re-populate the setting when switching user as the device config may have
+        // changed, which will only update for the previous user, see
+        // DeviceConfigListener#onPropertiesChanged.
         sendUserMessage(toUserId, POPULATE_GAME_MODE_SETTINGS, "ON_USER_SWITCHING",
                 0 /*delayMillis*/);
 
@@ -1392,8 +1385,9 @@
                 Slog.v(TAG, "Package configuration not found for " + packageName);
                 return;
             }
+        } else {
+            updateFps(packageConfig, packageName, gameMode, userId);
         }
-        updateFps(packageConfig, packageName, gameMode, userId);
         updateUseAngle(packageName, gameMode);
     }
 
diff --git a/services/core/java/com/android/server/appop/AppOpsService.java b/services/core/java/com/android/server/appop/AppOpsService.java
index a58583c..7e00c32 100644
--- a/services/core/java/com/android/server/appop/AppOpsService.java
+++ b/services/core/java/com/android/server/appop/AppOpsService.java
@@ -2920,18 +2920,18 @@
     }
 
     @Override
-    public SyncNotedAppOp startProxyOperation(int code,
+    public SyncNotedAppOp startProxyOperation(@NonNull IBinder clientId, int code,
             @NonNull AttributionSource attributionSource, boolean startIfModeDefault,
             boolean shouldCollectAsyncNotedOp, String message, boolean shouldCollectMessage,
             boolean skipProxyOperation, @AttributionFlags int proxyAttributionFlags,
             @AttributionFlags int proxiedAttributionFlags, int attributionChainId) {
-        return mCheckOpsDelegateDispatcher.startProxyOperation(code, attributionSource,
+        return mCheckOpsDelegateDispatcher.startProxyOperation(clientId, code, attributionSource,
                 startIfModeDefault, shouldCollectAsyncNotedOp, message, shouldCollectMessage,
                 skipProxyOperation, proxyAttributionFlags, proxiedAttributionFlags,
                 attributionChainId);
     }
 
-    private SyncNotedAppOp startProxyOperationImpl(int code,
+    private SyncNotedAppOp startProxyOperationImpl(@NonNull IBinder clientId, int code,
             @NonNull AttributionSource attributionSource,
             boolean startIfModeDefault, boolean shouldCollectAsyncNotedOp, String message,
             boolean shouldCollectMessage, boolean skipProxyOperation, @AttributionFlags
@@ -2940,11 +2940,9 @@
         final int proxyUid = attributionSource.getUid();
         final String proxyPackageName = attributionSource.getPackageName();
         final String proxyAttributionTag = attributionSource.getAttributionTag();
-        final IBinder proxyToken = attributionSource.getToken();
         final int proxiedUid = attributionSource.getNextUid();
         final String proxiedPackageName = attributionSource.getNextPackageName();
         final String proxiedAttributionTag = attributionSource.getNextAttributionTag();
-        final IBinder proxiedToken = attributionSource.getNextToken();
 
         verifyIncomingProxyUid(attributionSource);
         verifyIncomingOp(code);
@@ -2986,7 +2984,7 @@
 
         if (!skipProxyOperation) {
             // Test if the proxied operation will succeed before starting the proxy operation
-            final SyncNotedAppOp testProxiedOp = startOperationUnchecked(proxiedToken, code,
+            final SyncNotedAppOp testProxiedOp = startOperationUnchecked(clientId, code,
                     proxiedUid, resolvedProxiedPackageName, proxiedAttributionTag, proxyUid,
                     resolvedProxyPackageName, proxyAttributionTag, proxiedFlags, startIfModeDefault,
                     shouldCollectAsyncNotedOp, message, shouldCollectMessage,
@@ -2998,7 +2996,7 @@
             final int proxyFlags = isProxyTrusted ? AppOpsManager.OP_FLAG_TRUSTED_PROXY
                     : AppOpsManager.OP_FLAG_UNTRUSTED_PROXY;
 
-            final SyncNotedAppOp proxyAppOp = startOperationUnchecked(proxyToken, code, proxyUid,
+            final SyncNotedAppOp proxyAppOp = startOperationUnchecked(clientId, code, proxyUid,
                     resolvedProxyPackageName, proxyAttributionTag, Process.INVALID_UID, null, null,
                     proxyFlags, startIfModeDefault, !isProxyTrusted, "proxy " + message,
                     shouldCollectMessage, proxyAttributionFlags, attributionChainId,
@@ -3008,7 +3006,7 @@
             }
         }
 
-        return startOperationUnchecked(proxiedToken, code, proxiedUid, resolvedProxiedPackageName,
+        return startOperationUnchecked(clientId, code, proxiedUid, resolvedProxiedPackageName,
                 proxiedAttributionTag, proxyUid, resolvedProxyPackageName, proxyAttributionTag,
                 proxiedFlags, startIfModeDefault, shouldCollectAsyncNotedOp, message,
                 shouldCollectMessage, proxiedAttributionFlags, attributionChainId,
@@ -3151,22 +3149,20 @@
     }
 
     @Override
-    public void finishProxyOperation(int code, @NonNull AttributionSource attributionSource,
-            boolean skipProxyOperation) {
-        mCheckOpsDelegateDispatcher.finishProxyOperation(code, attributionSource,
+    public void finishProxyOperation(@NonNull IBinder clientId, int code,
+            @NonNull AttributionSource attributionSource, boolean skipProxyOperation) {
+        mCheckOpsDelegateDispatcher.finishProxyOperation(clientId, code, attributionSource,
                 skipProxyOperation);
     }
 
-    private Void finishProxyOperationImpl(int code, @NonNull AttributionSource attributionSource,
-            boolean skipProxyOperation) {
+    private Void finishProxyOperationImpl(IBinder clientId, int code,
+            @NonNull AttributionSource attributionSource, boolean skipProxyOperation) {
         final int proxyUid = attributionSource.getUid();
         final String proxyPackageName = attributionSource.getPackageName();
         final String proxyAttributionTag = attributionSource.getAttributionTag();
-        final IBinder proxyToken = attributionSource.getToken();
         final int proxiedUid = attributionSource.getNextUid();
         final String proxiedPackageName = attributionSource.getNextPackageName();
         final String proxiedAttributionTag = attributionSource.getNextAttributionTag();
-        final IBinder proxiedToken = attributionSource.getNextToken();
 
         skipProxyOperation = skipProxyOperation
                 && isCallerAndAttributionTrusted(attributionSource);
@@ -3185,7 +3181,7 @@
         }
 
         if (!skipProxyOperation) {
-            finishOperationUnchecked(proxyToken, code, proxyUid, resolvedProxyPackageName,
+            finishOperationUnchecked(clientId, code, proxyUid, resolvedProxyPackageName,
                     proxyAttributionTag);
         }
 
@@ -3195,7 +3191,7 @@
             return null;
         }
 
-        finishOperationUnchecked(proxiedToken, code, proxiedUid, resolvedProxiedPackageName,
+        finishOperationUnchecked(clientId, code, proxiedUid, resolvedProxiedPackageName,
                 proxiedAttributionTag);
 
         return null;
@@ -6262,7 +6258,6 @@
         Objects.requireNonNull(stackTrace);
         Preconditions.checkArgument(op >= 0);
         Preconditions.checkArgument(op < AppOpsManager._NUM_OP);
-        Objects.requireNonNull(version);
 
         NoteOpTrace noteOpTrace = new NoteOpTrace(stackTrace, op, packageName, version);
 
@@ -6436,42 +6431,42 @@
                     attributionFlags, attributionChainId, AppOpsService.this::startOperationImpl);
         }
 
-        public SyncNotedAppOp startProxyOperation(int code,
+        public SyncNotedAppOp startProxyOperation(@NonNull IBinder clientId, int code,
                 @NonNull AttributionSource attributionSource, boolean startIfModeDefault,
                 boolean shouldCollectAsyncNotedOp, String message, boolean shouldCollectMessage,
                 boolean skipProxyOperation, @AttributionFlags int proxyAttributionFlags,
                 @AttributionFlags int proxiedAttributionFlags, int attributionChainId) {
             if (mPolicy != null) {
                 if (mCheckOpsDelegate != null) {
-                    return mPolicy.startProxyOperation(code, attributionSource,
+                    return mPolicy.startProxyOperation(clientId, code, attributionSource,
                             startIfModeDefault, shouldCollectAsyncNotedOp, message,
                             shouldCollectMessage, skipProxyOperation, proxyAttributionFlags,
                             proxiedAttributionFlags, attributionChainId,
                             this::startDelegateProxyOperationImpl);
                 } else {
-                    return mPolicy.startProxyOperation(code, attributionSource,
+                    return mPolicy.startProxyOperation(clientId, code, attributionSource,
                             startIfModeDefault, shouldCollectAsyncNotedOp, message,
                             shouldCollectMessage, skipProxyOperation, proxyAttributionFlags,
                             proxiedAttributionFlags, attributionChainId,
                             AppOpsService.this::startProxyOperationImpl);
                 }
             } else if (mCheckOpsDelegate != null) {
-                return startDelegateProxyOperationImpl(code, attributionSource,
+                return startDelegateProxyOperationImpl(clientId, code, attributionSource,
                         startIfModeDefault, shouldCollectAsyncNotedOp, message,
                         shouldCollectMessage, skipProxyOperation, proxyAttributionFlags,
                         proxiedAttributionFlags, attributionChainId);
             }
-            return startProxyOperationImpl(code, attributionSource, startIfModeDefault,
+            return startProxyOperationImpl(clientId, code, attributionSource, startIfModeDefault,
                     shouldCollectAsyncNotedOp, message, shouldCollectMessage, skipProxyOperation,
                     proxyAttributionFlags, proxiedAttributionFlags, attributionChainId);
         }
 
-        private SyncNotedAppOp startDelegateProxyOperationImpl(int code,
+        private SyncNotedAppOp startDelegateProxyOperationImpl(@NonNull IBinder clientId, int code,
                 @NonNull AttributionSource attributionSource, boolean startIfModeDefault,
                 boolean shouldCollectAsyncNotedOp, String message, boolean shouldCollectMessage,
                 boolean skipProxyOperation, @AttributionFlags int proxyAttributionFlags,
                 @AttributionFlags int proxiedAttributionFlsgs, int attributionChainId) {
-            return mCheckOpsDelegate.startProxyOperation(code, attributionSource,
+            return mCheckOpsDelegate.startProxyOperation(clientId, code, attributionSource,
                     startIfModeDefault, shouldCollectAsyncNotedOp, message, shouldCollectMessage,
                     skipProxyOperation, proxyAttributionFlags, proxiedAttributionFlsgs,
                     attributionChainId, AppOpsService.this::startProxyOperationImpl);
@@ -6500,27 +6495,28 @@
                     AppOpsService.this::finishOperationImpl);
         }
 
-        public void finishProxyOperation(int code,
+        public void finishProxyOperation(@NonNull IBinder clientId, int code,
                 @NonNull AttributionSource attributionSource, boolean skipProxyOperation) {
             if (mPolicy != null) {
                 if (mCheckOpsDelegate != null) {
-                    mPolicy.finishProxyOperation(code, attributionSource,
+                    mPolicy.finishProxyOperation(clientId, code, attributionSource,
                             skipProxyOperation, this::finishDelegateProxyOperationImpl);
                 } else {
-                    mPolicy.finishProxyOperation(code, attributionSource,
+                    mPolicy.finishProxyOperation(clientId, code, attributionSource,
                             skipProxyOperation, AppOpsService.this::finishProxyOperationImpl);
                 }
             } else if (mCheckOpsDelegate != null) {
-                finishDelegateProxyOperationImpl(code, attributionSource, skipProxyOperation);
+                finishDelegateProxyOperationImpl(clientId, code, attributionSource,
+                        skipProxyOperation);
             } else {
-                finishProxyOperationImpl(code, attributionSource, skipProxyOperation);
+                finishProxyOperationImpl(clientId, code, attributionSource, skipProxyOperation);
             }
         }
 
-        private Void finishDelegateProxyOperationImpl(int code,
+        private Void finishDelegateProxyOperationImpl(@NonNull IBinder clientId, int code,
                 @NonNull AttributionSource attributionSource, boolean skipProxyOperation) {
-            mCheckOpsDelegate.finishProxyOperation(code, attributionSource, skipProxyOperation,
-                    AppOpsService.this::finishProxyOperationImpl);
+            mCheckOpsDelegate.finishProxyOperation(clientId, code, attributionSource,
+                    skipProxyOperation, AppOpsService.this::finishProxyOperationImpl);
             return null;
         }
     }
diff --git a/services/core/java/com/android/server/biometrics/TEST_MAPPING b/services/core/java/com/android/server/biometrics/TEST_MAPPING
index 36acc3c..8b80674 100644
--- a/services/core/java/com/android/server/biometrics/TEST_MAPPING
+++ b/services/core/java/com/android/server/biometrics/TEST_MAPPING
@@ -2,6 +2,9 @@
     "presubmit": [
         {
             "name": "CtsBiometricsTestCases"
+        },
+        {
+            "name": "CtsBiometricsHostTestCases"
         }
     ]
 }
\ No newline at end of file
diff --git a/services/core/java/com/android/server/biometrics/log/ALSProbe.java b/services/core/java/com/android/server/biometrics/log/ALSProbe.java
index da43618..d584c99 100644
--- a/services/core/java/com/android/server/biometrics/log/ALSProbe.java
+++ b/services/core/java/com/android/server/biometrics/log/ALSProbe.java
@@ -120,7 +120,7 @@
 
         // if a final consumer is set it will call destroy/disable on the next value if requested
         if (!mDestroyed && mNextConsumer == null) {
-            disableLightSensorLoggingLocked();
+            disableLightSensorLoggingLocked(false /* destroying */);
         }
     }
 
@@ -130,7 +130,7 @@
 
         // if a final consumer is set it will call destroy/disable on the next value if requested
         if (!mDestroyed && mNextConsumer == null) {
-            disableLightSensorLoggingLocked();
+            disableLightSensorLoggingLocked(true /* destroying */);
             mDestroyed = true;
         }
     }
@@ -177,11 +177,10 @@
         final float current = mLastAmbientLux;
         if (current > -1f) {
             nextConsumer.consume(current);
-        } else if (mDestroyed) {
-            nextConsumer.consume(-1f);
         } else if (mNextConsumer != null) {
             mNextConsumer.add(nextConsumer);
         } else {
+            mDestroyed = false;
             mNextConsumer = nextConsumer;
             enableLightSensorLoggingLocked();
         }
@@ -199,12 +198,14 @@
         resetTimerLocked(true /* start */);
     }
 
-    private void disableLightSensorLoggingLocked() {
+    private void disableLightSensorLoggingLocked(boolean destroying) {
         resetTimerLocked(false /* start */);
 
         if (mEnabled) {
             mEnabled = false;
-            mLastAmbientLux = -1;
+            if (!destroying) {
+                mLastAmbientLux = -1;
+            }
             mSensorManager.unregisterListener(mLightSensorListener);
             Slog.v(TAG, "Disable ALS: " + mLightSensorListener.hashCode());
         }
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/BiometricTestSessionImpl.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/BiometricTestSessionImpl.java
index 7a13c91..d11f099 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/BiometricTestSessionImpl.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/BiometricTestSessionImpl.java
@@ -22,6 +22,7 @@
 import android.hardware.biometrics.ITestSessionCallback;
 import android.hardware.biometrics.face.AuthenticationFrame;
 import android.hardware.biometrics.face.BaseFrame;
+import android.hardware.biometrics.face.EnrollmentFrame;
 import android.hardware.face.Face;
 import android.hardware.face.FaceAuthenticationFrame;
 import android.hardware.face.FaceEnrollFrame;
@@ -33,6 +34,7 @@
 import com.android.server.biometrics.HardwareAuthTokenUtils;
 import com.android.server.biometrics.sensors.BaseClientMonitor;
 import com.android.server.biometrics.sensors.ClientMonitorCallback;
+import com.android.server.biometrics.sensors.EnrollClient;
 import com.android.server.biometrics.sensors.face.FaceUtils;
 
 import java.util.HashSet;
@@ -200,22 +202,24 @@
     }
 
     @android.annotation.EnforcePermission(android.Manifest.permission.TEST_BIOMETRIC)
-    // TODO(b/178414967): replace with notifyAuthenticationFrame and notifyEnrollmentFrame.
     @Override
     public void notifyAcquired(int userId, int acquireInfo) {
-
         super.notifyAcquired_enforcePermission();
 
         BaseFrame data = new BaseFrame();
         data.acquiredInfo = (byte) acquireInfo;
 
-        AuthenticationFrame authenticationFrame = new AuthenticationFrame();
-        authenticationFrame.data = data;
-
-        // TODO(b/178414967): Currently onAuthenticationFrame and onEnrollmentFrame are the same.
-        // This will need to call the correct callback once the onAcquired callback is removed.
-        mSensor.getSessionForUser(userId).getHalSessionCallback().onAuthenticationFrame(
-                authenticationFrame);
+        if (mSensor.getScheduler().getCurrentClient() instanceof EnrollClient) {
+            final EnrollmentFrame frame = new EnrollmentFrame();
+            frame.data = data;
+            mSensor.getSessionForUser(userId).getHalSessionCallback()
+                    .onEnrollmentFrame(frame);
+        } else {
+            final AuthenticationFrame frame = new AuthenticationFrame();
+            frame.data = data;
+            mSensor.getSessionForUser(userId).getHalSessionCallback()
+                    .onAuthenticationFrame(frame);
+        }
     }
 
     @android.annotation.EnforcePermission(android.Manifest.permission.TEST_BIOMETRIC)
diff --git a/services/core/java/com/android/server/broadcastradio/hal2/Mutable.java b/services/core/java/com/android/server/broadcastradio/hal2/Mutable.java
index a9d8054..a6cf72c 100644
--- a/services/core/java/com/android/server/broadcastradio/hal2/Mutable.java
+++ b/services/core/java/com/android/server/broadcastradio/hal2/Mutable.java
@@ -34,13 +34,4 @@
     public Mutable() {
         value = null;
     }
-
-    /**
-     * Initialize value with specific value.
-     *
-     * @param value initial value.
-     */
-    public Mutable(E value) {
-        this.value = value;
-    }
 }
diff --git a/services/core/java/com/android/server/broadcastradio/hal2/Utils.java b/services/core/java/com/android/server/broadcastradio/hal2/Utils.java
index f6e90ef..188c25d 100644
--- a/services/core/java/com/android/server/broadcastradio/hal2/Utils.java
+++ b/services/core/java/com/android/server/broadcastradio/hal2/Utils.java
@@ -25,7 +25,7 @@
     AM_LW,
     AM_MW,
     AM_SW,
-};
+}
 
 class Utils {
 
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 81b56a3..d2e572f 100644
--- a/services/core/java/com/android/server/companion/virtual/VirtualDeviceManagerInternal.java
+++ b/services/core/java/com/android/server/companion/virtual/VirtualDeviceManagerInternal.java
@@ -18,6 +18,7 @@
 
 import android.annotation.NonNull;
 import android.companion.virtual.IVirtualDevice;
+import android.companion.virtual.VirtualDeviceParams;
 
 import java.util.Set;
 
@@ -109,4 +110,14 @@
      * Returns true if the {@code displayId} is owned by any virtual device
      */
     public abstract boolean isDisplayOwnedByAnyVirtualDevice(int displayId);
+
+    /**
+     * Returns the device policy for the given virtual device and policy type.
+     *
+     * <p>In case the virtual device identifier is not valid, or there's no explicitly specified
+     * policy for that device and policy type, then
+     * {@link VirtualDeviceParams#DEVICE_POLICY_DEFAULT} is returned.
+     */
+    public abstract @VirtualDeviceParams.DevicePolicy int getDevicePolicy(
+            int deviceId, @VirtualDeviceParams.PolicyType int policyType);
 }
diff --git a/services/core/java/com/android/server/connectivity/Vpn.java b/services/core/java/com/android/server/connectivity/Vpn.java
index 0741d46..bc9bc03 100644
--- a/services/core/java/com/android/server/connectivity/Vpn.java
+++ b/services/core/java/com/android/server/connectivity/Vpn.java
@@ -645,7 +645,8 @@
                 .addTransportType(NetworkCapabilities.TRANSPORT_VPN)
                 .removeCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VPN)
                 .addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VCN_MANAGED)
-                .setTransportInfo(new VpnTransportInfo(VpnManager.TYPE_VPN_NONE, null))
+                .setTransportInfo(new VpnTransportInfo(
+                        VpnManager.TYPE_VPN_NONE, null /* sessionId */, false /* bypassable */))
                 .build();
 
         loadAlwaysOnPackage();
@@ -709,7 +710,8 @@
     private void resetNetworkCapabilities() {
         mNetworkCapabilities = new NetworkCapabilities.Builder(mNetworkCapabilities)
                 .setUids(null)
-                .setTransportInfo(new VpnTransportInfo(VpnManager.TYPE_VPN_NONE, null))
+                .setTransportInfo(new VpnTransportInfo(
+                        VpnManager.TYPE_VPN_NONE, null /* sessionId */, false /* bypassable */))
                 .build();
     }
 
@@ -1567,7 +1569,8 @@
         capsBuilder.setUids(createUserAndRestrictedProfilesRanges(mUserId,
                 mConfig.allowedApplications, mConfig.disallowedApplications));
 
-        capsBuilder.setTransportInfo(new VpnTransportInfo(getActiveVpnType(), mConfig.session));
+        capsBuilder.setTransportInfo(
+                new VpnTransportInfo(getActiveVpnType(), mConfig.session, mConfig.allowBypass));
 
         // Only apps targeting Q and above can explicitly declare themselves as metered.
         // These VPNs are assumed metered unless they state otherwise.
diff --git a/services/core/java/com/android/server/display/DisplayPowerController.java b/services/core/java/com/android/server/display/DisplayPowerController.java
index 838bb53..d6f0fd0 100644
--- a/services/core/java/com/android/server/display/DisplayPowerController.java
+++ b/services/core/java/com/android/server/display/DisplayPowerController.java
@@ -1703,6 +1703,7 @@
         mTempBrightnessEvent.setRbcStrength(mCdsi != null
                 ? mCdsi.getReduceBrightColorsStrength() : -1);
         mTempBrightnessEvent.setPowerFactor(mPowerRequest.screenLowPowerBrightnessFactor);
+        mTempBrightnessEvent.setWasShortTermModelActive(hadUserBrightnessPoint);
         // Temporary is what we use during slider interactions. We avoid logging those so that
         // we don't spam logcat when the slider is being used.
         boolean tempToTempTransition =
@@ -1713,12 +1714,6 @@
                 || brightnessAdjustmentFlags != 0) {
             float lastBrightness = mLastBrightnessEvent.getBrightness();
             mTempBrightnessEvent.setInitialBrightness(lastBrightness);
-            mTempBrightnessEvent.setFastAmbientLux(
-                    mAutomaticBrightnessController == null
-                        ? -1f : mAutomaticBrightnessController.getFastAmbientLux());
-            mTempBrightnessEvent.setSlowAmbientLux(
-                    mAutomaticBrightnessController == null
-                        ? -1f : mAutomaticBrightnessController.getSlowAmbientLux());
             mTempBrightnessEvent.setAutomaticBrightnessEnabled(mPowerRequest.useAutoBrightness);
             mLastBrightnessEvent.copyFrom(mTempBrightnessEvent);
             BrightnessEvent newEvent = new BrightnessEvent(mTempBrightnessEvent);
@@ -2846,9 +2841,9 @@
             FrameworkStatsLog.write(FrameworkStatsLog.DISPLAY_BRIGHTNESS_CHANGED,
                     convertToNits(event.getInitialBrightness()),
                     convertToNits(event.getBrightness()),
-                    event.getSlowAmbientLux(),
+                    event.getLux(),
                     event.getPhysicalDisplayId(),
-                    event.isShortTermModelActive(),
+                    event.wasShortTermModelActive(),
                     appliedLowPowerMode,
                     appliedRbcStrength,
                     appliedHbmMaxNits,
diff --git a/services/core/java/com/android/server/display/DisplayPowerController2.java b/services/core/java/com/android/server/display/DisplayPowerController2.java
index bf0b388..300b589 100644
--- a/services/core/java/com/android/server/display/DisplayPowerController2.java
+++ b/services/core/java/com/android/server/display/DisplayPowerController2.java
@@ -1568,6 +1568,7 @@
         mTempBrightnessEvent.setRbcStrength(mCdsi != null
                 ? mCdsi.getReduceBrightColorsStrength() : -1);
         mTempBrightnessEvent.setPowerFactor(mPowerRequest.screenLowPowerBrightnessFactor);
+        mTempBrightnessEvent.setWasShortTermModelActive(hadUserBrightnessPoint);
         // Temporary is what we use during slider interactions. We avoid logging those so that
         // we don't spam logcat when the slider is being used.
         boolean tempToTempTransition =
@@ -1578,12 +1579,6 @@
                 || brightnessAdjustmentFlags != 0) {
             float lastBrightness = mLastBrightnessEvent.getBrightness();
             mTempBrightnessEvent.setInitialBrightness(lastBrightness);
-            mTempBrightnessEvent.setFastAmbientLux(
-                    mAutomaticBrightnessController == null
-                        ? -1f : mAutomaticBrightnessController.getFastAmbientLux());
-            mTempBrightnessEvent.setSlowAmbientLux(
-                    mAutomaticBrightnessController == null
-                        ? -1f : mAutomaticBrightnessController.getSlowAmbientLux());
             mTempBrightnessEvent.setAutomaticBrightnessEnabled(mPowerRequest.useAutoBrightness);
             mLastBrightnessEvent.copyFrom(mTempBrightnessEvent);
             BrightnessEvent newEvent = new BrightnessEvent(mTempBrightnessEvent);
@@ -2517,9 +2512,9 @@
             FrameworkStatsLog.write(FrameworkStatsLog.DISPLAY_BRIGHTNESS_CHANGED,
                     convertToNits(event.getInitialBrightness()),
                     convertToNits(event.getBrightness()),
-                    event.getSlowAmbientLux(),
+                    event.getLux(),
                     event.getPhysicalDisplayId(),
-                    event.isShortTermModelActive(),
+                    event.wasShortTermModelActive(),
                     appliedLowPowerMode,
                     appliedRbcStrength,
                     appliedHbmMaxNits,
@@ -2694,7 +2689,7 @@
                 int displayId, SensorManager sensorManager) {
             return new DisplayPowerProximityStateController(wakelockController, displayDeviceConfig,
                     looper, nudgeUpdatePowerState,
-                    displayId, sensorManager);
+                    displayId, sensorManager, /* injector= */ null);
         }
     }
 
diff --git a/services/core/java/com/android/server/display/DisplayPowerProximityStateController.java b/services/core/java/com/android/server/display/DisplayPowerProximityStateController.java
index 5b64dd5..a3433d9 100644
--- a/services/core/java/com/android/server/display/DisplayPowerProximityStateController.java
+++ b/services/core/java/com/android/server/display/DisplayPowerProximityStateController.java
@@ -30,6 +30,7 @@
 import android.view.Display;
 
 import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
 import com.android.server.display.utils.SensorUtils;
 
 import java.io.PrintWriter;
@@ -40,16 +41,22 @@
  * state changes.
  */
 public final class DisplayPowerProximityStateController {
-    private static final int MSG_PROXIMITY_SENSOR_DEBOUNCED = 1;
+    @VisibleForTesting
+    static final int MSG_PROXIMITY_SENSOR_DEBOUNCED = 1;
+    @VisibleForTesting
+    static final int PROXIMITY_UNKNOWN = -1;
+    @VisibleForTesting
+    static final int PROXIMITY_POSITIVE = 1;
+    @VisibleForTesting
+    static final int PROXIMITY_SENSOR_POSITIVE_DEBOUNCE_DELAY = 0;
+
     private static final int MSG_IGNORE_PROXIMITY = 2;
 
-    private static final int PROXIMITY_UNKNOWN = -1;
     private static final int PROXIMITY_NEGATIVE = 0;
-    private static final int PROXIMITY_POSITIVE = 1;
 
     private static final boolean DEBUG_PRETEND_PROXIMITY_SENSOR_ABSENT = false;
     // Proximity sensor debounce delay in milliseconds for positive transitions.
-    private static final int PROXIMITY_SENSOR_POSITIVE_DEBOUNCE_DELAY = 0;
+
     // Proximity sensor debounce delay in milliseconds for negative transitions.
     private static final int PROXIMITY_SENSOR_NEGATIVE_DEBOUNCE_DELAY = 250;
     // Trigger proximity if distance is less than 5 cm.
@@ -66,12 +73,13 @@
     private final DisplayPowerProximityStateHandler mHandler;
     // A runnable to execute the utility to update the power state.
     private final Runnable mNudgeUpdatePowerState;
+    private Clock mClock;
     // A listener which listen's to the events emitted by the proximity sensor.
     private final SensorEventListener mProximitySensorListener = new SensorEventListener() {
         @Override
         public void onSensorChanged(SensorEvent event) {
             if (mProximitySensorEnabled) {
-                final long time = SystemClock.uptimeMillis();
+                final long time = mClock.uptimeMillis();
                 final float distance = event.values[0];
                 boolean positive = distance >= 0.0f && distance < mProximityThreshold;
                 handleProximitySensorEvent(time, positive);
@@ -147,7 +155,12 @@
     public DisplayPowerProximityStateController(
             WakelockController wakeLockController, DisplayDeviceConfig displayDeviceConfig,
             Looper looper,
-            Runnable nudgeUpdatePowerState, int displayId, SensorManager sensorManager) {
+            Runnable nudgeUpdatePowerState, int displayId, SensorManager sensorManager,
+            Injector injector) {
+        if (injector == null) {
+            injector = new Injector();
+        }
+        mClock = injector.createClock();
         mWakelockController = wakeLockController;
         mHandler = new DisplayPowerProximityStateHandler(looper);
         mNudgeUpdatePowerState = nudgeUpdatePowerState;
@@ -239,7 +252,6 @@
                 setProximitySensorEnabled(false);
                 mWaitingForNegativeProximity = false;
             }
-
             if (mScreenOffBecauseOfProximity
                     && (mProximity != PROXIMITY_POSITIVE || mIgnoreProximityUntilChanged)) {
                 // The screen *was* off due to prox being near, but now it's "far" so lets turn
@@ -313,7 +325,7 @@
                 + mSkipRampBecauseOfProximityChangeToNegative);
     }
 
-    private void ignoreProximitySensorUntilChangedInternal() {
+    void ignoreProximitySensorUntilChangedInternal() {
         if (!mIgnoreProximityUntilChanged
                 && mProximity == PROXIMITY_POSITIVE) {
             // Only ignore if it is still reporting positive (near)
@@ -414,7 +426,7 @@
         if (mProximitySensorEnabled
                 && mPendingProximity != PROXIMITY_UNKNOWN
                 && mPendingProximityDebounceTime >= 0) {
-            final long now = SystemClock.uptimeMillis();
+            final long now = mClock.uptimeMillis();
             if (mPendingProximityDebounceTime <= now) {
                 if (mProximity != mPendingProximity) {
                     // if the status of the sensor changed, stop ignoring.
@@ -473,4 +485,66 @@
         }
     }
 
+    @VisibleForTesting
+    boolean getPendingWaitForNegativeProximityLocked() {
+        synchronized (mLock) {
+            return mPendingWaitForNegativeProximityLocked;
+        }
+    }
+
+    @VisibleForTesting
+    boolean getWaitingForNegativeProximity() {
+        return mWaitingForNegativeProximity;
+    }
+
+    @VisibleForTesting
+    boolean shouldIgnoreProximityUntilChanged() {
+        return mIgnoreProximityUntilChanged;
+    }
+
+    boolean isProximitySensorEnabled() {
+        return mProximitySensorEnabled;
+    }
+
+    @VisibleForTesting
+    Handler getHandler() {
+        return mHandler;
+    }
+
+    @VisibleForTesting
+    int getPendingProximity() {
+        return mPendingProximity;
+    }
+
+    @VisibleForTesting
+    int getProximity() {
+        return mProximity;
+    }
+
+
+    @VisibleForTesting
+    long getPendingProximityDebounceTime() {
+        return mPendingProximityDebounceTime;
+    }
+
+    @VisibleForTesting
+    SensorEventListener getProximitySensorListener() {
+        return mProximitySensorListener;
+    }
+
+    /** Functional interface for providing time. */
+    @VisibleForTesting
+    interface Clock {
+        /**
+         * Returns current time in milliseconds since boot, not counting time spent in deep sleep.
+         */
+        long uptimeMillis();
+    }
+
+    @VisibleForTesting
+    static class Injector {
+        Clock createClock() {
+            return () -> SystemClock.uptimeMillis();
+        }
+    }
 }
diff --git a/services/core/java/com/android/server/display/brightness/BrightnessEvent.java b/services/core/java/com/android/server/display/brightness/BrightnessEvent.java
index e3fa622..f19852b 100644
--- a/services/core/java/com/android/server/display/brightness/BrightnessEvent.java
+++ b/services/core/java/com/android/server/display/brightness/BrightnessEvent.java
@@ -39,8 +39,6 @@
     private String mPhysicalDisplayId;
     private long mTime;
     private float mLux;
-    private float mFastAmbientLux;
-    private float mSlowAmbientLux;
     private float mPreThresholdLux;
     private float mInitialBrightness;
     private float mBrightness;
@@ -51,6 +49,7 @@
     private int mRbcStrength;
     private float mThermalMax;
     private float mPowerFactor;
+    private boolean mWasShortTermModelActive;
     private int mFlags;
     private int mAdjustmentFlags;
     private boolean mAutomaticBrightnessEnabled;
@@ -76,8 +75,6 @@
         mTime = that.getTime();
         // Lux values
         mLux = that.getLux();
-        mFastAmbientLux = that.getFastAmbientLux();
-        mSlowAmbientLux = that.getSlowAmbientLux();
         mPreThresholdLux = that.getPreThresholdLux();
         // Brightness values
         mInitialBrightness = that.getInitialBrightness();
@@ -90,6 +87,7 @@
         mRbcStrength = that.getRbcStrength();
         mThermalMax = that.getThermalMax();
         mPowerFactor = that.getPowerFactor();
+        mWasShortTermModelActive = that.wasShortTermModelActive();
         mFlags = that.getFlags();
         mAdjustmentFlags = that.getAdjustmentFlags();
         // Auto-brightness setting
@@ -105,8 +103,6 @@
         mPhysicalDisplayId = "";
         // Lux values
         mLux = 0;
-        mFastAmbientLux = 0;
-        mSlowAmbientLux = 0;
         mPreThresholdLux = 0;
         // Brightness values
         mInitialBrightness = PowerManager.BRIGHTNESS_INVALID_FLOAT;
@@ -119,6 +115,7 @@
         mRbcStrength = 0;
         mThermalMax = PowerManager.BRIGHTNESS_MAX;
         mPowerFactor = 1f;
+        mWasShortTermModelActive = false;
         mFlags = 0;
         mAdjustmentFlags = 0;
         // Auto-brightness setting
@@ -140,10 +137,6 @@
                 && mDisplayId == that.mDisplayId
                 && mPhysicalDisplayId.equals(that.mPhysicalDisplayId)
                 && Float.floatToRawIntBits(mLux) == Float.floatToRawIntBits(that.mLux)
-                && Float.floatToRawIntBits(mFastAmbientLux)
-                == Float.floatToRawIntBits(that.mFastAmbientLux)
-                && Float.floatToRawIntBits(mSlowAmbientLux)
-                == Float.floatToRawIntBits(that.mSlowAmbientLux)
                 && Float.floatToRawIntBits(mPreThresholdLux)
                 == Float.floatToRawIntBits(that.mPreThresholdLux)
                 && Float.floatToRawIntBits(mInitialBrightness)
@@ -161,6 +154,7 @@
                 == Float.floatToRawIntBits(that.mThermalMax)
                 && Float.floatToRawIntBits(mPowerFactor)
                 == Float.floatToRawIntBits(that.mPowerFactor)
+                && mWasShortTermModelActive == that.mWasShortTermModelActive
                 && mFlags == that.mFlags
                 && mAdjustmentFlags == that.mAdjustmentFlags
                 && mAutomaticBrightnessEnabled == that.mAutomaticBrightnessEnabled;
@@ -182,14 +176,13 @@
                 + ", rcmdBrt=" + mRecommendedBrightness
                 + ", preBrt=" + mPreThresholdBrightness
                 + ", lux=" + mLux
-                + ", fastLux=" + mFastAmbientLux
-                + ", slowLux=" + mSlowAmbientLux
                 + ", preLux=" + mPreThresholdLux
                 + ", hbmMax=" + mHbmMax
                 + ", hbmMode=" + BrightnessInfo.hbmToString(mHbmMode)
                 + ", rbcStrength=" + mRbcStrength
                 + ", thrmMax=" + mThermalMax
                 + ", powerFactor=" + mPowerFactor
+                + ", wasShortTermModelActive=" + mWasShortTermModelActive
                 + ", flags=" + flagsToString()
                 + ", reason=" + mReason.toString(mAdjustmentFlags)
                 + ", autoBrightness=" + mAutomaticBrightnessEnabled;
@@ -240,22 +233,6 @@
         this.mLux = lux;
     }
 
-    public float getFastAmbientLux() {
-        return mFastAmbientLux;
-    }
-
-    public void setFastAmbientLux(float mFastAmbientLux) {
-        this.mFastAmbientLux = mFastAmbientLux;
-    }
-
-    public float getSlowAmbientLux() {
-        return mSlowAmbientLux;
-    }
-
-    public void setSlowAmbientLux(float mSlowAmbientLux) {
-        this.mSlowAmbientLux = mSlowAmbientLux;
-    }
-
     public float getPreThresholdLux() {
         return mPreThresholdLux;
     }
@@ -344,6 +321,20 @@
         return (mFlags & FLAG_LOW_POWER_MODE) != 0;
     }
 
+    /**
+     * Set whether the short term model was active before the brightness event.
+     */
+    public boolean setWasShortTermModelActive(boolean wasShortTermModelActive) {
+        return this.mWasShortTermModelActive = wasShortTermModelActive;
+    }
+
+    /**
+     * Returns whether the short term model was active before the brightness event.
+     */
+    public boolean wasShortTermModelActive() {
+        return this.mWasShortTermModelActive;
+    }
+
     public int getFlags() {
         return mFlags;
     }
@@ -352,10 +343,6 @@
         this.mFlags = flags;
     }
 
-    public boolean isShortTermModelActive() {
-        return (mFlags & FLAG_USER_SET) != 0;
-    }
-
     public int getAdjustmentFlags() {
         return mAdjustmentFlags;
     }
diff --git a/services/core/java/com/android/server/hdmi/HdmiUtils.java b/services/core/java/com/android/server/hdmi/HdmiUtils.java
index 573bf19..5646e1b 100644
--- a/services/core/java/com/android/server/hdmi/HdmiUtils.java
+++ b/services/core/java/com/android/server/hdmi/HdmiUtils.java
@@ -20,6 +20,8 @@
 import static com.android.server.hdmi.Constants.ADDR_BACKUP_2;
 import static com.android.server.hdmi.Constants.ADDR_TV;
 
+import static java.util.Map.entry;
+
 import android.annotation.Nullable;
 import android.hardware.hdmi.HdmiControlManager;
 import android.hardware.hdmi.HdmiDeviceInfo;
@@ -45,7 +47,6 @@
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collections;
-import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
 import java.util.Objects;
@@ -57,38 +58,34 @@
 
     private static final String TAG = "HdmiUtils";
 
-    private static final Map<Integer, List<Integer>> ADDRESS_TO_TYPE =
-            new HashMap<Integer, List<Integer>>() {
-                {
-                    put(Constants.ADDR_TV, Lists.newArrayList(HdmiDeviceInfo.DEVICE_TV));
-                    put(Constants.ADDR_RECORDER_1,
-                            Lists.newArrayList(HdmiDeviceInfo.DEVICE_RECORDER));
-                    put(Constants.ADDR_RECORDER_2,
-                            Lists.newArrayList(HdmiDeviceInfo.DEVICE_RECORDER));
-                    put(Constants.ADDR_TUNER_1, Lists.newArrayList(HdmiDeviceInfo.DEVICE_TUNER));
-                    put(Constants.ADDR_PLAYBACK_1,
-                            Lists.newArrayList(HdmiDeviceInfo.DEVICE_PLAYBACK));
-                    put(Constants.ADDR_AUDIO_SYSTEM,
-                            Lists.newArrayList(HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM));
-                    put(Constants.ADDR_TUNER_2, Lists.newArrayList(HdmiDeviceInfo.DEVICE_TUNER));
-                    put(Constants.ADDR_TUNER_3, Lists.newArrayList(HdmiDeviceInfo.DEVICE_TUNER));
-                    put(Constants.ADDR_PLAYBACK_2,
-                            Lists.newArrayList(HdmiDeviceInfo.DEVICE_PLAYBACK));
-                    put(Constants.ADDR_RECORDER_3,
-                            Lists.newArrayList(HdmiDeviceInfo.DEVICE_RECORDER));
-                    put(Constants.ADDR_TUNER_4, Lists.newArrayList(HdmiDeviceInfo.DEVICE_TUNER));
-                    put(Constants.ADDR_PLAYBACK_3,
-                            Lists.newArrayList(HdmiDeviceInfo.DEVICE_PLAYBACK));
-                    put(Constants.ADDR_BACKUP_1, Lists.newArrayList(HdmiDeviceInfo.DEVICE_PLAYBACK,
-                            HdmiDeviceInfo.DEVICE_RECORDER, HdmiDeviceInfo.DEVICE_TUNER,
-                            HdmiDeviceInfo.DEVICE_VIDEO_PROCESSOR));
-                    put(Constants.ADDR_BACKUP_2, Lists.newArrayList(HdmiDeviceInfo.DEVICE_PLAYBACK,
-                            HdmiDeviceInfo.DEVICE_RECORDER, HdmiDeviceInfo.DEVICE_TUNER,
-                            HdmiDeviceInfo.DEVICE_VIDEO_PROCESSOR));
-                    put(Constants.ADDR_SPECIFIC_USE, Lists.newArrayList(ADDR_TV));
-                    put(Constants.ADDR_UNREGISTERED, Collections.emptyList());
-                }
-            };
+    private static final Map<Integer, List<Integer>> ADDRESS_TO_TYPE = Map.ofEntries(
+            entry(Constants.ADDR_TV, Lists.newArrayList(HdmiDeviceInfo.DEVICE_TV)),
+            entry(Constants.ADDR_RECORDER_1,
+                    Lists.newArrayList(HdmiDeviceInfo.DEVICE_RECORDER)),
+            entry(Constants.ADDR_RECORDER_2,
+                    Lists.newArrayList(HdmiDeviceInfo.DEVICE_RECORDER)),
+            entry(Constants.ADDR_TUNER_1, Lists.newArrayList(HdmiDeviceInfo.DEVICE_TUNER)),
+            entry(Constants.ADDR_PLAYBACK_1,
+                    Lists.newArrayList(HdmiDeviceInfo.DEVICE_PLAYBACK)),
+            entry(Constants.ADDR_AUDIO_SYSTEM,
+                    Lists.newArrayList(HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM)),
+            entry(Constants.ADDR_TUNER_2, Lists.newArrayList(HdmiDeviceInfo.DEVICE_TUNER)),
+            entry(Constants.ADDR_TUNER_3, Lists.newArrayList(HdmiDeviceInfo.DEVICE_TUNER)),
+            entry(Constants.ADDR_PLAYBACK_2,
+                    Lists.newArrayList(HdmiDeviceInfo.DEVICE_PLAYBACK)),
+            entry(Constants.ADDR_RECORDER_3,
+                    Lists.newArrayList(HdmiDeviceInfo.DEVICE_RECORDER)),
+            entry(Constants.ADDR_TUNER_4, Lists.newArrayList(HdmiDeviceInfo.DEVICE_TUNER)),
+            entry(Constants.ADDR_PLAYBACK_3,
+                    Lists.newArrayList(HdmiDeviceInfo.DEVICE_PLAYBACK)),
+            entry(Constants.ADDR_BACKUP_1, Lists.newArrayList(HdmiDeviceInfo.DEVICE_PLAYBACK,
+                    HdmiDeviceInfo.DEVICE_RECORDER, HdmiDeviceInfo.DEVICE_TUNER,
+                    HdmiDeviceInfo.DEVICE_VIDEO_PROCESSOR)),
+            entry(Constants.ADDR_BACKUP_2, Lists.newArrayList(HdmiDeviceInfo.DEVICE_PLAYBACK,
+                    HdmiDeviceInfo.DEVICE_RECORDER, HdmiDeviceInfo.DEVICE_TUNER,
+                    HdmiDeviceInfo.DEVICE_VIDEO_PROCESSOR)),
+            entry(Constants.ADDR_SPECIFIC_USE, Lists.newArrayList(ADDR_TV)),
+            entry(Constants.ADDR_UNREGISTERED, Collections.emptyList()));
 
     private static final String[] DEFAULT_NAMES = {
         "TV",
diff --git a/services/core/java/com/android/server/input/InputManagerService.java b/services/core/java/com/android/server/input/InputManagerService.java
index cb615a9..8497dfb 100644
--- a/services/core/java/com/android/server/input/InputManagerService.java
+++ b/services/core/java/com/android/server/input/InputManagerService.java
@@ -1814,8 +1814,8 @@
      */
     public boolean transferTouchFocus(@NonNull IBinder fromChannelToken,
             @NonNull IBinder toChannelToken) {
-        Objects.nonNull(fromChannelToken);
-        Objects.nonNull(toChannelToken);
+        Objects.requireNonNull(fromChannelToken);
+        Objects.requireNonNull(toChannelToken);
         return mNative.transferTouchFocus(fromChannelToken, toChannelToken,
                 false /* isDragDrop */);
     }
diff --git a/services/core/java/com/android/server/location/contexthub/ContextHubTransactionManager.java b/services/core/java/com/android/server/location/contexthub/ContextHubTransactionManager.java
index 4f6d0d4..e46b8c0c 100644
--- a/services/core/java/com/android/server/location/contexthub/ContextHubTransactionManager.java
+++ b/services/core/java/com/android/server/location/contexthub/ContextHubTransactionManager.java
@@ -523,9 +523,9 @@
     @Override
     public String toString() {
         StringBuilder sb = new StringBuilder(100);
-        TransactionRecord[] arr;
+        ContextHubServiceTransaction[] arr;
         synchronized (this) {
-            arr = mTransactionQueue.toArray(new TransactionRecord[0]);
+            arr = mTransactionQueue.toArray(new ContextHubServiceTransaction[0]);
         }
         for (int i = 0; i < arr.length; i++) {
             sb.append(i + ": " + arr[i] + "\n");
diff --git a/services/core/java/com/android/server/location/gnss/GnssConfiguration.java b/services/core/java/com/android/server/location/gnss/GnssConfiguration.java
index b8abd98..77cd673 100644
--- a/services/core/java/com/android/server/location/gnss/GnssConfiguration.java
+++ b/services/core/java/com/android/server/location/gnss/GnssConfiguration.java
@@ -296,26 +296,24 @@
                 Log.e(TAG, "Unable to set " + CONFIG_ES_EXTENSION_SEC + ": " + mEsExtensionSec);
             }
 
-            Map<String, SetCarrierProperty> map = new HashMap<String, SetCarrierProperty>() {
-                {
-                    put(CONFIG_SUPL_VER, GnssConfiguration::native_set_supl_version);
-                    put(CONFIG_SUPL_MODE, GnssConfiguration::native_set_supl_mode);
+            Map<String, SetCarrierProperty> map = new HashMap<String, SetCarrierProperty>();
 
-                    if (isConfigSuplEsSupported(gnssConfigurationIfaceVersion)) {
-                        put(CONFIG_SUPL_ES, GnssConfiguration::native_set_supl_es);
-                    }
+            map.put(CONFIG_SUPL_VER, GnssConfiguration::native_set_supl_version);
+            map.put(CONFIG_SUPL_MODE, GnssConfiguration::native_set_supl_mode);
 
-                    put(CONFIG_LPP_PROFILE, GnssConfiguration::native_set_lpp_profile);
-                    put(CONFIG_A_GLONASS_POS_PROTOCOL_SELECT,
-                            GnssConfiguration::native_set_gnss_pos_protocol_select);
-                    put(CONFIG_USE_EMERGENCY_PDN_FOR_EMERGENCY_SUPL,
-                            GnssConfiguration::native_set_emergency_supl_pdn);
+            if (isConfigSuplEsSupported(gnssConfigurationIfaceVersion)) {
+                map.put(CONFIG_SUPL_ES, GnssConfiguration::native_set_supl_es);
+            }
 
-                    if (isConfigGpsLockSupported(gnssConfigurationIfaceVersion)) {
-                        put(CONFIG_GPS_LOCK, GnssConfiguration::native_set_gps_lock);
-                    }
-                }
-            };
+            map.put(CONFIG_LPP_PROFILE, GnssConfiguration::native_set_lpp_profile);
+            map.put(CONFIG_A_GLONASS_POS_PROTOCOL_SELECT,
+                    GnssConfiguration::native_set_gnss_pos_protocol_select);
+            map.put(CONFIG_USE_EMERGENCY_PDN_FOR_EMERGENCY_SUPL,
+                    GnssConfiguration::native_set_emergency_supl_pdn);
+
+            if (isConfigGpsLockSupported(gnssConfigurationIfaceVersion)) {
+                map.put(CONFIG_GPS_LOCK, GnssConfiguration::native_set_gps_lock);
+            }
 
             for (Entry<String, SetCarrierProperty> entry : map.entrySet()) {
                 String propertyName = entry.getKey();
diff --git a/services/core/java/com/android/server/media/MediaButtonReceiverHolder.java b/services/core/java/com/android/server/media/MediaButtonReceiverHolder.java
index dcdb881..72ce38b 100644
--- a/services/core/java/com/android/server/media/MediaButtonReceiverHolder.java
+++ b/services/core/java/com/android/server/media/MediaButtonReceiverHolder.java
@@ -275,6 +275,10 @@
                 String.valueOf(mComponentType));
     }
 
+    public ComponentName getComponentName() {
+        return mComponentName;
+    }
+
     @ComponentType
     private static int getComponentType(PendingIntent pendingIntent) {
         if (pendingIntent.isBroadcast()) {
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index c3a5558..d6b9bd5 100755
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -3797,13 +3797,13 @@
         }
 
         private void createNotificationChannelsImpl(String pkg, int uid,
-                ParceledListSlice channelsList) {
-            createNotificationChannelsImpl(pkg, uid, channelsList,
+                ParceledListSlice channelsList, boolean fromTargetApp) {
+            createNotificationChannelsImpl(pkg, uid, channelsList, fromTargetApp,
                     ActivityTaskManager.INVALID_TASK_ID);
         }
 
         private void createNotificationChannelsImpl(String pkg, int uid,
-                ParceledListSlice channelsList, int startingTaskId) {
+                ParceledListSlice channelsList, boolean fromTargetApp, int startingTaskId) {
             List<NotificationChannel> channels = channelsList.getList();
             final int channelsSize = channels.size();
             ParceledListSlice<NotificationChannel> oldChannels =
@@ -3815,7 +3815,7 @@
                 final NotificationChannel channel = channels.get(i);
                 Objects.requireNonNull(channel, "channel in list is null");
                 needsPolicyFileChange = mPreferencesHelper.createNotificationChannel(pkg, uid,
-                        channel, true /* fromTargetApp */,
+                        channel, fromTargetApp,
                         mConditionProviders.isPackageOrComponentAllowed(
                                 pkg, UserHandle.getUserId(uid)));
                 if (needsPolicyFileChange) {
@@ -3851,6 +3851,7 @@
         @Override
         public void createNotificationChannels(String pkg, ParceledListSlice channelsList) {
             checkCallerIsSystemOrSameApp(pkg);
+            boolean fromTargetApp = !isCallerSystemOrPhone();  // if not system, it's from the app
             int taskId = ActivityTaskManager.INVALID_TASK_ID;
             try {
                 int uid = mPackageManager.getPackageUid(pkg, 0,
@@ -3859,14 +3860,15 @@
             } catch (RemoteException e) {
                 // Do nothing
             }
-            createNotificationChannelsImpl(pkg, Binder.getCallingUid(), channelsList, taskId);
+            createNotificationChannelsImpl(pkg, Binder.getCallingUid(), channelsList, fromTargetApp,
+                    taskId);
         }
 
         @Override
         public void createNotificationChannelsForPackage(String pkg, int uid,
                 ParceledListSlice channelsList) {
             enforceSystemOrSystemUI("only system can call this");
-            createNotificationChannelsImpl(pkg, uid, channelsList);
+            createNotificationChannelsImpl(pkg, uid, channelsList, false /* fromTargetApp */);
         }
 
         @Override
@@ -3881,7 +3883,8 @@
                     CONVERSATION_CHANNEL_ID_FORMAT, parentId, conversationId));
             conversationChannel.setConversationId(parentId, conversationId);
             createNotificationChannelsImpl(
-                    pkg, uid, new ParceledListSlice(Arrays.asList(conversationChannel)));
+                    pkg, uid, new ParceledListSlice(Arrays.asList(conversationChannel)),
+                    false /* fromTargetApp */);
             mRankingHandler.requestSort();
             handleSavePolicyFile();
         }
@@ -4967,7 +4970,16 @@
             }
             enforcePolicyAccess(Binder.getCallingUid(), "addAutomaticZenRule");
 
-            return mZenModeHelper.addAutomaticZenRule(pkg, automaticZenRule,
+            // If the calling app is the system (from any user), take the package name from the
+            // rule's owner rather than from the caller's package.
+            String rulePkg = pkg;
+            if (isCallingAppIdSystem()) {
+                if (automaticZenRule.getOwner() != null) {
+                    rulePkg = automaticZenRule.getOwner().getPackageName();
+                }
+            }
+
+            return mZenModeHelper.addAutomaticZenRule(rulePkg, automaticZenRule,
                     "addAutomaticZenRule");
         }
 
@@ -9764,6 +9776,12 @@
         return uid == Process.SYSTEM_UID;
     }
 
+    protected boolean isCallingAppIdSystem() {
+        final int uid = Binder.getCallingUid();
+        final int appid = UserHandle.getAppId(uid);
+        return appid == Process.SYSTEM_UID;
+    }
+
     protected boolean isUidSystemOrPhone(int uid) {
         final int appid = UserHandle.getAppId(uid);
         return (appid == Process.SYSTEM_UID || appid == Process.PHONE_UID
diff --git a/services/core/java/com/android/server/notification/PreferencesHelper.java b/services/core/java/com/android/server/notification/PreferencesHelper.java
index bbbf452..444fef6 100644
--- a/services/core/java/com/android/server/notification/PreferencesHelper.java
+++ b/services/core/java/com/android/server/notification/PreferencesHelper.java
@@ -852,7 +852,9 @@
         Objects.requireNonNull(pkg);
         Objects.requireNonNull(group);
         Objects.requireNonNull(group.getId());
-        Objects.requireNonNull(!TextUtils.isEmpty(group.getName()));
+        if (TextUtils.isEmpty(group.getName())) {
+            throw new IllegalArgumentException("group.getName() can't be empty");
+        }
         boolean needsDndChange = false;
         synchronized (mPackagePreferences) {
             PackagePreferences r = getOrCreatePackagePreferencesLocked(pkg, uid);
@@ -916,7 +918,7 @@
                 throw new IllegalArgumentException("Reserved id");
             }
             NotificationChannel existing = r.channels.get(channel.getId());
-            if (existing != null && fromTargetApp) {
+            if (existing != null) {
                 // Actually modifying an existing channel - keep most of the existing settings
                 if (existing.isDeleted()) {
                     // The existing channel was deleted - undelete it.
@@ -1002,9 +1004,7 @@
                 }
                 if (fromTargetApp) {
                     channel.setLockscreenVisibility(r.visibility);
-                    channel.setAllowBubbles(existing != null
-                            ? existing.getAllowBubbles()
-                            : NotificationChannel.DEFAULT_ALLOW_BUBBLE);
+                    channel.setAllowBubbles(NotificationChannel.DEFAULT_ALLOW_BUBBLE);
                 }
                 clearLockedFieldsLocked(channel);
 
diff --git a/services/core/java/com/android/server/notification/ZenModeHelper.java b/services/core/java/com/android/server/notification/ZenModeHelper.java
index f2c78ad..4b2c88c 100644
--- a/services/core/java/com/android/server/notification/ZenModeHelper.java
+++ b/services/core/java/com/android/server/notification/ZenModeHelper.java
@@ -326,7 +326,7 @@
 
     public String addAutomaticZenRule(String pkg, AutomaticZenRule automaticZenRule,
             String reason) {
-        if (!isSystemRule(automaticZenRule)) {
+        if (!ZenModeConfig.SYSTEM_AUTHORITY.equals(pkg)) {
             PackageItemInfo component = getServiceInfo(automaticZenRule.getOwner());
             if (component == null) {
                 component = getActivityInfo(automaticZenRule.getConfigurationActivity());
@@ -582,11 +582,6 @@
         }
     }
 
-    private boolean isSystemRule(AutomaticZenRule rule) {
-        return rule.getOwner() != null
-                && ZenModeConfig.SYSTEM_AUTHORITY.equals(rule.getOwner().getPackageName());
-    }
-
     private ServiceInfo getServiceInfo(ComponentName owner) {
         Intent queryIntent = new Intent();
         queryIntent.setComponent(owner);
diff --git a/services/core/java/com/android/server/pm/DeletePackageHelper.java b/services/core/java/com/android/server/pm/DeletePackageHelper.java
index 88a3f8e..095a7f6 100644
--- a/services/core/java/com/android/server/pm/DeletePackageHelper.java
+++ b/services/core/java/com/android/server/pm/DeletePackageHelper.java
@@ -261,6 +261,7 @@
             final boolean killApp = (deleteFlags & PackageManager.DELETE_DONT_KILL_APP) == 0;
             info.sendPackageRemovedBroadcasts(killApp, removedBySystem);
             info.sendSystemPackageUpdatedBroadcasts();
+            PackageMetrics.onUninstallSucceeded(info, deleteFlags, mUserManagerInternal);
         }
 
         // Force a gc to clear up things.
diff --git a/services/core/java/com/android/server/pm/InstallPackageHelper.java b/services/core/java/com/android/server/pm/InstallPackageHelper.java
index 2a0b44d..70bd24c 100644
--- a/services/core/java/com/android/server/pm/InstallPackageHelper.java
+++ b/services/core/java/com/android/server/pm/InstallPackageHelper.java
@@ -2619,11 +2619,29 @@
                 }
             }
 
+            Bundle extras = new Bundle();
+            extras.putInt(Intent.EXTRA_UID, request.getUid());
+            if (update) {
+                extras.putBoolean(Intent.EXTRA_REPLACING, true);
+            }
+            extras.putInt(PackageInstaller.EXTRA_DATA_LOADER_TYPE, dataLoaderType);
+
+            // If a package is a static shared library, then only the installer of the package
+            // should get the broadcast.
+            if (installerPackageName != null
+                    && request.getPkg().getStaticSharedLibraryName() != null) {
+                mPm.sendPackageBroadcast(Intent.ACTION_PACKAGE_ADDED, packageName,
+                        extras, 0 /*flags*/,
+                        installerPackageName, null /*finishedReceiver*/,
+                        request.getNewUsers(), null /* instantUserIds*/,
+                        null /* broadcastAllowList */, null);
+            }
+
             // Send installed broadcasts if the package is not a static shared lib.
             if (request.getPkg().getStaticSharedLibraryName() == null) {
                 mPm.mProcessLoggingHandler.invalidateBaseApkHash(request.getPkg().getBaseApkPath());
 
-                // Send added for users that see the package for the first time
+                // Send PACKAGE_ADDED broadcast for users that see the package for the first time
                 // sendPackageAddedForNewUsers also deals with system apps
                 int appId = UserHandle.getAppId(request.getUid());
                 boolean isSystem = request.getPkg().isSystem();
@@ -2631,13 +2649,9 @@
                         isSystem || virtualPreload, virtualPreload /*startReceiver*/, appId,
                         firstUserIds, firstInstantUserIds, dataLoaderType);
 
-                // Send added for users that don't see the package for the first time
-                Bundle extras = new Bundle();
-                extras.putInt(Intent.EXTRA_UID, request.getUid());
-                if (update) {
-                    extras.putBoolean(Intent.EXTRA_REPLACING, true);
-                }
-                extras.putInt(PackageInstaller.EXTRA_DATA_LOADER_TYPE, dataLoaderType);
+                // Send PACKAGE_ADDED broadcast for users that don't see
+                // the package for the first time
+
                 // Send to all running apps.
                 final SparseArray<int[]> newBroadcastAllowList;
                 synchronized (mPm.mLock) {
@@ -2650,8 +2664,8 @@
                         extras, 0 /*flags*/,
                         null /*targetPackage*/, null /*finishedReceiver*/,
                         updateUserIds, instantUserIds, newBroadcastAllowList, null);
+                // Send to the installer, even if it's not running.
                 if (installerPackageName != null) {
-                    // Send to the installer, even if it's not running.
                     mPm.sendPackageBroadcast(Intent.ACTION_PACKAGE_ADDED, packageName,
                             extras, 0 /*flags*/,
                             installerPackageName, null /*finishedReceiver*/,
diff --git a/services/core/java/com/android/server/pm/PackageMetrics.java b/services/core/java/com/android/server/pm/PackageMetrics.java
index 0391163..cb87ff5 100644
--- a/services/core/java/com/android/server/pm/PackageMetrics.java
+++ b/services/core/java/com/android/server/pm/PackageMetrics.java
@@ -19,12 +19,13 @@
 import static android.os.Process.INVALID_UID;
 
 import android.annotation.IntDef;
+import android.content.pm.PackageManager;
 import android.content.pm.parsing.ApkLiteParseUtils;
-import android.os.UserManager;
 import android.util.Pair;
 import android.util.SparseArray;
 
 import com.android.internal.util.FrameworkStatsLog;
+import com.android.server.LocalServices;
 import com.android.server.pm.pkg.PackageStateInternal;
 
 import java.io.File;
@@ -73,6 +74,8 @@
     }
 
     private void reportInstallationStats(Computer snapshot, boolean success) {
+        UserManagerInternal userManagerInternal =
+                LocalServices.getService(UserManagerInternal.class);
         // TODO(b/249294752): do not log if adb
         final long installDurationMillis =
                 System.currentTimeMillis() - mInstallStartTimestampMillis;
@@ -93,9 +96,9 @@
                 success ? null : packageName /* not report package_name on success */,
                 mInstallRequest.getUid() /* uid */,
                 newUsers /* user_ids */,
-                getUserTypes(newUsers) /* user_types */,
+                userManagerInternal.getUserTypesForStatsd(newUsers) /* user_types */,
                 originalUsers /* original_user_ids */,
-                getUserTypes(originalUsers) /* original_user_types */,
+                userManagerInternal.getUserTypesForStatsd(originalUsers) /* original_user_types */,
                 mInstallRequest.getReturnCode() /* public_return_code */,
                 0 /* internal_error_code */,
                 apksSize /* apks_size_bytes */,
@@ -163,18 +166,6 @@
         return new Pair<>(stepsArray, durationsArray);
     }
 
-    private static int[] getUserTypes(int[] userIds) {
-        if (userIds == null) {
-            return null;
-        }
-        final int[] userTypes = new int[userIds.length];
-        for (int i = 0; i < userTypes.length; i++) {
-            String userType = UserManagerService.getInstance().getUserInfo(userIds[i]).userType;
-            userTypes[i] = UserManager.getUserTypeForStatsd(userType);
-        }
-        return userTypes;
-    }
-
     private static class InstallStep {
         private final long mStartTimestampMillis;
         private long mDurationMillis = -1;
@@ -191,4 +182,20 @@
             return mDurationMillis;
         }
     }
+
+    public static void onUninstallSucceeded(PackageRemovedInfo info, int deleteFlags,
+            UserManagerInternal userManagerInternal) {
+        if (info.mIsUpdate) {
+            // Not logging uninstalls caused by app updates
+            return;
+        }
+        final int[] removedUsers = info.mRemovedUsers;
+        final int[] removedUserTypes = userManagerInternal.getUserTypesForStatsd(removedUsers);
+        final int[] originalUsers = info.mOrigUsers;
+        final int[] originalUserTypes = userManagerInternal.getUserTypesForStatsd(originalUsers);
+        FrameworkStatsLog.write(FrameworkStatsLog.PACKAGE_UNINSTALLATION_REPORTED,
+                info.mUid, removedUsers, removedUserTypes, originalUsers, originalUserTypes,
+                deleteFlags, PackageManager.DELETE_SUCCEEDED, info.mIsRemovedPackageSystemUpdate,
+                !info.mRemovedForAllUsers);
+    }
 }
diff --git a/services/core/java/com/android/server/pm/PackageRemovedInfo.java b/services/core/java/com/android/server/pm/PackageRemovedInfo.java
index 3c863d0..4cac115 100644
--- a/services/core/java/com/android/server/pm/PackageRemovedInfo.java
+++ b/services/core/java/com/android/server/pm/PackageRemovedInfo.java
@@ -111,12 +111,6 @@
     }
 
     private void sendPackageRemovedBroadcastInternal(boolean killApp, boolean removedBySystem) {
-        // Don't send static shared library removal broadcasts as these
-        // libs are visible only the apps that depend on them an one
-        // cannot remove the library if it has a dependency.
-        if (mIsStaticSharedLib) {
-            return;
-        }
         Bundle extras = new Bundle();
         final int removedUid = mRemovedAppId >= 0  ? mRemovedAppId : mUid;
         extras.putInt(Intent.EXTRA_UID, removedUid);
@@ -128,15 +122,22 @@
             extras.putBoolean(Intent.EXTRA_REPLACING, true);
         }
         extras.putBoolean(Intent.EXTRA_REMOVED_FOR_ALL_USERS, mRemovedForAllUsers);
+
+        // Send PACKAGE_REMOVED broadcast to the respective installer.
+        if (mRemovedPackage != null && mInstallerPackageName != null) {
+            mPackageSender.sendPackageBroadcast(Intent.ACTION_PACKAGE_REMOVED,
+                    mRemovedPackage, extras, 0 /*flags*/,
+                    mInstallerPackageName, null, mBroadcastUsers, mInstantUserIds, null, null);
+        }
+        if (mIsStaticSharedLib) {
+            // When uninstalling static shared libraries, only the package's installer needs to be
+            // sent a PACKAGE_REMOVED broadcast. There are no other intended recipients.
+            return;
+        }
         if (mRemovedPackage != null) {
             mPackageSender.sendPackageBroadcast(Intent.ACTION_PACKAGE_REMOVED,
                     mRemovedPackage, extras, 0, null /*targetPackage*/, null,
                     mBroadcastUsers, mInstantUserIds, mBroadcastAllowList, null);
-            if (mInstallerPackageName != null) {
-                mPackageSender.sendPackageBroadcast(Intent.ACTION_PACKAGE_REMOVED,
-                        mRemovedPackage, extras, 0 /*flags*/,
-                        mInstallerPackageName, null, mBroadcastUsers, mInstantUserIds, null, null);
-            }
             mPackageSender.sendPackageBroadcast(Intent.ACTION_PACKAGE_REMOVED_INTERNAL,
                     mRemovedPackage, extras, 0 /*flags*/, PLATFORM_PACKAGE_NAME,
                     null /*finishedReceiver*/, mBroadcastUsers, mInstantUserIds,
diff --git a/services/core/java/com/android/server/pm/PerPackageReadTimeouts.java b/services/core/java/com/android/server/pm/PerPackageReadTimeouts.java
index 3b306a8..b310c62a 100644
--- a/services/core/java/com/android/server/pm/PerPackageReadTimeouts.java
+++ b/services/core/java/com/android/server/pm/PerPackageReadTimeouts.java
@@ -16,7 +16,7 @@
 
 package com.android.server.pm;
 
-import android.annotation.NonNull;;
+import android.annotation.NonNull;
 import android.text.TextUtils;
 
 import com.android.internal.util.HexDump;
diff --git a/services/core/java/com/android/server/pm/UserManagerInternal.java b/services/core/java/com/android/server/pm/UserManagerInternal.java
index 9155830..1420cbf 100644
--- a/services/core/java/com/android/server/pm/UserManagerInternal.java
+++ b/services/core/java/com/android/server/pm/UserManagerInternal.java
@@ -25,6 +25,7 @@
 import android.graphics.Bitmap;
 import android.os.Bundle;
 import android.os.UserManager;
+import android.util.DebugUtils;
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
@@ -46,6 +47,18 @@
     public @interface OwnerType {
     }
 
+    public static final int USER_ASSIGNMENT_RESULT_SUCCESS_VISIBLE = 1;
+    public static final int USER_ASSIGNMENT_RESULT_SUCCESS_INVISIBLE = 2;
+    public static final int USER_ASSIGNMENT_RESULT_FAILURE = -1;
+
+    private static final String PREFIX_USER_ASSIGNMENT_RESULT = "USER_ASSIGNMENT_RESULT";
+    @IntDef(flag = false, prefix = {PREFIX_USER_ASSIGNMENT_RESULT}, value = {
+            USER_ASSIGNMENT_RESULT_SUCCESS_VISIBLE,
+            USER_ASSIGNMENT_RESULT_SUCCESS_INVISIBLE,
+            USER_ASSIGNMENT_RESULT_FAILURE
+    })
+    public @interface UserAssignmentResult {}
+
     public interface UserRestrictionsListener {
         /**
          * Called when a user restriction changes.
@@ -343,34 +356,28 @@
     public abstract @Nullable UserProperties getUserProperties(@UserIdInt int userId);
 
     /**
-     * Assigns a user to a display.
-     *
-     * <p>On most devices this call will be a no-op, but it will be used on devices that support
-     * multiple users on multiple displays (like automotives with passenger displays).
+     * Assigns a user to a display when it's starting, returning whether the assignment succeeded
+     * and the user is {@link UserManager#isUserVisible() visible}.
      *
      * <p><b>NOTE: </b>this method is meant to be used only by {@code UserController} (when a user
-     * is started)
+     * is started). If other clients (like {@code CarService} need to explicitly change the user /
+     * display assignment, we'll need to provide other APIs.
      *
      * <p><b>NOTE: </b>this method doesn't validate if the display exists, it's up to the caller to
-     * check it. In fact, one of the intended clients for this method is
-     * {@code DisplayManagerService}, which will call it when a virtual display is created (another
-     * client is {@code UserController}, which will call it when a user is started).
+     * pass a valid display id.
      */
-    // TODO(b/244644281): rename to assignUserToDisplayOnStart() and make sure it's called on boot
-    // as well
-    public abstract void assignUserToDisplay(@UserIdInt int userId, @UserIdInt int profileGroupId,
+    public abstract @UserAssignmentResult int assignUserToDisplayOnStart(@UserIdInt int userId,
+            @UserIdInt int profileGroupId,
             boolean foreground, int displayId);
 
     /**
-     * Unassigns a user from its current display.
-     *
-     * <p>On most devices this call will be a no-op, but it will be used on devices that support
-     * multiple users on multiple displays (like automotives with passenger displays).
+     * Unassigns a user from its current display when it's stopping.
      *
      * <p><b>NOTE: </b>this method is meant to be used only by {@code UserController} (when a user
-     * is stopped).
+     * is stopped). If other clients (like {@code CarService} need to explicitly change the user /
+     * display assignment, we'll need to provide other APIs.
      */
-    public abstract void unassignUserFromDisplay(@UserIdInt int userId);
+    public abstract void unassignUserFromDisplayOnStop(@UserIdInt int userId);
 
     /**
      * Returns {@code true} if the user is visible (as defined by
@@ -413,6 +420,15 @@
      */
     public abstract @UserIdInt int getUserAssignedToDisplay(int displayId);
 
+    /**
+     * Gets the user-friendly representation of the {@code result} of a
+     * {@link #assignUserToDisplayOnStart(int, int, boolean, int)} call.
+     */
+    public static String userAssignmentResultToString(@UserAssignmentResult int result) {
+        return DebugUtils.constantToString(UserManagerInternal.class, PREFIX_USER_ASSIGNMENT_RESULT,
+                result);
+    }
+
     /** Adds a {@link UserVisibilityListener}. */
     public abstract void addUserVisibilityListener(UserVisibilityListener listener);
 
@@ -421,4 +437,8 @@
 
     /** TODO(b/244333150): temporary method until UserVisibilityMediator handles that logic */
     public abstract void onUserVisibilityChanged(@UserIdInt int userId, boolean visible);
+
+    /** Return the integer types of the given user IDs. Only used for reporting metrics to statsd.
+     */
+    public abstract int[] getUserTypesForStatsd(@UserIdInt int[] userIds);
 }
diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java
index 1c50f38..9f84ab0 100644
--- a/services/core/java/com/android/server/pm/UserManagerService.java
+++ b/services/core/java/com/android/server/pm/UserManagerService.java
@@ -265,7 +265,7 @@
     @VisibleForTesting
     static final int MAX_RECENTLY_REMOVED_IDS_SIZE = 100;
 
-    private static final int USER_VERSION = 10;
+    private static final int USER_VERSION = 11;
 
     private static final long EPOCH_PLUS_30_YEARS = 30L * 365 * 24 * 60 * 60 * 1000L; // ms
 
@@ -3355,6 +3355,7 @@
                 final int oldFlags = systemUserData.info.flags;
                 final int newFlags;
                 final String newUserType;
+                // TODO(b/256624031): Also handle FLAG_MAIN
                 if (newHeadlessSystemUserMode) {
                     newUserType = UserManager.USER_TYPE_SYSTEM_HEADLESS;
                     newFlags = oldFlags & ~UserInfo.FLAG_FULL;
@@ -3647,6 +3648,22 @@
             userVersion = 10;
         }
 
+        if (userVersion < 11) {
+            // Add FLAG_MAIN
+            if (isHeadlessSystemUserMode()) {
+                final UserInfo earliestCreatedUser = getEarliestCreatedFullUser();
+                earliestCreatedUser.flags |= UserInfo.FLAG_MAIN;
+                userIdsToWrite.add(earliestCreatedUser.id);
+            } else {
+                synchronized (mUsersLock) {
+                    final UserData userData = mUsers.get(UserHandle.USER_SYSTEM);
+                    userData.info.flags |= UserInfo.FLAG_MAIN;
+                    userIdsToWrite.add(userData.info.id);
+                }
+            }
+            userVersion = 11;
+        }
+
         // Reminder: If you add another upgrade, make sure to increment USER_VERSION too.
 
         // Done with userVersion changes, moving on to deal with userTypeVersion upgrades
@@ -3776,6 +3793,21 @@
         userInfo.profileBadge = getFreeProfileBadgeLU(userInfo.profileGroupId, userInfo.userType);
     }
 
+    private UserInfo getEarliestCreatedFullUser() {
+        final List<UserInfo> users = getUsersInternal(true, true, true);
+        UserInfo earliestUser = users.get(0);
+        long earliestCreationTime = earliestUser.creationTime;
+        for (int i = 0; i < users.size(); i++) {
+            final UserInfo info = users.get(i);
+            if (info.isFull() && info.isAdmin() && info.creationTime > 0
+                    && info.creationTime < earliestCreationTime) {
+                earliestCreationTime = info.creationTime;
+                earliestUser = info;
+            }
+        }
+        return earliestUser;
+    }
+
     @GuardedBy({"mPackagesLock"})
     private void fallbackToSingleUserLP() {
         int flags = UserInfo.FLAG_SYSTEM | UserInfo.FLAG_INITIALIZED | UserInfo.FLAG_ADMIN
@@ -6802,15 +6834,14 @@
         }
 
         @Override
-        public void assignUserToDisplay(@UserIdInt int userId, @UserIdInt int profileGroupId,
+        public int assignUserToDisplayOnStart(@UserIdInt int userId, @UserIdInt int profileGroupId,
                 boolean foreground, int displayId) {
-            mUserVisibilityMediator.startUser(userId, profileGroupId, foreground, displayId);
-            mUserVisibilityMediator.assignUserToDisplay(userId, profileGroupId, displayId);
+            return mUserVisibilityMediator.startUser(userId, profileGroupId, foreground,
+                    displayId);
         }
 
         @Override
-        public void unassignUserFromDisplay(@UserIdInt int userId) {
-            mUserVisibilityMediator.unassignUserFromDisplay(userId);
+        public void unassignUserFromDisplayOnStop(@UserIdInt int userId) {
             mUserVisibilityMediator.stopUser(userId);
         }
 
@@ -6862,6 +6893,24 @@
                 }
             });
         }
+
+        @Override
+        public int[] getUserTypesForStatsd(@UserIdInt int[] userIds) {
+            if (userIds == null) {
+                return null;
+            }
+            final int[] userTypes = new int[userIds.length];
+            for (int i = 0; i < userTypes.length; i++) {
+                final UserInfo userInfo = getUserInfo(userIds[i]);
+                if (userInfo == null) {
+                    // Not possible because the input user ids should all be valid
+                    userTypes[i] = UserManager.getUserTypeForStatsd("");
+                } else {
+                    userTypes[i] = UserManager.getUserTypeForStatsd(userInfo.userType);
+                }
+            }
+            return userTypes;
+        }
     } // class LocalService
 
 
diff --git a/services/core/java/com/android/server/pm/UserTypeFactory.java b/services/core/java/com/android/server/pm/UserTypeFactory.java
index 871420a9..8fb5773 100644
--- a/services/core/java/com/android/server/pm/UserTypeFactory.java
+++ b/services/core/java/com/android/server/pm/UserTypeFactory.java
@@ -262,7 +262,8 @@
     private static UserTypeDetails.Builder getDefaultTypeFullSystem() {
         return new UserTypeDetails.Builder()
                 .setName(USER_TYPE_FULL_SYSTEM)
-                .setBaseType(FLAG_SYSTEM | FLAG_FULL);
+                .setBaseType(FLAG_SYSTEM | FLAG_FULL)
+                .setDefaultUserInfoPropertyFlags(UserInfo.FLAG_MAIN);
     }
 
     /**
diff --git a/services/core/java/com/android/server/pm/UserVisibilityMediator.java b/services/core/java/com/android/server/pm/UserVisibilityMediator.java
index bd81062..cbf7dfe 100644
--- a/services/core/java/com/android/server/pm/UserVisibilityMediator.java
+++ b/services/core/java/com/android/server/pm/UserVisibilityMediator.java
@@ -20,12 +20,15 @@
 import static android.os.UserHandle.USER_SYSTEM;
 import static android.view.Display.DEFAULT_DISPLAY;
 
-import android.annotation.IntDef;
+import static com.android.server.pm.UserManagerInternal.USER_ASSIGNMENT_RESULT_FAILURE;
+import static com.android.server.pm.UserManagerInternal.USER_ASSIGNMENT_RESULT_SUCCESS_INVISIBLE;
+import static com.android.server.pm.UserManagerInternal.USER_ASSIGNMENT_RESULT_SUCCESS_VISIBLE;
+import static com.android.server.pm.UserManagerInternal.userAssignmentResultToString;
+
 import android.annotation.Nullable;
 import android.annotation.UserIdInt;
 import android.os.UserHandle;
 import android.os.UserManager;
-import android.util.DebugUtils;
 import android.util.Dumpable;
 import android.util.IndentingPrintWriter;
 import android.util.SparseIntArray;
@@ -34,6 +37,7 @@
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.util.Preconditions;
+import com.android.server.pm.UserManagerInternal.UserAssignmentResult;
 import com.android.server.utils.Slogf;
 
 import java.io.PrintWriter;
@@ -52,23 +56,10 @@
 
     private static final String TAG = UserVisibilityMediator.class.getSimpleName();
 
-    private static final String PREFIX_START_USER_RESULT = "START_USER_";
-
     // TODO(b/242195409): might need to change this if boot logic is refactored for HSUM devices
     @VisibleForTesting
     static final int INITIAL_CURRENT_USER_ID = USER_SYSTEM;
 
-    public static final int START_USER_RESULT_SUCCESS_VISIBLE = 1;
-    public static final int START_USER_RESULT_SUCCESS_INVISIBLE = 2;
-    public static final int START_USER_RESULT_FAILURE = -1;
-
-    @IntDef(flag = false, prefix = {PREFIX_START_USER_RESULT}, value = {
-            START_USER_RESULT_SUCCESS_VISIBLE,
-            START_USER_RESULT_SUCCESS_INVISIBLE,
-            START_USER_RESULT_FAILURE
-    })
-    public @interface StartUserResult {}
-
     private final Object mLock = new Object();
 
     private final boolean mUsersOnSecondaryDisplaysEnabled;
@@ -97,10 +88,37 @@
     }
 
     /**
-     * TODO(b/244644281): merge with assignUserToDisplay() or add javadoc.
+     * See {@link UserManagerInternal#assignUserToDisplayOnStart(int, int, boolean, int)}.
      */
-    public @StartUserResult int startUser(@UserIdInt int userId, @UserIdInt int profileGroupId,
+    public @UserAssignmentResult int startUser(@UserIdInt int userId, @UserIdInt int profileGroupId,
             boolean foreground, int displayId) {
+        // TODO(b/244644281): this method need to perform 4 actions:
+        //
+        // 1. Check if the user can be started given the provided arguments
+        // 2. If it can, decide whether it's visible or not (which is the return value)
+        // 3. Update the current user / profiles state
+        // 4. Update the users on secondary display state (if applicable)
+        //
+        // Ideally, they should be done "atomically" (i.e, only changing state while holding the
+        // mLock), but the initial implementation is just calling the existing methods, as the
+        // focus is to change the UserController startUser() workflow (so it relies on this class
+        // for the logic above).
+        //
+        // The next CL will refactor it (and the unit tests) to achieve that atomicity.
+        int result = startOnly(userId, profileGroupId, foreground, displayId);
+        if (result != USER_ASSIGNMENT_RESULT_FAILURE) {
+            assignUserToDisplay(userId, profileGroupId, displayId);
+        }
+        return result;
+    }
+
+    /**
+     * @deprecated - see comment inside {@link #startUser(int, int, boolean, int)}
+     */
+    @Deprecated
+    @VisibleForTesting
+    @UserAssignmentResult int startOnly(@UserIdInt int userId,
+            @UserIdInt int profileGroupId, boolean foreground, int displayId) {
         int actualProfileGroupId = profileGroupId == NO_PROFILE_GROUP_ID
                 ? userId
                 : profileGroupId;
@@ -111,7 +129,7 @@
         if (foreground && displayId != DEFAULT_DISPLAY) {
             Slogf.w(TAG, "startUser(%d, %d, %b, %d) failed: cannot start foreground user on "
                     + "secondary display", userId, actualProfileGroupId, foreground, displayId);
-            return START_USER_RESULT_FAILURE;
+            return USER_ASSIGNMENT_RESULT_FAILURE;
         }
 
         int visibility;
@@ -121,13 +139,12 @@
                     Slogf.w(TAG, "startUser(%d, %d, %b, %d) failed: cannot start profile user on "
                             + "secondary display", userId, actualProfileGroupId, foreground,
                             displayId);
-                    return START_USER_RESULT_FAILURE;
+                    return USER_ASSIGNMENT_RESULT_FAILURE;
                 }
                 if (foreground) {
                     Slogf.w(TAG, "startUser(%d, %d, %b, %d) failed: cannot start profile user in "
-                            + "foreground", userId, actualProfileGroupId, foreground,
-                            displayId);
-                    return START_USER_RESULT_FAILURE;
+                            + "foreground", userId, actualProfileGroupId, foreground, displayId);
+                    return USER_ASSIGNMENT_RESULT_FAILURE;
                 } else {
                     boolean isParentRunning = mStartedProfileGroupIds
                             .get(actualProfileGroupId) == actualProfileGroupId;
@@ -135,18 +152,18 @@
                         Slogf.d(TAG, "profile parent running: %b", isParentRunning);
                     }
                     visibility = isParentRunning
-                            ? START_USER_RESULT_SUCCESS_VISIBLE
-                            : START_USER_RESULT_SUCCESS_INVISIBLE;
+                            ? USER_ASSIGNMENT_RESULT_SUCCESS_VISIBLE
+                            : USER_ASSIGNMENT_RESULT_SUCCESS_INVISIBLE;
                 }
             } else if (foreground) {
                 mCurrentUserId = userId;
-                visibility = START_USER_RESULT_SUCCESS_VISIBLE;
+                visibility = USER_ASSIGNMENT_RESULT_SUCCESS_VISIBLE;
             } else {
-                visibility = START_USER_RESULT_SUCCESS_INVISIBLE;
+                visibility = USER_ASSIGNMENT_RESULT_SUCCESS_INVISIBLE;
             }
             if (DBG) {
                 Slogf.d(TAG, "adding user / profile mapping (%d -> %d) and returning %s",
-                        userId, actualProfileGroupId, startUserResultToString(visibility));
+                        userId, actualProfileGroupId, userAssignmentResultToString(visibility));
             }
             mStartedProfileGroupIds.put(userId, actualProfileGroupId);
         }
@@ -154,21 +171,11 @@
     }
 
     /**
-     * TODO(b/244644281): merge with unassignUserFromDisplay() or add javadoc (and unit tests)
+     * @deprecated - see comment inside {@link #startUser(int, int, boolean, int)}
      */
-    public void stopUser(@UserIdInt int userId) {
-        if (DBG) {
-            Slogf.d(TAG, "stopUser(%d)", userId);
-        }
-        synchronized (mLock) {
-            mStartedProfileGroupIds.delete(userId);
-        }
-    }
-
-    /**
-     * See {@link UserManagerInternal#assignUserToDisplay(int, int)}.
-     */
-    public void assignUserToDisplay(int userId, int profileGroupId, int displayId) {
+    @Deprecated
+    @VisibleForTesting
+    void assignUserToDisplay(int userId, int profileGroupId, int displayId) {
         if (DBG) {
             Slogf.d(TAG, "assignUserToDisplay(%d, %d): mUsersOnSecondaryDisplaysEnabled=%b",
                     userId, displayId, mUsersOnSecondaryDisplaysEnabled);
@@ -246,23 +253,26 @@
     }
 
     /**
-     * See {@link UserManagerInternal#unassignUserFromDisplay(int)}.
+     * See {@link UserManagerInternal#unassignUserFromDisplayOnStop(int)}.
      */
-    public void unassignUserFromDisplay(int userId) {
+    public void stopUser(int userId) {
         if (DBG) {
-            Slogf.d(TAG, "unassignUserFromDisplay(%d)", userId);
+            Slogf.d(TAG, "stopUser(%d)", userId);
         }
-        if (!mUsersOnSecondaryDisplaysEnabled) {
-            // Don't need to do anything because methods (such as isUserVisible()) already know
-            // that the current user (and their profiles) is assigned to the default display.
-            if (DBG) {
-                Slogf.d(TAG, "ignoring when device doesn't support MUMD");
-            }
-            return;
-        }
-
         synchronized (mLock) {
             if (DBG) {
+                Slogf.d(TAG, "Removing %d from mStartedProfileGroupIds (%s)", userId,
+                        mStartedProfileGroupIds);
+            }
+            mStartedProfileGroupIds.delete(userId);
+
+            if (!mUsersOnSecondaryDisplaysEnabled) {
+                // Don't need to do update mUsersOnSecondaryDisplays because methods (such as
+                // isUserVisible()) already know that the current user (and their profiles) is
+                // assigned to the default display.
+                return;
+            }
+            if (DBG) {
                 Slogf.d(TAG, "Removing %d from mUsersOnSecondaryDisplays (%s)", userId,
                         mUsersOnSecondaryDisplays);
             }
@@ -395,33 +405,40 @@
             ipw.print("Current user id: ");
             ipw.println(mCurrentUserId);
 
-            ipw.print("Number of started user / profile group mappings: ");
-            ipw.println(mStartedProfileGroupIds.size());
-            if (mStartedProfileGroupIds.size() > 0) {
-                ipw.increaseIndent();
-                for (int i = 0; i < mStartedProfileGroupIds.size(); i++) {
-                    ipw.print("User #");
-                    ipw.print(mStartedProfileGroupIds.keyAt(i));
-                    ipw.print(" -> profile #");
-                    ipw.println(mStartedProfileGroupIds.valueAt(i));
-                }
-                ipw.decreaseIndent();
-            }
+            dumpIntArray(ipw, mStartedProfileGroupIds, "started user / profile group", "u", "pg");
 
-            ipw.print("Supports users on secondary displays: ");
+            ipw.print("Supports background users on secondary displays: ");
             ipw.println(mUsersOnSecondaryDisplaysEnabled);
 
             if (mUsersOnSecondaryDisplaysEnabled) {
-                ipw.print("Users on secondary displays: ");
-                synchronized (mLock) {
-                    ipw.println(mUsersOnSecondaryDisplays);
-                }
+                dumpIntArray(ipw, mUsersOnSecondaryDisplays, "background user / secondary display",
+                        "u", "d");
             }
         }
 
         ipw.decreaseIndent();
     }
 
+    private static void dumpIntArray(IndentingPrintWriter ipw, SparseIntArray array,
+            String arrayDescription, String keyName, String valueName) {
+        ipw.print("Number of ");
+        ipw.print(arrayDescription);
+        ipw.print(" mappings: ");
+        ipw.println(array.size());
+        if (array.size() <= 0) {
+            return;
+        }
+        ipw.increaseIndent();
+        for (int i = 0; i < array.size(); i++) {
+            ipw.print(keyName); ipw.print(':');
+            ipw.print(array.keyAt(i));
+            ipw.print(" -> ");
+            ipw.print(valueName); ipw.print(':');
+            ipw.println(array.valueAt(i));
+        }
+        ipw.decreaseIndent();
+    }
+
     @Override
     public void dump(PrintWriter pw, String[] args) {
         if (pw instanceof IndentingPrintWriter) {
@@ -445,14 +462,6 @@
         return map;
     }
 
-    /**
-     * Gets the user-friendly representation of the {@code result}.
-     */
-    public static String startUserResultToString(@StartUserResult int result) {
-        return DebugUtils.constantToString(UserVisibilityMediator.class, PREFIX_START_USER_RESULT,
-                result);
-    }
-
     // TODO(b/244644281): methods below are needed because some APIs use the current users (full and
     // profiles) state to decide whether a user is visible or not. If we decide to always store that
     // info into intermediate maps, we should remove them.
@@ -483,6 +492,14 @@
     }
 
     @VisibleForTesting
+    boolean isStartedUser(@UserIdInt int userId) {
+        synchronized (mLock) {
+            return mStartedProfileGroupIds.get(userId,
+                    INITIAL_CURRENT_USER_ID) != INITIAL_CURRENT_USER_ID;
+        }
+    }
+
+    @VisibleForTesting
     boolean isStartedProfile(@UserIdInt int userId) {
         int profileGroupId;
         synchronized (mLock) {
diff --git a/services/core/java/com/android/server/pm/dex/DexoptUtils.java b/services/core/java/com/android/server/pm/dex/DexoptUtils.java
index 5ba209d..9bca155 100644
--- a/services/core/java/com/android/server/pm/dex/DexoptUtils.java
+++ b/services/core/java/com/android/server/pm/dex/DexoptUtils.java
@@ -295,6 +295,7 @@
      * NOTE: Keep this in sync with the dexopt expectations! Right now that is either "PCL[path]"
      * for a PathClassLoader or "DLC[path]" for a DelegateLastClassLoader.
      */
+    @SuppressWarnings("ReturnValueIgnored")
     /*package*/ static String encodeClassLoader(String classpath, String classLoaderName) {
         classpath.getClass();  // Throw NPE if classpath is null
         String classLoaderDexoptEncoding = classLoaderName;
diff --git a/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java b/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java
index 28f86ed..83e17a5 100644
--- a/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java
+++ b/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java
@@ -86,6 +86,7 @@
 import java.util.Collections;
 import java.util.List;
 import java.util.Map;
+import java.util.Objects;
 import java.util.Set;
 
 /**
@@ -165,6 +166,11 @@
         COARSE_BACKGROUND_LOCATION_PERMISSIONS.add(Manifest.permission.ACCESS_BACKGROUND_LOCATION);
     }
 
+    private static final Set<String> FINE_LOCATION_PERMISSIONS = new ArraySet<>();
+    static {
+        FINE_LOCATION_PERMISSIONS.add(Manifest.permission.ACCESS_FINE_LOCATION);
+    }
+
     private static final Set<String> ACTIVITY_RECOGNITION_PERMISSIONS = new ArraySet<>();
     static {
         ACTIVITY_RECOGNITION_PERMISSIONS.add(Manifest.permission.ACTIVITY_RECOGNITION);
@@ -787,6 +793,8 @@
                         CONTACTS_PERMISSIONS, CALENDAR_PERMISSIONS, MICROPHONE_PERMISSIONS,
                         PHONE_PERMISSIONS, SMS_PERMISSIONS, COARSE_BACKGROUND_LOCATION_PERMISSIONS,
                         NEARBY_DEVICES_PERMISSIONS, NOTIFICATION_PERMISSIONS);
+                revokeRuntimePermissions(pm, voiceInteractPackageName, FINE_LOCATION_PERMISSIONS,
+                        false, userId);
             }
         }
 
@@ -1932,7 +1940,7 @@
                             mPkgRequestingPerm, newRestrictionExcemptFlags, -1, mUser);
                 }
 
-                if (newGranted != null && newGranted != mOriginalGranted) {
+                if (newGranted != null && !Objects.equals(newGranted, mOriginalGranted)) {
                     if (newGranted) {
                         NO_PM_CACHE.grantPermission(mPermission, mPkgRequestingPerm, mUser);
                     } else {
diff --git a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
index 5a638bb..9ec63fc 100644
--- a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
+++ b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
@@ -1101,7 +1101,7 @@
                     if (resolvedPackageName == null) {
                         return;
                     }
-                    appOpsManager.finishOp(accessorSource.getToken(), op,
+                    appOpsManager.finishOp(attributionSourceState.token, op,
                             accessorSource.getUid(), resolvedPackageName,
                             accessorSource.getAttributionTag());
                 } else {
@@ -1110,8 +1110,9 @@
                     if (resolvedAttributionSource.getPackageName() == null) {
                         return;
                     }
-                    appOpsManager.finishProxyOp(AppOpsManager.opToPublicName(op),
-                            resolvedAttributionSource, skipCurrentFinish);
+                    appOpsManager.finishProxyOp(attributionSourceState.token,
+                            AppOpsManager.opToPublicName(op), resolvedAttributionSource,
+                            skipCurrentFinish);
                 }
                 RegisteredAttribution registered =
                         sRunningAttributionSources.remove(current.getToken());
@@ -1227,10 +1228,11 @@
                         && next.getNext() == null);
                 final boolean selfAccess = singleReceiverFromDatasource || next == null;
 
-                final int opMode = performOpTransaction(context, op, current, message,
-                        forDataDelivery, /*startDataDelivery*/ false, skipCurrentChecks,
-                        selfAccess, singleReceiverFromDatasource, AppOpsManager.OP_NONE,
-                        AppOpsManager.ATTRIBUTION_FLAGS_NONE, AppOpsManager.ATTRIBUTION_FLAGS_NONE,
+                final int opMode = performOpTransaction(context, attributionSource.getToken(), op,
+                        current, message, forDataDelivery, /*startDataDelivery*/ false,
+                        skipCurrentChecks, selfAccess, singleReceiverFromDatasource,
+                        AppOpsManager.OP_NONE, AppOpsManager.ATTRIBUTION_FLAGS_NONE,
+                        AppOpsManager.ATTRIBUTION_FLAGS_NONE,
                         AppOpsManager.ATTRIBUTION_CHAIN_ID_NONE);
 
                 switch (opMode) {
@@ -1333,10 +1335,10 @@
                         attributionSource, next, fromDatasource, startDataDelivery, selfAccess,
                         isLinkTrusted) : ATTRIBUTION_FLAGS_NONE;
 
-                final int opMode = performOpTransaction(context, op, current, message,
-                        forDataDelivery, startDataDelivery, skipCurrentChecks, selfAccess,
-                        singleReceiverFromDatasource, attributedOp, proxyAttributionFlags,
-                        proxiedAttributionFlags, attributionChainId);
+                final int opMode = performOpTransaction(context, attributionSource.getToken(), op,
+                        current, message, forDataDelivery, startDataDelivery, skipCurrentChecks,
+                        selfAccess, singleReceiverFromDatasource, attributedOp,
+                        proxyAttributionFlags, proxiedAttributionFlags, attributionChainId);
 
                 switch (opMode) {
                     case AppOpsManager.MODE_ERRORED: {
@@ -1481,8 +1483,8 @@
                         attributionSource, next, /*fromDatasource*/ false, startDataDelivery,
                         selfAccess, isLinkTrusted) : ATTRIBUTION_FLAGS_NONE;
 
-                final int opMode = performOpTransaction(context, op, current, message,
-                        forDataDelivery, startDataDelivery, skipCurrentChecks, selfAccess,
+                final int opMode = performOpTransaction(context, current.getToken(), op, current,
+                        message, forDataDelivery, startDataDelivery, skipCurrentChecks, selfAccess,
                         /*fromDatasource*/ false, AppOpsManager.OP_NONE, proxyAttributionFlags,
                         proxiedAttributionFlags, attributionChainId);
 
@@ -1504,7 +1506,8 @@
         }
 
         @SuppressWarnings("ConstantConditions")
-        private static int performOpTransaction(@NonNull Context context, int op,
+        private static int performOpTransaction(@NonNull Context context,
+                @NonNull IBinder chainStartToken, int op,
                 @NonNull AttributionSource attributionSource, @Nullable String message,
                 boolean forDataDelivery, boolean startDataDelivery, boolean skipProxyOperation,
                 boolean selfAccess, boolean singleReceiverFromDatasource, int attributedOp,
@@ -1566,7 +1569,7 @@
                 if (selfAccess) {
                     try {
                         startedOpResult = appOpsManager.startOpNoThrow(
-                                resolvedAttributionSource.getToken(), startedOp,
+                                chainStartToken, startedOp,
                                 resolvedAttributionSource.getUid(),
                                 resolvedAttributionSource.getPackageName(),
                                 /*startIfModeDefault*/ false,
@@ -1577,14 +1580,14 @@
                                 + " platform defined runtime permission "
                                 + AppOpsManager.opToPermission(op) + " while not having "
                                 + Manifest.permission.UPDATE_APP_OPS_STATS);
-                        startedOpResult = appOpsManager.startProxyOpNoThrow(attributedOp,
-                                attributionSource, message, skipProxyOperation,
+                        startedOpResult = appOpsManager.startProxyOpNoThrow(chainStartToken,
+                                attributedOp, attributionSource, message, skipProxyOperation,
                                 proxyAttributionFlags, proxiedAttributionFlags, attributionChainId);
                     }
                 } else {
                     try {
-                        startedOpResult = appOpsManager.startProxyOpNoThrow(startedOp,
-                                resolvedAttributionSource, message, skipProxyOperation,
+                        startedOpResult = appOpsManager.startProxyOpNoThrow(chainStartToken,
+                                startedOp, resolvedAttributionSource, message, skipProxyOperation,
                                 proxyAttributionFlags, proxiedAttributionFlags, attributionChainId);
                     } catch (SecurityException e) {
                         //TODO 195339480: remove
diff --git a/services/core/java/com/android/server/policy/AppOpsPolicy.java b/services/core/java/com/android/server/policy/AppOpsPolicy.java
index a6d148c..383249f 100644
--- a/services/core/java/com/android/server/policy/AppOpsPolicy.java
+++ b/services/core/java/com/android/server/policy/AppOpsPolicy.java
@@ -45,13 +45,11 @@
 import android.util.SparseArray;
 
 import com.android.internal.annotations.GuardedBy;
-import com.android.internal.util.function.DecFunction;
 import com.android.internal.util.function.HeptFunction;
 import com.android.internal.util.function.HexFunction;
 import com.android.internal.util.function.QuadFunction;
 import com.android.internal.util.function.QuintConsumer;
 import com.android.internal.util.function.QuintFunction;
-import com.android.internal.util.function.TriFunction;
 import com.android.internal.util.function.UndecFunction;
 import com.android.server.LocalServices;
 
@@ -257,14 +255,14 @@
     }
 
     @Override
-    public SyncNotedAppOp startProxyOperation(int code,
+    public SyncNotedAppOp startProxyOperation(@NonNull IBinder clientId, int code,
             @NonNull AttributionSource attributionSource, boolean startIfModeDefault,
             boolean shouldCollectAsyncNotedOp, String message, boolean shouldCollectMessage,
             boolean skipProxyOperation, @AttributionFlags int proxyAttributionFlags,
             @AttributionFlags int proxiedAttributionFlags, int attributionChainId,
-            @NonNull DecFunction<Integer, AttributionSource, Boolean, Boolean, String, Boolean,
-                    Boolean, Integer, Integer, Integer, SyncNotedAppOp> superImpl) {
-        return superImpl.apply(resolveDatasourceOp(code, attributionSource.getUid(),
+            @NonNull UndecFunction<IBinder, Integer, AttributionSource, Boolean, Boolean, String,
+                    Boolean, Boolean, Integer, Integer, Integer, SyncNotedAppOp> superImpl) {
+        return superImpl.apply(clientId, resolveDatasourceOp(code, attributionSource.getUid(),
                 attributionSource.getPackageName(), attributionSource.getAttributionTag()),
                 attributionSource, startIfModeDefault, shouldCollectAsyncNotedOp, message,
                 shouldCollectMessage, skipProxyOperation, proxyAttributionFlags,
@@ -280,10 +278,10 @@
     }
 
     @Override
-    public void finishProxyOperation(int code, @NonNull AttributionSource attributionSource,
-            boolean skipProxyOperation, @NonNull TriFunction<Integer, AttributionSource,
-            Boolean, Void> superImpl) {
-        superImpl.apply(resolveDatasourceOp(code, attributionSource.getUid(),
+    public void finishProxyOperation(@NonNull IBinder clientId, int code,
+            @NonNull AttributionSource attributionSource, boolean skipProxyOperation,
+            @NonNull QuadFunction<IBinder, Integer, AttributionSource, Boolean, Void> superImpl) {
+        superImpl.apply(clientId, resolveDatasourceOp(code, attributionSource.getUid(),
                 attributionSource.getPackageName(), attributionSource.getAttributionTag()),
                 attributionSource, skipProxyOperation);
     }
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index 98b5c1b..3aa333a 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -3589,7 +3589,12 @@
                     @Override
                     public void onKeyguardExitResult(boolean success) {
                         if (success) {
-                            startDockOrHome(displayId, true /*fromHomeKey*/, awakenFromDreams);
+                            final long origId = Binder.clearCallingIdentity();
+                            try {
+                                startDockOrHome(displayId, true /*fromHomeKey*/, awakenFromDreams);
+                            } finally {
+                                Binder.restoreCallingIdentity(origId);
+                            }
                         }
                     }
                 });
diff --git a/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java b/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java
index 916df89..0c5e451 100644
--- a/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java
+++ b/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java
@@ -11507,6 +11507,9 @@
 
         mHistory.reset();
 
+        // Store the empty state to disk to ensure consistency
+        writeSyncLocked();
+
         // Flush external data, gathering snapshots, but don't process it since it is pre-reset data
         mIgnoreNextExternalStats = true;
         mExternalSync.scheduleSync("reset", ExternalStatsSync.UPDATE_ON_RESET);
diff --git a/services/core/java/com/android/server/timedetector/EnvironmentImpl.java b/services/core/java/com/android/server/timedetector/EnvironmentImpl.java
index 8d106f7..5801920 100644
--- a/services/core/java/com/android/server/timedetector/EnvironmentImpl.java
+++ b/services/core/java/com/android/server/timedetector/EnvironmentImpl.java
@@ -128,4 +128,5 @@
     @Override
     public void dumpDebugLog(@NonNull PrintWriter printWriter) {
         SystemClockTime.dump(printWriter);
-    }}
+    }
+}
diff --git a/services/core/java/com/android/server/tv/interactive/TvInteractiveAppManagerService.java b/services/core/java/com/android/server/tv/interactive/TvInteractiveAppManagerService.java
index dbddb41..2c8fd96 100644
--- a/services/core/java/com/android/server/tv/interactive/TvInteractiveAppManagerService.java
+++ b/services/core/java/com/android/server/tv/interactive/TvInteractiveAppManagerService.java
@@ -1066,7 +1066,28 @@
             } finally {
                 Binder.restoreCallingIdentity(identity);
             }
+        }
 
+        @Override
+        public void notifyRecordingStopped(IBinder sessionToken, String recordingId, int userId) {
+            final int callingUid = Binder.getCallingUid();
+            final int callingPid = Binder.getCallingPid();
+            final int resolvedUserId = resolveCallingUserId(callingPid, callingUid, userId,
+                    "notifyRecordingStopped");
+            final long identity = Binder.clearCallingIdentity();
+            try {
+                synchronized (mLock) {
+                    try {
+                        SessionState sessionState = getSessionStateLocked(sessionToken, callingUid,
+                                resolvedUserId);
+                        getSessionLocked(sessionState).notifyRecordingStopped(recordingId);
+                    } catch (RemoteException | SessionNotFoundException e) {
+                        Slogf.e(TAG, "error in notifyRecordingStopped", e);
+                    }
+                }
+            } finally {
+                Binder.restoreCallingIdentity(identity);
+            }
         }
 
         @Override
diff --git a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
index b8cd8d9..c875f4a 100644
--- a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
+++ b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
@@ -1897,12 +1897,9 @@
         }
     }
 
-    private static final HashMap<Integer, String> sWallpaperType = new HashMap<Integer, String>() {
-        {
-            put(FLAG_SYSTEM, RECORD_FILE);
-            put(FLAG_LOCK, RECORD_LOCK_FILE);
-        }
-    };
+    private static final Map<Integer, String> sWallpaperType = Map.of(
+            FLAG_SYSTEM, RECORD_FILE,
+            FLAG_LOCK, RECORD_LOCK_FILE);
 
     private void errorCheck(int userID) {
         sWallpaperType.forEach((type, filename) -> {
diff --git a/services/core/java/com/android/server/wm/ActivityStarter.java b/services/core/java/com/android/server/wm/ActivityStarter.java
index e6d9492..a857d90 100644
--- a/services/core/java/com/android/server/wm/ActivityStarter.java
+++ b/services/core/java/com/android/server/wm/ActivityStarter.java
@@ -1989,12 +1989,6 @@
                 ? targetTask.getTopNonFinishingActivity()
                 : targetTaskTop;
 
-        // At this point we are certain we want the task moved to the front. If we need to dismiss
-        // any other always-on-top root tasks, now is the time to do it.
-        if (targetTaskTop.canTurnScreenOn() && mService.isDreaming()) {
-            targetTaskTop.mTaskSupervisor.wakeUp("recycleTask#turnScreenOnFlag");
-        }
-
         if (mMovedToFront) {
             // We moved the task to front, use starting window to hide initial drawn delay.
             targetTaskTop.showStartingWindow(true /* taskSwitch */);
@@ -2006,6 +2000,12 @@
         // And for paranoia, make sure we have correctly resumed the top activity.
         resumeTargetRootTaskIfNeeded();
 
+        // This is moving an existing task to front. But since dream activity has a higher z-order
+        // to cover normal activities, it needs the awakening event to be dismissed.
+        if (mService.isDreaming() && targetTaskTop.canTurnScreenOn()) {
+            targetTaskTop.mTaskSupervisor.wakeUp("recycleTask#turnScreenOnFlag");
+        }
+
         mLastStartActivityRecord = targetTaskTop;
         return mMovedToFront ? START_TASK_TO_FRONT : START_DELIVERED_TO_TOP;
     }
diff --git a/services/core/java/com/android/server/wm/DisplayWindowSettings.java b/services/core/java/com/android/server/wm/DisplayWindowSettings.java
index e0644b6..b735b30 100644
--- a/services/core/java/com/android/server/wm/DisplayWindowSettings.java
+++ b/services/core/java/com/android/server/wm/DisplayWindowSettings.java
@@ -442,11 +442,12 @@
                     mRemoveContentMode = other.mRemoveContentMode;
                     changed = true;
                 }
-                if (other.mShouldShowWithInsecureKeyguard != mShouldShowWithInsecureKeyguard) {
+                if (!Objects.equals(
+                        other.mShouldShowWithInsecureKeyguard, mShouldShowWithInsecureKeyguard)) {
                     mShouldShowWithInsecureKeyguard = other.mShouldShowWithInsecureKeyguard;
                     changed = true;
                 }
-                if (other.mShouldShowSystemDecors != mShouldShowSystemDecors) {
+                if (!Objects.equals(other.mShouldShowSystemDecors, mShouldShowSystemDecors)) {
                     mShouldShowSystemDecors = other.mShouldShowSystemDecors;
                     changed = true;
                 }
@@ -458,15 +459,15 @@
                     mFixedToUserRotation = other.mFixedToUserRotation;
                     changed = true;
                 }
-                if (other.mIgnoreOrientationRequest != mIgnoreOrientationRequest) {
+                if (!Objects.equals(other.mIgnoreOrientationRequest, mIgnoreOrientationRequest)) {
                     mIgnoreOrientationRequest = other.mIgnoreOrientationRequest;
                     changed = true;
                 }
-                if (other.mIgnoreDisplayCutout != mIgnoreDisplayCutout) {
+                if (!Objects.equals(other.mIgnoreDisplayCutout, mIgnoreDisplayCutout)) {
                     mIgnoreDisplayCutout = other.mIgnoreDisplayCutout;
                     changed = true;
                 }
-                if (other.mDontMoveToTop != mDontMoveToTop) {
+                if (!Objects.equals(other.mDontMoveToTop, mDontMoveToTop)) {
                     mDontMoveToTop = other.mDontMoveToTop;
                     changed = true;
                 }
@@ -522,14 +523,13 @@
                     mRemoveContentMode = delta.mRemoveContentMode;
                     changed = true;
                 }
-                if (delta.mShouldShowWithInsecureKeyguard != null
-                        && delta.mShouldShowWithInsecureKeyguard
-                        != mShouldShowWithInsecureKeyguard) {
+                if (delta.mShouldShowWithInsecureKeyguard != null && !Objects.equals(
+                        delta.mShouldShowWithInsecureKeyguard, mShouldShowWithInsecureKeyguard)) {
                     mShouldShowWithInsecureKeyguard = delta.mShouldShowWithInsecureKeyguard;
                     changed = true;
                 }
-                if (delta.mShouldShowSystemDecors != null
-                        && delta.mShouldShowSystemDecors != mShouldShowSystemDecors) {
+                if (delta.mShouldShowSystemDecors != null && !Objects.equals(
+                        delta.mShouldShowSystemDecors, mShouldShowSystemDecors)) {
                     mShouldShowSystemDecors = delta.mShouldShowSystemDecors;
                     changed = true;
                 }
@@ -543,18 +543,18 @@
                     mFixedToUserRotation = delta.mFixedToUserRotation;
                     changed = true;
                 }
-                if (delta.mIgnoreOrientationRequest != null
-                        && delta.mIgnoreOrientationRequest != mIgnoreOrientationRequest) {
+                if (delta.mIgnoreOrientationRequest != null && !Objects.equals(
+                        delta.mIgnoreOrientationRequest, mIgnoreOrientationRequest)) {
                     mIgnoreOrientationRequest = delta.mIgnoreOrientationRequest;
                     changed = true;
                 }
-                if (delta.mIgnoreDisplayCutout != null
-                        && delta.mIgnoreDisplayCutout != mIgnoreDisplayCutout) {
+                if (delta.mIgnoreDisplayCutout != null && !Objects.equals(
+                        delta.mIgnoreDisplayCutout, mIgnoreDisplayCutout)) {
                     mIgnoreDisplayCutout = delta.mIgnoreDisplayCutout;
                     changed = true;
                 }
-                if (delta.mDontMoveToTop != null
-                        && delta.mDontMoveToTop != mDontMoveToTop) {
+                if (delta.mDontMoveToTop != null && !Objects.equals(
+                        delta.mDontMoveToTop, mDontMoveToTop)) {
                     mDontMoveToTop = delta.mDontMoveToTop;
                     changed = true;
                 }
diff --git a/services/core/java/com/android/server/wm/ImmersiveModeConfirmation.java b/services/core/java/com/android/server/wm/ImmersiveModeConfirmation.java
index 4c18d0b..56edde0 100644
--- a/services/core/java/com/android/server/wm/ImmersiveModeConfirmation.java
+++ b/services/core/java/com/android/server/wm/ImmersiveModeConfirmation.java
@@ -57,7 +57,6 @@
 import android.view.WindowInsets.Type;
 import android.view.WindowManager;
 import android.view.WindowManagerGlobal;
-import android.view.animation.Animation;
 import android.view.animation.AnimationUtils;
 import android.view.animation.Interpolator;
 import android.widget.Button;
@@ -109,18 +108,13 @@
         mContext = display.getDisplayId() == DEFAULT_DISPLAY
                 ? uiContext : uiContext.createDisplayContext(display);
         mHandler = new H(looper);
-        mShowDelayMs = getNavBarExitDuration() * 3;
+        mShowDelayMs = context.getResources().getInteger(R.integer.dock_enter_exit_duration) * 3L;
         mPanicThresholdMs = context.getResources()
                 .getInteger(R.integer.config_immersive_mode_confirmation_panic);
         mVrModeEnabled = vrModeEnabled;
         mCanSystemBarsBeShownByUser = canSystemBarsBeShownByUser;
     }
 
-    private long getNavBarExitDuration() {
-        Animation exit = AnimationUtils.loadAnimation(mContext, R.anim.dock_bottom_exit);
-        return exit != null ? exit.getDuration() : 0;
-    }
-
     static boolean loadSetting(int currentUserId, Context context) {
         final boolean wasConfirmed = sConfirmed;
         sConfirmed = false;
diff --git a/services/core/java/com/android/server/wm/RefreshRatePolicy.java b/services/core/java/com/android/server/wm/RefreshRatePolicy.java
index ccc71bb..de42c55 100644
--- a/services/core/java/com/android/server/wm/RefreshRatePolicy.java
+++ b/services/core/java/com/android/server/wm/RefreshRatePolicy.java
@@ -16,15 +16,21 @@
 
 package com.android.server.wm;
 
+import static android.hardware.display.DisplayManager.SWITCHING_TYPE_NONE;
+import static android.hardware.display.DisplayManager.SWITCHING_TYPE_RENDER_FRAME_RATE_ONLY;
+
 import static com.android.server.wm.WindowContainer.AnimationFlags.PARENTS;
 import static com.android.server.wm.WindowContainer.AnimationFlags.TRANSITION;
 
+import android.hardware.display.DisplayManager;
 import android.view.Display;
 import android.view.Display.Mode;
 import android.view.DisplayInfo;
+import android.view.Surface;
 import android.view.SurfaceControl.RefreshRateRange;
 
 import java.util.HashMap;
+import java.util.Objects;
 
 /**
  * Policy to select a lower refresh rate for the display if applicable.
@@ -154,39 +160,109 @@
         return LAYER_PRIORITY_UNSET;
     }
 
-    float getPreferredRefreshRate(WindowState w) {
+    public static class FrameRateVote {
+        float mRefreshRate;
+        @Surface.FrameRateCompatibility int mCompatibility;
+
+        FrameRateVote(float refreshRate, @Surface.FrameRateCompatibility int compatibility) {
+            update(refreshRate, compatibility);
+        }
+
+        FrameRateVote() {
+            reset();
+        }
+
+        boolean update(float refreshRate, @Surface.FrameRateCompatibility int compatibility) {
+            if (!refreshRateEquals(refreshRate) || mCompatibility != compatibility) {
+                mRefreshRate = refreshRate;
+                mCompatibility = compatibility;
+                return true;
+            }
+            return false;
+        }
+
+        boolean reset() {
+            return update(0, Surface.FRAME_RATE_COMPATIBILITY_DEFAULT);
+        }
+
+        @Override
+        public boolean equals(Object o) {
+            if (!(o instanceof FrameRateVote)) {
+                return false;
+            }
+
+            FrameRateVote other = (FrameRateVote) o;
+            return refreshRateEquals(other.mRefreshRate)
+                    && mCompatibility == other.mCompatibility;
+        }
+
+        @Override
+        public int hashCode() {
+            return Objects.hash(mRefreshRate, mCompatibility);
+        }
+
+        @Override
+        public String toString() {
+            return "mRefreshRate=" + mRefreshRate + ", mCompatibility=" + mCompatibility;
+        }
+
+        private boolean refreshRateEquals(float refreshRate) {
+            return mRefreshRate <= refreshRate + RefreshRateRange.FLOAT_TOLERANCE
+                    && mRefreshRate >= refreshRate - RefreshRateRange.FLOAT_TOLERANCE;
+        }
+    }
+
+    boolean updateFrameRateVote(WindowState w) {
+        @DisplayManager.SwitchingType int refreshRateSwitchingType =
+                mWmService.mDisplayManagerInternal.getRefreshRateSwitchingType();
+
+        // If refresh rate switching is disabled there is no point to set the frame rate on the
+        // surface as the refresh rate will be limited by display manager to a single value
+        // and SurfaceFlinger wouldn't be able to change it anyways.
+        if (refreshRateSwitchingType == SWITCHING_TYPE_NONE) {
+            return w.mFrameRateVote.reset();
+        }
+
         // If app is animating, it's not able to control refresh rate because we want the animation
         // to run in default refresh rate.
         if (w.isAnimating(TRANSITION | PARENTS)) {
-            return 0;
+            return w.mFrameRateVote.reset();
         }
 
         // If the app set a preferredDisplayModeId, the preferred refresh rate is the refresh rate
         // of that mode id.
-        final int preferredModeId = w.mAttrs.preferredDisplayModeId;
-        if (preferredModeId > 0) {
-            DisplayInfo info = w.getDisplayInfo();
-            if (info != null) {
-                for (Display.Mode mode : info.supportedModes) {
-                    if (preferredModeId == mode.getModeId()) {
-                        return mode.getRefreshRate();
+        if (refreshRateSwitchingType != SWITCHING_TYPE_RENDER_FRAME_RATE_ONLY) {
+            final int preferredModeId = w.mAttrs.preferredDisplayModeId;
+            if (preferredModeId > 0) {
+                DisplayInfo info = w.getDisplayInfo();
+                if (info != null) {
+                    for (Display.Mode mode : info.supportedModes) {
+                        if (preferredModeId == mode.getModeId()) {
+                            return w.mFrameRateVote.update(mode.getRefreshRate(),
+                                    Surface.FRAME_RATE_COMPATIBILITY_EXACT);
+
+                        }
                     }
                 }
             }
         }
 
         if (w.mAttrs.preferredRefreshRate > 0) {
-            return w.mAttrs.preferredRefreshRate;
+            return w.mFrameRateVote.update(w.mAttrs.preferredRefreshRate,
+                    Surface.FRAME_RATE_COMPATIBILITY_DEFAULT);
         }
 
         // If the app didn't set a preferred mode id or refresh rate, but it is part of the deny
         // list, we return the low refresh rate as the preferred one.
-        final String packageName = w.getOwningPackage();
-        if (mHighRefreshRateDenylist.isDenylisted(packageName)) {
-            return mLowRefreshRateMode.getRefreshRate();
+        if (refreshRateSwitchingType != SWITCHING_TYPE_RENDER_FRAME_RATE_ONLY) {
+            final String packageName = w.getOwningPackage();
+            if (mHighRefreshRateDenylist.isDenylisted(packageName)) {
+                return w.mFrameRateVote.update(mLowRefreshRateMode.getRefreshRate(),
+                        Surface.FRAME_RATE_COMPATIBILITY_EXACT);
+            }
         }
 
-        return 0;
+        return w.mFrameRateVote.reset();
     }
 
     float getPreferredMinRefreshRate(WindowState w) {
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index cdb3321..dfb61a8 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -4291,13 +4291,14 @@
     }
 
     /**
-     * @return true if the task is currently focused.
+     * @return {@code true} if the task is currently focused or one of its children is focused.
      */
     boolean isFocused() {
         if (mDisplayContent == null || mDisplayContent.mFocusedApp == null) {
             return false;
         }
-        return mDisplayContent.mFocusedApp.getTask() == this;
+        final Task focusedTask = mDisplayContent.mFocusedApp.getTask();
+        return focusedTask == this || (focusedTask != null && focusedTask.getParent() == this);
     }
 
     /**
@@ -4317,6 +4318,8 @@
      */
     void onAppFocusChanged(boolean hasFocus) {
         dispatchTaskInfoChangedIfNeeded(false /* force */);
+        final Task parentTask = getParent().asTask();
+        if (parentTask != null) parentTask.dispatchTaskInfoChangedIfNeeded(false /* force */);
     }
 
     void onPictureInPictureParamsChanged() {
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 3419207..df343db 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -426,7 +426,7 @@
      * @see #ENABLE_SHELL_TRANSITIONS
      */
     public static final boolean sEnableShellTransitions =
-            SystemProperties.getBoolean(ENABLE_SHELL_TRANSITIONS, true);
+            SystemProperties.getBoolean(ENABLE_SHELL_TRANSITIONS, false);
 
     /**
      * Allows a fullscreen windowing mode activity to launch in its desired orientation directly
diff --git a/services/core/java/com/android/server/wm/WindowManagerThreadPriorityBooster.java b/services/core/java/com/android/server/wm/WindowManagerThreadPriorityBooster.java
index 6f2930c..1b70d1d 100644
--- a/services/core/java/com/android/server/wm/WindowManagerThreadPriorityBooster.java
+++ b/services/core/java/com/android/server/wm/WindowManagerThreadPriorityBooster.java
@@ -21,7 +21,7 @@
 import static android.os.Process.myTid;
 import static android.os.Process.setThreadPriority;
 
-import static com.android.server.LockGuard.INDEX_WINDOW;;
+import static com.android.server.LockGuard.INDEX_WINDOW;
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.server.AnimationThread;
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index 86dd0b5..659b044 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -24,8 +24,6 @@
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
 import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
 import static android.graphics.GraphicsProtos.dumpPointProto;
-import static android.hardware.display.DisplayManager.SWITCHING_TYPE_NONE;
-import static android.hardware.display.DisplayManager.SWITCHING_TYPE_RENDER_FRAME_RATE_ONLY;
 import static android.os.InputConstants.DEFAULT_DISPATCHING_TIMEOUT_MILLIS;
 import static android.os.PowerManager.DRAW_WAKE_LOCK;
 import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER;
@@ -203,7 +201,6 @@
 import android.graphics.RectF;
 import android.graphics.Region;
 import android.gui.TouchOcclusionMode;
-import android.hardware.display.DisplayManager;
 import android.os.Binder;
 import android.os.Build;
 import android.os.Debug;
@@ -261,6 +258,7 @@
 import com.android.internal.util.ToBooleanFunction;
 import com.android.server.policy.WindowManagerPolicy;
 import com.android.server.wm.LocalAnimationAdapter.AnimationSpec;
+import com.android.server.wm.RefreshRatePolicy.FrameRateVote;
 import com.android.server.wm.SurfaceAnimator.AnimationType;
 
 import dalvik.annotation.optimization.NeverCompile;
@@ -792,7 +790,7 @@
      * preferredDisplayModeId or is part of the high refresh rate deny list.
      * The variable is cached, so we do not send too many updates to SF.
      */
-    float mAppPreferredFrameRate = 0f;
+    FrameRateVote mFrameRateVote = new FrameRateVote();
 
     static final int BLAST_TIMEOUT_DURATION = 5000; /* milliseconds */
 
@@ -5507,20 +5505,12 @@
                     mFrameRateSelectionPriority);
         }
 
-        // If refresh rate switching is disabled there is no point to set the frame rate on the
-        // surface as the refresh rate will be limited by display manager to a single value
-        // and SurfaceFlinger wouldn't be able to change it anyways.
-        @DisplayManager.SwitchingType int refreshRateSwitchingType =
-                mWmService.mDisplayManagerInternal.getRefreshRateSwitchingType();
-        if (refreshRateSwitchingType != SWITCHING_TYPE_NONE
-                && refreshRateSwitchingType != SWITCHING_TYPE_RENDER_FRAME_RATE_ONLY) {
-            final float refreshRate = refreshRatePolicy.getPreferredRefreshRate(this);
-            if (mAppPreferredFrameRate != refreshRate) {
-                mAppPreferredFrameRate = refreshRate;
-                getPendingTransaction().setFrameRate(
-                        mSurfaceControl, mAppPreferredFrameRate,
-                        Surface.FRAME_RATE_COMPATIBILITY_EXACT, Surface.CHANGE_FRAME_RATE_ALWAYS);
-            }
+        boolean voteChanged = refreshRatePolicy.updateFrameRateVote(this);
+        if (voteChanged) {
+            getPendingTransaction().setFrameRate(
+                    mSurfaceControl, mFrameRateVote.mRefreshRate,
+                    mFrameRateVote.mCompatibility, Surface.CHANGE_FRAME_RATE_ALWAYS);
+
         }
     }
 
diff --git a/services/core/jni/com_android_server_am_BatteryStatsService.cpp b/services/core/jni/com_android_server_am_BatteryStatsService.cpp
index 16eaa77..3678ced 100644
--- a/services/core/jni/com_android_server_am_BatteryStatsService.cpp
+++ b/services/core/jni/com_android_server_am_BatteryStatsService.cpp
@@ -98,7 +98,7 @@
 public:
     binder::Status notifyWakeup(bool success,
                                 const std::vector<std::string>& wakeupReasons) override {
-        ALOGI("In wakeup_callback: %s", success ? "resumed from suspend" : "suspend aborted");
+        ALOGV("In wakeup_callback: %s", success ? "resumed from suspend" : "suspend aborted");
         bool reasonsCaptured = false;
         {
             std::unique_lock<std::mutex> reasonsLock(mReasonsMutex, std::defer_lock);
diff --git a/services/core/jni/com_android_server_input_InputManagerService.cpp b/services/core/jni/com_android_server_input_InputManagerService.cpp
index 2030347..5d0551b 100644
--- a/services/core/jni/com_android_server_input_InputManagerService.cpp
+++ b/services/core/jni/com_android_server_input_InputManagerService.cpp
@@ -1191,8 +1191,9 @@
                 static_cast<const KeyEvent*>(inputEvent));
         break;
     case AINPUT_EVENT_TYPE_MOTION:
-        inputEventObj = android_view_MotionEvent_obtainAsCopy(env,
-                static_cast<const MotionEvent*>(inputEvent));
+        inputEventObj =
+                android_view_MotionEvent_obtainAsCopy(env,
+                                                      static_cast<const MotionEvent&>(*inputEvent));
         break;
     default:
         return true; // dispatch the event normally
diff --git a/services/java/com/android/server/BootUserInitializer.java b/services/java/com/android/server/BootUserInitializer.java
index 46e59a9..c3329795 100644
--- a/services/java/com/android/server/BootUserInitializer.java
+++ b/services/java/com/android/server/BootUserInitializer.java
@@ -83,9 +83,10 @@
             Slogf.d(TAG, "Creating initial user");
             t.traceBegin("create-initial-user");
             try {
+                int flags = UserInfo.FLAG_ADMIN | UserInfo.FLAG_MAIN;
                 // TODO(b/204091126): proper name for user
                 UserInfo newUser = um.createUserEvenWhenDisallowed("Real User",
-                        UserManager.USER_TYPE_FULL_SECONDARY, UserInfo.FLAG_ADMIN,
+                        UserManager.USER_TYPE_FULL_SECONDARY, flags,
                         /* disallowedPackages= */ null, /* token= */ null);
                 Slogf.i(TAG, "Created initial user: %s", newUser.toFullString());
                 initialUserId = newUser.id;
diff --git a/services/people/java/com/android/server/people/PeopleService.java b/services/people/java/com/android/server/people/PeopleService.java
index eab3b77..292320e 100644
--- a/services/people/java/com/android/server/people/PeopleService.java
+++ b/services/people/java/com/android/server/people/PeopleService.java
@@ -53,6 +53,7 @@
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
+import java.util.Objects;
 import java.util.function.Consumer;
 
 /**
@@ -372,7 +373,8 @@
         @Override
         public boolean equals(Object o) {
             ListenerKey key = (ListenerKey) o;
-            return key.getPackageName().equals(mPackageName) && key.getUserId() == mUserId
+            return key.getPackageName().equals(mPackageName)
+                    && Objects.equals(key.getUserId(), mUserId)
                     && key.getShortcutId().equals(mShortcutId);
         }
 
diff --git a/services/tests/mockingservicestests/Android.bp b/services/tests/mockingservicestests/Android.bp
index 73b1907c..681bfcf 100644
--- a/services/tests/mockingservicestests/Android.bp
+++ b/services/tests/mockingservicestests/Android.bp
@@ -56,6 +56,7 @@
         "service-jobscheduler",
         "service-permission.impl",
         "service-sdksandbox.impl",
+        "services.backup",
         "services.companion",
         "services.core",
         "services.devicepolicy",
diff --git a/services/tests/mockingservicestests/src/com/android/server/app/GameManagerServiceTests.java b/services/tests/mockingservicestests/src/com/android/server/app/GameManagerServiceTests.java
index d78f6d83..24e5175 100644
--- a/services/tests/mockingservicestests/src/com/android/server/app/GameManagerServiceTests.java
+++ b/services/tests/mockingservicestests/src/com/android/server/app/GameManagerServiceTests.java
@@ -1507,6 +1507,39 @@
     }
 
     @Test
+    public void testSwitchUser() {
+        mockManageUsersGranted();
+        mockModifyGameModeGranted();
+
+        mockDeviceConfigBattery();
+        final Context context = InstrumentationRegistry.getContext();
+        GameManagerService gameManagerService = new GameManagerService(mMockContext,
+                mTestLooper.getLooper(), context.getFilesDir());
+        startUser(gameManagerService, USER_ID_1);
+        startUser(gameManagerService, USER_ID_2);
+        gameManagerService.setGameMode(mPackageName, GameManager.GAME_MODE_BATTERY, USER_ID_1);
+        checkReportedModes(gameManagerService, GameManager.GAME_MODE_STANDARD,
+                GameManager.GAME_MODE_BATTERY);
+        assertEquals(gameManagerService.getGameMode(mPackageName, USER_ID_1),
+                GameManager.GAME_MODE_BATTERY);
+
+        mockDeviceConfigAll();
+        switchUser(gameManagerService, USER_ID_1, USER_ID_2);
+        assertEquals(gameManagerService.getGameMode(mPackageName, USER_ID_2),
+                GameManager.GAME_MODE_STANDARD);
+        checkReportedModes(gameManagerService, GameManager.GAME_MODE_STANDARD,
+                GameManager.GAME_MODE_BATTERY, GameManager.GAME_MODE_PERFORMANCE);
+        gameManagerService.setGameMode(mPackageName, GameManager.GAME_MODE_PERFORMANCE, USER_ID_2);
+        gameManagerService.setGameMode(mPackageName, GameManager.GAME_MODE_BATTERY, USER_ID_1);
+
+        switchUser(gameManagerService, USER_ID_2, USER_ID_1);
+        checkReportedModes(gameManagerService, GameManager.GAME_MODE_STANDARD,
+                GameManager.GAME_MODE_BATTERY, GameManager.GAME_MODE_PERFORMANCE);
+        gameManagerService.setGameMode(mPackageName, GameManager.GAME_MODE_PERFORMANCE, USER_ID_2);
+        gameManagerService.setGameMode(mPackageName, GameManager.GAME_MODE_BATTERY, USER_ID_1);
+    }
+
+    @Test
     public void testUpdateResolutionScalingFactor() {
         mockModifyGameModeGranted();
         mockDeviceConfigBattery();
diff --git a/services/tests/mockingservicestests/src/com/android/server/backup/BackupAndRestoreFeatureFlagsTest.java b/services/tests/mockingservicestests/src/com/android/server/backup/BackupAndRestoreFeatureFlagsTest.java
new file mode 100644
index 0000000..f535997
--- /dev/null
+++ b/services/tests/mockingservicestests/src/com/android/server/backup/BackupAndRestoreFeatureFlagsTest.java
@@ -0,0 +1,73 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.backup;
+
+import android.platform.test.annotations.Presubmit;
+import android.provider.DeviceConfig;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import com.android.modules.utils.testing.TestableDeviceConfig;
+
+import static com.google.common.truth.Truth.assertThat;
+
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@Presubmit
+@RunWith(AndroidJUnit4.class)
+public class BackupAndRestoreFeatureFlagsTest {
+    @Rule
+    public TestableDeviceConfig.TestableDeviceConfigRule
+            mDeviceConfigRule = new TestableDeviceConfig.TestableDeviceConfigRule();
+
+    @Test
+    public void getBackupTransportFutureTimeoutMillis_notSet_returnsDefault() {
+        assertThat(
+                BackupAndRestoreFeatureFlags.getBackupTransportFutureTimeoutMillis()).isEqualTo(
+                600000);
+    }
+
+    @Test
+    public void getBackupTransportFutureTimeoutMillis_set_returnsSetValue() {
+        DeviceConfig.setProperty("backup_and_restore", "backup_transport_future_timeout_millis",
+                "1234", false);
+
+        assertThat(
+                BackupAndRestoreFeatureFlags.getBackupTransportFutureTimeoutMillis()).isEqualTo(
+                1234);
+    }
+
+    @Test
+    public void getBackupTransportCallbackTimeoutMillis_notSet_returnsDefault() {
+        assertThat(
+                BackupAndRestoreFeatureFlags.getBackupTransportCallbackTimeoutMillis()).isEqualTo(
+                300000);
+    }
+
+    @Test
+    public void getBackupTransportCallbackTimeoutMillis_set_returnsSetValue() {
+        DeviceConfig.setProperty("backup_and_restore", "backup_transport_callback_timeout_millis",
+                "5678", false);
+
+        assertThat(
+                BackupAndRestoreFeatureFlags.getBackupTransportCallbackTimeoutMillis()).isEqualTo(
+                5678);
+    }
+}
diff --git a/services/tests/mockingservicestests/src/com/android/server/backup/OWNERS b/services/tests/mockingservicestests/src/com/android/server/backup/OWNERS
new file mode 100644
index 0000000..d99779e
--- /dev/null
+++ b/services/tests/mockingservicestests/src/com/android/server/backup/OWNERS
@@ -0,0 +1 @@
+include /services/backup/OWNERS
diff --git a/services/tests/mockingservicestests/src/com/android/server/display/DisplayPowerController2Test.java b/services/tests/mockingservicestests/src/com/android/server/display/DisplayPowerController2Test.java
index dc49a94..4c28c51 100644
--- a/services/tests/mockingservicestests/src/com/android/server/display/DisplayPowerController2Test.java
+++ b/services/tests/mockingservicestests/src/com/android/server/display/DisplayPowerController2Test.java
@@ -144,7 +144,7 @@
                     SensorManager sensorManager) {
                 return new DisplayPowerProximityStateController(wakelockController,
                         displayDeviceConfig, looper, nudgeUpdatePowerState, displayId,
-                        sensorManager);
+                        sensorManager, /* injector= */ null);
             }
         };
 
diff --git a/services/tests/mockingservicestests/src/com/android/server/display/DisplayPowerProximityStateControllerTest.java b/services/tests/mockingservicestests/src/com/android/server/display/DisplayPowerProximityStateControllerTest.java
new file mode 100644
index 0000000..6e91b24
--- /dev/null
+++ b/services/tests/mockingservicestests/src/com/android/server/display/DisplayPowerProximityStateControllerTest.java
@@ -0,0 +1,407 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.display;
+
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verifyZeroInteractions;
+import static org.mockito.Mockito.when;
+
+import android.hardware.Sensor;
+import android.hardware.SensorEventListener;
+import android.hardware.SensorManager;
+import android.hardware.display.DisplayManagerInternal;
+import android.os.test.TestLooper;
+import android.view.Display;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.server.testutils.OffsettableClock;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.List;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public final class DisplayPowerProximityStateControllerTest {
+    @Mock
+    WakelockController mWakelockController;
+
+    @Mock
+    DisplayDeviceConfig mDisplayDeviceConfig;
+
+    @Mock
+    Runnable mNudgeUpdatePowerState;
+
+    @Mock
+    SensorManager mSensorManager;
+
+    private Sensor mProximitySensor;
+    private OffsettableClock mClock;
+    private TestLooper mTestLooper;
+    private SensorEventListener mSensorEventListener;
+    private DisplayPowerProximityStateController mDisplayPowerProximityStateController;
+
+    @Before
+    public void before() throws Exception {
+        MockitoAnnotations.initMocks(this);
+        mClock = new OffsettableClock.Stopped();
+        mTestLooper = new TestLooper(mClock::now);
+        when(mDisplayDeviceConfig.getProximitySensor()).thenReturn(
+                new DisplayDeviceConfig.SensorData() {
+                    {
+                        type = Sensor.STRING_TYPE_PROXIMITY;
+                        // This is kept null because currently there is no way to define a sensor
+                        // name in TestUtils
+                        name = null;
+                    }
+                });
+        setUpProxSensor();
+        DisplayPowerProximityStateController.Injector injector =
+                new DisplayPowerProximityStateController.Injector() {
+                    @Override
+                    DisplayPowerProximityStateController.Clock createClock() {
+                        return new DisplayPowerProximityStateController.Clock() {
+                            @Override
+                            public long uptimeMillis() {
+                                return mClock.now();
+                            }
+                        };
+                    }
+                };
+        mDisplayPowerProximityStateController = new DisplayPowerProximityStateController(
+                mWakelockController, mDisplayDeviceConfig, mTestLooper.getLooper(),
+                mNudgeUpdatePowerState, 0,
+                mSensorManager, injector);
+        mSensorEventListener = mDisplayPowerProximityStateController.getProximitySensorListener();
+    }
+
+    @Test
+    public void updatePendingProximityRequestsWorksAsExpectedWhenPending() {
+        // Set the system to pending wait for proximity
+        assertTrue(mDisplayPowerProximityStateController.setPendingWaitForNegativeProximityLocked(
+                true));
+        assertTrue(
+                mDisplayPowerProximityStateController.getPendingWaitForNegativeProximityLocked());
+
+        // Update the pending proximity wait request
+        mDisplayPowerProximityStateController.updatePendingProximityRequestsLocked();
+        assertTrue(mDisplayPowerProximityStateController.getWaitingForNegativeProximity());
+        assertFalse(
+                mDisplayPowerProximityStateController.getPendingWaitForNegativeProximityLocked());
+    }
+
+    @Test
+    public void updatePendingProximityRequestsWorksAsExpectedWhenNotPending() {
+        // Will not wait or be in the pending wait state of not already pending
+        mDisplayPowerProximityStateController.updatePendingProximityRequestsLocked();
+        assertFalse(mDisplayPowerProximityStateController.getWaitingForNegativeProximity());
+        assertFalse(
+                mDisplayPowerProximityStateController.getPendingWaitForNegativeProximityLocked());
+    }
+
+    @Test
+    public void updatePendingProximityRequestsWorksAsExpectedWhenPendingAndProximityIgnored()
+            throws Exception {
+        // Set the system to the state where it will ignore proximity unless changed
+        enableProximitySensor();
+        emitAndValidatePositiveProximityEvent();
+        mDisplayPowerProximityStateController.ignoreProximitySensorUntilChangedInternal();
+        advanceTime(1);
+        assertTrue(mDisplayPowerProximityStateController.shouldIgnoreProximityUntilChanged());
+        verify(mNudgeUpdatePowerState, times(2)).run();
+
+        // Do not set the system to pending wait for proximity
+        mDisplayPowerProximityStateController.updatePendingProximityRequestsLocked();
+        assertFalse(mDisplayPowerProximityStateController.getWaitingForNegativeProximity());
+        assertFalse(
+                mDisplayPowerProximityStateController.getPendingWaitForNegativeProximityLocked());
+
+        // Set the system to pending wait for proximity. But because the proximity is being
+        // ignored, it will not wait or not set the pending wait
+        assertTrue(mDisplayPowerProximityStateController.setPendingWaitForNegativeProximityLocked(
+                true));
+        mDisplayPowerProximityStateController.updatePendingProximityRequestsLocked();
+        assertFalse(mDisplayPowerProximityStateController.getWaitingForNegativeProximity());
+        assertFalse(
+                mDisplayPowerProximityStateController.getPendingWaitForNegativeProximityLocked());
+    }
+
+    @Test
+    public void cleanupDisablesTheProximitySensor() {
+        enableProximitySensor();
+        mDisplayPowerProximityStateController.cleanup();
+        verify(mSensorManager).unregisterListener(
+                mSensorEventListener);
+        assertFalse(mDisplayPowerProximityStateController.isProximitySensorEnabled());
+        assertFalse(mDisplayPowerProximityStateController.getWaitingForNegativeProximity());
+        assertFalse(mDisplayPowerProximityStateController.shouldIgnoreProximityUntilChanged());
+        assertEquals(mDisplayPowerProximityStateController.getProximity(),
+                DisplayPowerProximityStateController.PROXIMITY_UNKNOWN);
+        when(mWakelockController.releaseWakelock(
+                WakelockController.WAKE_LOCK_PROXIMITY_DEBOUNCE)).thenReturn(true);
+        assertEquals(mDisplayPowerProximityStateController.getPendingProximityDebounceTime(), -1);
+    }
+
+    @Test
+    public void isProximitySensorAvailableReturnsTrueWhenAvailable() {
+        assertTrue(mDisplayPowerProximityStateController.isProximitySensorAvailable());
+    }
+
+    @Test
+    public void isProximitySensorAvailableReturnsFalseWhenNotAvailable() {
+        when(mDisplayDeviceConfig.getProximitySensor()).thenReturn(
+                new DisplayDeviceConfig.SensorData() {
+                    {
+                        type = null;
+                        name = null;
+                    }
+                });
+        mDisplayPowerProximityStateController = new DisplayPowerProximityStateController(
+                mWakelockController, mDisplayDeviceConfig, mTestLooper.getLooper(),
+                mNudgeUpdatePowerState, 1,
+                mSensorManager, null);
+        assertFalse(mDisplayPowerProximityStateController.isProximitySensorAvailable());
+    }
+
+    @Test
+    public void notifyDisplayDeviceChangedReloadsTheProximitySensor() throws Exception {
+        DisplayDeviceConfig updatedDisplayDeviceConfig = mock(DisplayDeviceConfig.class);
+        when(updatedDisplayDeviceConfig.getProximitySensor()).thenReturn(
+                new DisplayDeviceConfig.SensorData() {
+                    {
+                        type = Sensor.STRING_TYPE_PROXIMITY;
+                        name = null;
+                    }
+                });
+        Sensor newProxSensor = TestUtils.createSensor(
+                Sensor.TYPE_PROXIMITY, Sensor.STRING_TYPE_PROXIMITY, 4.0f);
+        when(mSensorManager.getSensorList(eq(Sensor.TYPE_ALL)))
+                .thenReturn(List.of(newProxSensor));
+        mDisplayPowerProximityStateController.notifyDisplayDeviceChanged(
+                updatedDisplayDeviceConfig);
+        assertTrue(mDisplayPowerProximityStateController.isProximitySensorAvailable());
+    }
+
+    @Test
+    public void setPendingWaitForNegativeProximityLockedWorksAsExpected() {
+        // Doesn't do anything not asked to wait
+        assertFalse(mDisplayPowerProximityStateController.setPendingWaitForNegativeProximityLocked(
+                false));
+        assertFalse(
+                mDisplayPowerProximityStateController.getPendingWaitForNegativeProximityLocked());
+
+        // Sets pending wait negative proximity if not already waiting
+        assertTrue(mDisplayPowerProximityStateController.setPendingWaitForNegativeProximityLocked(
+                true));
+        assertTrue(
+                mDisplayPowerProximityStateController.getPendingWaitForNegativeProximityLocked());
+
+        // Will not set pending wait negative proximity if already waiting
+        assertFalse(mDisplayPowerProximityStateController.setPendingWaitForNegativeProximityLocked(
+                true));
+        assertTrue(
+                mDisplayPowerProximityStateController.getPendingWaitForNegativeProximityLocked());
+
+    }
+
+    @Test
+    public void evaluateProximityStateWhenRequestedUseOfProximitySensor() throws Exception {
+        // Enable the proximity sensor
+        enableProximitySensor();
+
+        // Emit a positive proximity event to move the system to a state to mimic a scenario
+        // where the system is in positive proximity
+        emitAndValidatePositiveProximityEvent();
+
+        // Again evaluate the proximity state, with system having positive proximity
+        setScreenOffBecauseOfPositiveProximityState();
+    }
+
+    @Test
+    public void evaluateProximityStateWhenScreenOffBecauseOfPositiveProximity() throws Exception {
+        // Enable the proximity sensor
+        enableProximitySensor();
+
+        // Emit a positive proximity event to move the system to a state to mimic a scenario
+        // where the system is in positive proximity
+        emitAndValidatePositiveProximityEvent();
+
+        // Again evaluate the proximity state, with system having positive proximity
+        setScreenOffBecauseOfPositiveProximityState();
+
+        // Set the system to pending wait for proximity
+        mDisplayPowerProximityStateController.setPendingWaitForNegativeProximityLocked(true);
+        // Update the pending proximity wait request
+        mDisplayPowerProximityStateController.updatePendingProximityRequestsLocked();
+
+        // Start ignoring proximity sensor
+        mDisplayPowerProximityStateController.ignoreProximitySensorUntilChangedInternal();
+        // Re-evaluate the proximity state, such that the system is detecting the positive
+        // proximity, and screen is off because of that
+        when(mWakelockController.getOnProximityNegativeRunnable()).thenReturn(mock(Runnable.class));
+        mDisplayPowerProximityStateController.updateProximityState(mock(
+                DisplayManagerInternal.DisplayPowerRequest.class), Display.STATE_ON);
+        assertTrue(mDisplayPowerProximityStateController.isProximitySensorEnabled());
+        assertFalse(mDisplayPowerProximityStateController.isScreenOffBecauseOfProximity());
+        assertTrue(
+                mDisplayPowerProximityStateController
+                        .shouldSkipRampBecauseOfProximityChangeToNegative());
+        verify(mWakelockController).acquireWakelock(
+                WakelockController.WAKE_LOCK_PROXIMITY_NEGATIVE);
+    }
+
+    @Test
+    public void evaluateProximityStateWhenDisplayIsTurningOff() throws Exception {
+        // Enable the proximity sensor
+        enableProximitySensor();
+
+        // Emit a positive proximity event to move the system to a state to mimic a scenario
+        // where the system is in positive proximity
+        emitAndValidatePositiveProximityEvent();
+
+        // Again evaluate the proximity state, with system having positive proximity
+        setScreenOffBecauseOfPositiveProximityState();
+
+        // Re-evaluate the proximity state, such that the system is detecting the positive
+        // proximity, and screen is off because of that
+        mDisplayPowerProximityStateController.updateProximityState(mock(
+                DisplayManagerInternal.DisplayPowerRequest.class), Display.STATE_OFF);
+        verify(mSensorManager).unregisterListener(
+                mSensorEventListener);
+        assertFalse(mDisplayPowerProximityStateController.isProximitySensorEnabled());
+        assertFalse(mDisplayPowerProximityStateController.getWaitingForNegativeProximity());
+        assertFalse(mDisplayPowerProximityStateController.shouldIgnoreProximityUntilChanged());
+        assertEquals(mDisplayPowerProximityStateController.getProximity(),
+                DisplayPowerProximityStateController.PROXIMITY_UNKNOWN);
+        when(mWakelockController.releaseWakelock(
+                WakelockController.WAKE_LOCK_PROXIMITY_DEBOUNCE)).thenReturn(true);
+        assertEquals(mDisplayPowerProximityStateController.getPendingProximityDebounceTime(), -1);
+    }
+
+    @Test
+    public void evaluateProximityStateNotWaitingForNegativeProximityAndNotUsingProxSensor()
+            throws Exception {
+        // Enable the proximity sensor
+        enableProximitySensor();
+
+        // Emit a positive proximity event to move the system to a state to mimic a scenario
+        // where the system is in positive proximity
+        emitAndValidatePositiveProximityEvent();
+
+        // Re-evaluate the proximity state, such that the system is detecting the positive
+        // proximity, and screen is off because of that
+        mDisplayPowerProximityStateController.updateProximityState(mock(
+                DisplayManagerInternal.DisplayPowerRequest.class), Display.STATE_ON);
+        verify(mSensorManager).unregisterListener(
+                mSensorEventListener);
+        assertFalse(mDisplayPowerProximityStateController.isProximitySensorEnabled());
+        assertFalse(mDisplayPowerProximityStateController.getWaitingForNegativeProximity());
+        assertFalse(mDisplayPowerProximityStateController.shouldIgnoreProximityUntilChanged());
+        assertEquals(mDisplayPowerProximityStateController.getProximity(),
+                DisplayPowerProximityStateController.PROXIMITY_UNKNOWN);
+        when(mWakelockController.releaseWakelock(
+                WakelockController.WAKE_LOCK_PROXIMITY_DEBOUNCE)).thenReturn(true);
+        assertEquals(mDisplayPowerProximityStateController.getPendingProximityDebounceTime(), -1);
+    }
+
+    private void advanceTime(long timeMs) {
+        mClock.fastForward(timeMs);
+        mTestLooper.dispatchAll();
+    }
+
+    private void setUpProxSensor() throws Exception {
+        mProximitySensor = TestUtils.createSensor(
+                Sensor.TYPE_PROXIMITY, Sensor.STRING_TYPE_PROXIMITY, 5.0f);
+        when(mSensorManager.getSensorList(eq(Sensor.TYPE_ALL)))
+                .thenReturn(List.of(mProximitySensor));
+    }
+
+    private void emitAndValidatePositiveProximityEvent() throws Exception {
+        // Emit a positive proximity event to move the system to a state to mimic a scenario
+        // where the system is in positive proximity
+        when(mWakelockController.releaseWakelock(
+                WakelockController.WAKE_LOCK_PROXIMITY_DEBOUNCE)).thenReturn(true);
+        mSensorEventListener.onSensorChanged(TestUtils.createSensorEvent(mProximitySensor, 4));
+        verify(mSensorManager).registerListener(mSensorEventListener,
+                mProximitySensor, SensorManager.SENSOR_DELAY_NORMAL,
+                mDisplayPowerProximityStateController.getHandler());
+        verify(mWakelockController).acquireWakelock(
+                WakelockController.WAKE_LOCK_PROXIMITY_DEBOUNCE);
+        assertEquals(mDisplayPowerProximityStateController.getPendingProximity(),
+                DisplayPowerProximityStateController.PROXIMITY_POSITIVE);
+        assertFalse(mDisplayPowerProximityStateController.shouldIgnoreProximityUntilChanged());
+        assertEquals(mDisplayPowerProximityStateController.getProximity(),
+                DisplayPowerProximityStateController.PROXIMITY_POSITIVE);
+        verify(mNudgeUpdatePowerState).run();
+        assertEquals(mDisplayPowerProximityStateController.getPendingProximityDebounceTime(), -1);
+    }
+
+    // Call evaluateProximityState with the request for using the proximity sensor. This will
+    // register the proximity sensor listener, which will be needed for mocking positive
+    // proximity scenarios.
+    private void enableProximitySensor() {
+        DisplayManagerInternal.DisplayPowerRequest displayPowerRequest = mock(
+                DisplayManagerInternal.DisplayPowerRequest.class);
+        displayPowerRequest.useProximitySensor = true;
+        mDisplayPowerProximityStateController.updateProximityState(displayPowerRequest,
+                Display.STATE_ON);
+        verify(mSensorManager).registerListener(
+                mSensorEventListener,
+                mProximitySensor, SensorManager.SENSOR_DELAY_NORMAL,
+                mDisplayPowerProximityStateController.getHandler());
+        assertTrue(mDisplayPowerProximityStateController.isProximitySensorEnabled());
+        assertFalse(mDisplayPowerProximityStateController.shouldIgnoreProximityUntilChanged());
+        assertFalse(mDisplayPowerProximityStateController.isScreenOffBecauseOfProximity());
+        verifyZeroInteractions(mWakelockController);
+    }
+
+    private void setScreenOffBecauseOfPositiveProximityState() {
+        // Prepare a request to indicate that the proximity sensor is to be used
+        DisplayManagerInternal.DisplayPowerRequest displayPowerRequest = mock(
+                DisplayManagerInternal.DisplayPowerRequest.class);
+        displayPowerRequest.useProximitySensor = true;
+
+        Runnable onProximityPositiveRunnable = mock(Runnable.class);
+        when(mWakelockController.getOnProximityPositiveRunnable()).thenReturn(
+                onProximityPositiveRunnable);
+
+        mDisplayPowerProximityStateController.updateProximityState(displayPowerRequest,
+                Display.STATE_ON);
+        verify(mSensorManager).registerListener(
+                mSensorEventListener,
+                mProximitySensor, SensorManager.SENSOR_DELAY_NORMAL,
+                mDisplayPowerProximityStateController.getHandler());
+        assertTrue(mDisplayPowerProximityStateController.isProximitySensorEnabled());
+        assertFalse(mDisplayPowerProximityStateController.shouldIgnoreProximityUntilChanged());
+        assertTrue(mDisplayPowerProximityStateController.isScreenOffBecauseOfProximity());
+        verify(mWakelockController).acquireWakelock(
+                WakelockController.WAKE_LOCK_PROXIMITY_POSITIVE);
+    }
+}
diff --git a/services/tests/mockingservicestests/src/com/android/server/job/JobConcurrencyManagerTest.java b/services/tests/mockingservicestests/src/com/android/server/job/JobConcurrencyManagerTest.java
index 6279b87..6e4d214 100644
--- a/services/tests/mockingservicestests/src/com/android/server/job/JobConcurrencyManagerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/job/JobConcurrencyManagerTest.java
@@ -17,6 +17,7 @@
 package com.android.server.job;
 
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.doAnswer;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.doNothing;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
@@ -34,16 +35,20 @@
 import static junit.framework.Assert.assertFalse;
 import static junit.framework.Assert.assertTrue;
 
+import static org.junit.Assert.fail;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyString;
 import static org.mockito.Mockito.anyLong;
 import static org.mockito.Mockito.eq;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.when;
 
 import android.annotation.Nullable;
+import android.app.ActivityManager;
 import android.app.ActivityManagerInternal;
 import android.app.AppGlobals;
+import android.app.IActivityManager;
 import android.app.job.JobInfo;
 import android.content.ComponentName;
 import android.content.Context;
@@ -51,6 +56,8 @@
 import android.content.pm.UserInfo;
 import android.content.res.Resources;
 import android.os.Looper;
+import android.os.PowerManager;
+import android.os.RemoteException;
 import android.os.UserHandle;
 import android.provider.DeviceConfig;
 import android.util.ArrayMap;
@@ -149,12 +156,14 @@
                 R.bool.config_jobSchedulerRestrictBackgroundUser);
         when(mContext.getResources()).thenReturn(mResources);
         doReturn(mContext).when(jobSchedulerService).getTestableContext();
+        doReturn(jobSchedulerService).when(jobSchedulerService).getLock();
         mConfigBuilder = new DeviceConfig.Properties.Builder(DeviceConfig.NAMESPACE_JOB_SCHEDULER);
         doAnswer((Answer<DeviceConfig.Properties>) invocationOnMock -> mConfigBuilder.build())
                 .when(() -> DeviceConfig.getProperties(eq(DeviceConfig.NAMESPACE_JOB_SCHEDULER)));
         mPendingJobQueue = new PendingJobQueue();
         doReturn(mPendingJobQueue).when(jobSchedulerService).getPendingJobQueue();
         doReturn(mIPackageManager).when(AppGlobals::getPackageManager);
+        doReturn(mock(PowerManager.class)).when(mContext).getSystemService(PowerManager.class);
         mInjector = new InjectorForTest();
         doAnswer((Answer<Long>) invocationOnMock -> {
             Object[] args = invocationOnMock.getArguments();
@@ -171,6 +180,16 @@
         createCurrentUser(true);
         mNextUserId = 10;
         mJobConcurrencyManager.mGracePeriodObserver = mGracePeriodObserver;
+
+        IActivityManager activityManager = ActivityManager.getService();
+        spyOn(activityManager);
+        try {
+            doNothing().when(activityManager).registerUserSwitchObserver(any(), anyString());
+        } catch (RemoteException e) {
+            fail("registerUserSwitchObserver threw exception: " + e.getMessage());
+        }
+
+        mJobConcurrencyManager.onSystemReady();
     }
 
     @After
@@ -188,13 +207,16 @@
         final ArraySet<JobConcurrencyManager.ContextAssignment> idle = new ArraySet<>();
         final List<JobConcurrencyManager.ContextAssignment> preferredUidOnly = new ArrayList<>();
         final List<JobConcurrencyManager.ContextAssignment> stoppable = new ArrayList<>();
-        final long minPreferredUidOnlyWaitingTimeMs = mJobConcurrencyManager
-                .prepareForAssignmentDeterminationLocked(idle, preferredUidOnly, stoppable);
+        final JobConcurrencyManager.AssignmentInfo assignmentInfo =
+                new JobConcurrencyManager.AssignmentInfo();
+        mJobConcurrencyManager.prepareForAssignmentDeterminationLocked(
+                idle, preferredUidOnly, stoppable, assignmentInfo);
 
         assertEquals(JobConcurrencyManager.STANDARD_CONCURRENCY_LIMIT, idle.size());
         assertEquals(0, preferredUidOnly.size());
         assertEquals(0, stoppable.size());
-        assertEquals(0, minPreferredUidOnlyWaitingTimeMs);
+        assertEquals(0, assignmentInfo.minPreferredUidOnlyWaitingTimeMs);
+        assertEquals(0, assignmentInfo.numRunningTopEj);
     }
 
     @Test
@@ -207,13 +229,16 @@
         final ArraySet<JobConcurrencyManager.ContextAssignment> idle = new ArraySet<>();
         final List<JobConcurrencyManager.ContextAssignment> preferredUidOnly = new ArrayList<>();
         final List<JobConcurrencyManager.ContextAssignment> stoppable = new ArrayList<>();
-        final long minPreferredUidOnlyWaitingTimeMs = mJobConcurrencyManager
-                .prepareForAssignmentDeterminationLocked(idle, preferredUidOnly, stoppable);
+        final JobConcurrencyManager.AssignmentInfo assignmentInfo =
+                new JobConcurrencyManager.AssignmentInfo();
+        mJobConcurrencyManager.prepareForAssignmentDeterminationLocked(
+                idle, preferredUidOnly, stoppable, assignmentInfo);
 
         assertEquals(JobConcurrencyManager.STANDARD_CONCURRENCY_LIMIT, idle.size());
         assertEquals(0, preferredUidOnly.size());
         assertEquals(0, stoppable.size());
-        assertEquals(0, minPreferredUidOnlyWaitingTimeMs);
+        assertEquals(0, assignmentInfo.minPreferredUidOnlyWaitingTimeMs);
+        assertEquals(0, assignmentInfo.numRunningTopEj);
     }
 
     @Test
@@ -230,13 +255,45 @@
         final ArraySet<JobConcurrencyManager.ContextAssignment> idle = new ArraySet<>();
         final List<JobConcurrencyManager.ContextAssignment> preferredUidOnly = new ArrayList<>();
         final List<JobConcurrencyManager.ContextAssignment> stoppable = new ArrayList<>();
-        final long minPreferredUidOnlyWaitingTimeMs = mJobConcurrencyManager
-                .prepareForAssignmentDeterminationLocked(idle, preferredUidOnly, stoppable);
+        final JobConcurrencyManager.AssignmentInfo assignmentInfo =
+                new JobConcurrencyManager.AssignmentInfo();
+        mJobConcurrencyManager.prepareForAssignmentDeterminationLocked(
+                idle, preferredUidOnly, stoppable, assignmentInfo);
 
         assertEquals(0, idle.size());
         assertEquals(JobConcurrencyManager.STANDARD_CONCURRENCY_LIMIT, preferredUidOnly.size());
         assertEquals(0, stoppable.size());
-        assertEquals(0, minPreferredUidOnlyWaitingTimeMs);
+        assertEquals(0, assignmentInfo.minPreferredUidOnlyWaitingTimeMs);
+        assertEquals(0, assignmentInfo.numRunningTopEj);
+    }
+
+    @Test
+    public void testPrepareForAssignmentDetermination_onlyRunningTopEjs() {
+        for (int i = 0; i < JobConcurrencyManager.STANDARD_CONCURRENCY_LIMIT; ++i) {
+            JobStatus job = createJob(mDefaultUserId * UserHandle.PER_USER_RANGE + i);
+            job.startedAsExpeditedJob = true;
+            job.lastEvaluatedBias = JobInfo.BIAS_TOP_APP;
+            mJobConcurrencyManager.addRunningJobForTesting(job);
+        }
+
+        for (int i = 0; i < mInjector.contexts.size(); ++i) {
+            doReturn(i % 2 == 0).when(mInjector.contexts.keyAt(i)).isWithinExecutionGuaranteeTime();
+        }
+
+        final ArraySet<JobConcurrencyManager.ContextAssignment> idle = new ArraySet<>();
+        final List<JobConcurrencyManager.ContextAssignment> preferredUidOnly = new ArrayList<>();
+        final List<JobConcurrencyManager.ContextAssignment> stoppable = new ArrayList<>();
+        final JobConcurrencyManager.AssignmentInfo assignmentInfo =
+                new JobConcurrencyManager.AssignmentInfo();
+        mJobConcurrencyManager.prepareForAssignmentDeterminationLocked(
+                idle, preferredUidOnly, stoppable, assignmentInfo);
+
+        assertEquals(0, idle.size());
+        assertEquals(JobConcurrencyManager.STANDARD_CONCURRENCY_LIMIT / 2, preferredUidOnly.size());
+        assertEquals(JobConcurrencyManager.STANDARD_CONCURRENCY_LIMIT / 2, stoppable.size());
+        assertEquals(0, assignmentInfo.minPreferredUidOnlyWaitingTimeMs);
+        assertEquals(JobConcurrencyManager.STANDARD_CONCURRENCY_LIMIT,
+                assignmentInfo.numRunningTopEj);
     }
 
     @Test
@@ -257,11 +314,13 @@
         final ArraySet<JobConcurrencyManager.ContextAssignment> idle = new ArraySet<>();
         final List<JobConcurrencyManager.ContextAssignment> preferredUidOnly = new ArrayList<>();
         final List<JobConcurrencyManager.ContextAssignment> stoppable = new ArrayList<>();
-        mJobConcurrencyManager
-                .prepareForAssignmentDeterminationLocked(idle, preferredUidOnly, stoppable);
+        final JobConcurrencyManager.AssignmentInfo assignmentInfo =
+                new JobConcurrencyManager.AssignmentInfo();
+        mJobConcurrencyManager.prepareForAssignmentDeterminationLocked(
+                idle, preferredUidOnly, stoppable, assignmentInfo);
         mJobConcurrencyManager
                 .determineAssignmentsLocked(changed, idle, preferredUidOnly, stoppable,
-                        Long.MAX_VALUE);
+                        assignmentInfo);
 
         assertEquals(JobConcurrencyManager.STANDARD_CONCURRENCY_LIMIT, changed.size());
         for (int i = changed.size() - 1; i >= 0; --i) {
@@ -301,15 +360,17 @@
         final ArraySet<JobConcurrencyManager.ContextAssignment> idle = new ArraySet<>();
         final List<JobConcurrencyManager.ContextAssignment> preferredUidOnly = new ArrayList<>();
         final List<JobConcurrencyManager.ContextAssignment> stoppable = new ArrayList<>();
+        final JobConcurrencyManager.AssignmentInfo assignmentInfo =
+                new JobConcurrencyManager.AssignmentInfo();
 
-        long minPreferredUidOnlyWaitingTimeMs = mJobConcurrencyManager
-                .prepareForAssignmentDeterminationLocked(idle, preferredUidOnly, stoppable);
-        assertEquals(remainingTimeMs, minPreferredUidOnlyWaitingTimeMs);
+        mJobConcurrencyManager.prepareForAssignmentDeterminationLocked(
+                idle, preferredUidOnly, stoppable, assignmentInfo);
+        assertEquals(remainingTimeMs, assignmentInfo.minPreferredUidOnlyWaitingTimeMs);
         assertEquals(JobConcurrencyManager.STANDARD_CONCURRENCY_LIMIT, preferredUidOnly.size());
 
         mJobConcurrencyManager
                 .determineAssignmentsLocked(changed, idle, preferredUidOnly, stoppable,
-                        minPreferredUidOnlyWaitingTimeMs);
+                        assignmentInfo);
 
         assertEquals(JobConcurrencyManager.STANDARD_CONCURRENCY_LIMIT, preferredUidOnly.size());
         assertEquals(0, changed.size());
@@ -350,15 +411,17 @@
         final ArraySet<JobConcurrencyManager.ContextAssignment> idle = new ArraySet<>();
         final List<JobConcurrencyManager.ContextAssignment> preferredUidOnly = new ArrayList<>();
         final List<JobConcurrencyManager.ContextAssignment> stoppable = new ArrayList<>();
+        final JobConcurrencyManager.AssignmentInfo assignmentInfo =
+                new JobConcurrencyManager.AssignmentInfo();
 
-        long minPreferredUidOnlyWaitingTimeMs = mJobConcurrencyManager
-                .prepareForAssignmentDeterminationLocked(idle, preferredUidOnly, stoppable);
-        assertEquals(remainingTimeMs, minPreferredUidOnlyWaitingTimeMs);
+        mJobConcurrencyManager.prepareForAssignmentDeterminationLocked(
+                idle, preferredUidOnly, stoppable, assignmentInfo);
+        assertEquals(remainingTimeMs, assignmentInfo.minPreferredUidOnlyWaitingTimeMs);
         assertEquals(JobConcurrencyManager.STANDARD_CONCURRENCY_LIMIT, preferredUidOnly.size());
 
         mJobConcurrencyManager
                 .determineAssignmentsLocked(changed, idle, preferredUidOnly, stoppable,
-                        minPreferredUidOnlyWaitingTimeMs);
+                        assignmentInfo);
 
         assertEquals(JobConcurrencyManager.STANDARD_CONCURRENCY_LIMIT, preferredUidOnly.size());
         for (int i = changed.size() - 1; i >= 0; --i) {
@@ -404,15 +467,17 @@
         final ArraySet<JobConcurrencyManager.ContextAssignment> idle = new ArraySet<>();
         final List<JobConcurrencyManager.ContextAssignment> preferredUidOnly = new ArrayList<>();
         final List<JobConcurrencyManager.ContextAssignment> stoppable = new ArrayList<>();
+        final JobConcurrencyManager.AssignmentInfo assignmentInfo =
+                new JobConcurrencyManager.AssignmentInfo();
 
-        long minPreferredUidOnlyWaitingTimeMs = mJobConcurrencyManager
-                .prepareForAssignmentDeterminationLocked(idle, preferredUidOnly, stoppable);
-        assertEquals(remainingTimeMs, minPreferredUidOnlyWaitingTimeMs);
+        mJobConcurrencyManager.prepareForAssignmentDeterminationLocked(
+                idle, preferredUidOnly, stoppable, assignmentInfo);
+        assertEquals(remainingTimeMs, assignmentInfo.minPreferredUidOnlyWaitingTimeMs);
         assertEquals(JobConcurrencyManager.STANDARD_CONCURRENCY_LIMIT, preferredUidOnly.size());
 
         mJobConcurrencyManager
                 .determineAssignmentsLocked(changed, idle, preferredUidOnly, stoppable,
-                        minPreferredUidOnlyWaitingTimeMs);
+                        assignmentInfo);
 
         assertEquals(JobConcurrencyManager.STANDARD_CONCURRENCY_LIMIT, preferredUidOnly.size());
         // Depending on iteration order, we may create 1 or 2 contexts.
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/DeletePackageHelperTest.kt b/services/tests/mockingservicestests/src/com/android/server/pm/DeletePackageHelperTest.kt
index 7ccd6d9..e0662c4 100644
--- a/services/tests/mockingservicestests/src/com/android/server/pm/DeletePackageHelperTest.kt
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/DeletePackageHelperTest.kt
@@ -51,6 +51,7 @@
 
         mUserManagerInternal = rule.mocks().injector.userManagerInternal
         whenever(mUserManagerInternal.getUserIds()).thenReturn(intArrayOf(0, 1))
+        whenever(mUserManagerInternal.getUserTypesForStatsd(any())).thenReturn(intArrayOf(1, 1))
 
         mPms = createPackageManagerService()
         doAnswer { false }.`when`(mPms).isPackageDeviceAdmin(any(), any())
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/UserVisibilityMediatorMUMDTest.java b/services/tests/mockingservicestests/src/com/android/server/pm/UserVisibilityMediatorMUMDTest.java
index 923c3e3..9be370f 100644
--- a/services/tests/mockingservicestests/src/com/android/server/pm/UserVisibilityMediatorMUMDTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/UserVisibilityMediatorMUMDTest.java
@@ -153,14 +153,8 @@
         assertUserAssignedToDisplay(PARENT_USER_ID, SECONDARY_DISPLAY_ID);
     }
 
-    @Test
-    public void testUnassignUserFromDisplay() {
-        testAssignUserToDisplay_displayAvailable();
-
-        mMediator.unassignUserFromDisplay(USER_ID);
-
-        assertNoUserAssignedToDisplay();
-    }
+    // TODO(b/244644281): when start & assign are merged, rename tests above and also call
+    // stopUserAndAssertState() at the end of them
 
     @Test
     public void testIsUserVisible_bgUserOnSecondaryDisplay() {
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/UserVisibilityMediatorSUSDTest.java b/services/tests/mockingservicestests/src/com/android/server/pm/UserVisibilityMediatorSUSDTest.java
index 7af5f5d..7abdd9e 100644
--- a/services/tests/mockingservicestests/src/com/android/server/pm/UserVisibilityMediatorSUSDTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/UserVisibilityMediatorSUSDTest.java
@@ -15,8 +15,6 @@
  */
 package com.android.server.pm;
 
-import static android.os.UserHandle.USER_SYSTEM;
-
 import static org.junit.Assert.assertThrows;
 
 import org.junit.Test;
@@ -34,6 +32,9 @@
         super(/* usersOnSecondaryDisplaysEnabled= */ false);
     }
 
+    // TODO(b/244644281): when start & assign are merged, rename tests below and also call
+    // stopUserAndAssertState() at the end of them
+
     @Test
     public void testAssignUserToDisplay_otherDisplay_currentUser() {
         mockCurrentUser(USER_ID);
@@ -59,15 +60,4 @@
         assertThrows(UnsupportedOperationException.class, () -> mMediator
                 .assignUserToDisplay(PROFILE_USER_ID, PARENT_USER_ID, SECONDARY_DISPLAY_ID));
     }
-
-    @Test
-    public void testUnassignUserFromDisplay_ignored() {
-        mockCurrentUser(USER_ID);
-
-        mMediator.unassignUserFromDisplay(USER_SYSTEM);
-        mMediator.unassignUserFromDisplay(USER_ID);
-        mMediator.unassignUserFromDisplay(OTHER_USER_ID);
-
-        assertNoUserAssignedToDisplay();
-    }
 }
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/UserVisibilityMediatorTestCase.java b/services/tests/mockingservicestests/src/com/android/server/pm/UserVisibilityMediatorTestCase.java
index 7b20092..e8be97d 100644
--- a/services/tests/mockingservicestests/src/com/android/server/pm/UserVisibilityMediatorTestCase.java
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/UserVisibilityMediatorTestCase.java
@@ -20,11 +20,11 @@
 import static android.view.Display.DEFAULT_DISPLAY;
 import static android.view.Display.INVALID_DISPLAY;
 
+import static com.android.server.pm.UserManagerInternal.USER_ASSIGNMENT_RESULT_FAILURE;
+import static com.android.server.pm.UserManagerInternal.USER_ASSIGNMENT_RESULT_SUCCESS_INVISIBLE;
+import static com.android.server.pm.UserManagerInternal.USER_ASSIGNMENT_RESULT_SUCCESS_VISIBLE;
+import static com.android.server.pm.UserManagerInternal.userAssignmentResultToString;
 import static com.android.server.pm.UserVisibilityMediator.INITIAL_CURRENT_USER_ID;
-import static com.android.server.pm.UserVisibilityMediator.START_USER_RESULT_FAILURE;
-import static com.android.server.pm.UserVisibilityMediator.START_USER_RESULT_SUCCESS_INVISIBLE;
-import static com.android.server.pm.UserVisibilityMediator.START_USER_RESULT_SUCCESS_VISIBLE;
-import static com.android.server.pm.UserVisibilityMediator.startUserResultToString;
 
 import static com.google.common.truth.Truth.assertWithMessage;
 
@@ -100,79 +100,92 @@
 
     @Test
     public final void testStartUser_currentUser() {
-        int result = mMediator.startUser(USER_ID, USER_ID, FG, DEFAULT_DISPLAY);
-        assertStartUserResult(result, START_USER_RESULT_SUCCESS_VISIBLE);
+        int result = mMediator.startOnly(USER_ID, USER_ID, FG, DEFAULT_DISPLAY);
+        assertStartUserResult(result, USER_ASSIGNMENT_RESULT_SUCCESS_VISIBLE);
 
         assertCurrentUser(USER_ID);
         assertIsCurrentUserOrRunningProfileOfCurrentUser(USER_ID);
         assertStartedProfileGroupIdOf(USER_ID, USER_ID);
+
+        stopUserAndAssertState(USER_ID);
     }
 
     @Test
     public final void testStartUser_currentUserSecondaryDisplay() {
-        int result = mMediator.startUser(USER_ID, USER_ID, FG, SECONDARY_DISPLAY_ID);
-        assertStartUserResult(result, START_USER_RESULT_FAILURE);
+        int result = mMediator.startOnly(USER_ID, USER_ID, FG, SECONDARY_DISPLAY_ID);
+        assertStartUserResult(result, USER_ASSIGNMENT_RESULT_FAILURE);
 
         assertCurrentUser(INITIAL_CURRENT_USER_ID);
         assertIsNotCurrentUserOrRunningProfileOfCurrentUser(USER_ID);
         assertStartedProfileGroupIdOf(USER_ID, NO_PROFILE_GROUP_ID);
+
+        stopUserAndAssertState(USER_ID);
     }
 
     @Test
     public final void testStartUser_profileBg_parentStarted() {
         mockCurrentUser(PARENT_USER_ID);
 
-        int result = mMediator.startUser(PROFILE_USER_ID, PARENT_USER_ID, BG, DEFAULT_DISPLAY);
-        assertStartUserResult(result, START_USER_RESULT_SUCCESS_VISIBLE);
+        int result = mMediator.startOnly(PROFILE_USER_ID, PARENT_USER_ID, BG, DEFAULT_DISPLAY);
+        assertStartUserResult(result, USER_ASSIGNMENT_RESULT_SUCCESS_VISIBLE);
 
         assertCurrentUser(PARENT_USER_ID);
         assertIsCurrentUserOrRunningProfileOfCurrentUser(PROFILE_USER_ID);
         assertStartedProfileGroupIdOf(PROFILE_USER_ID, PARENT_USER_ID);
-        assertIsStartedProfile(PROFILE_USER_ID);
+        assertProfileIsStarted(PROFILE_USER_ID);
+
+        stopUserAndAssertState(USER_ID);
     }
 
     @Test
     public final void testStartUser_profileBg_parentNotStarted() {
-        int result = mMediator.startUser(PROFILE_USER_ID, PARENT_USER_ID, BG, DEFAULT_DISPLAY);
-        assertStartUserResult(result, START_USER_RESULT_SUCCESS_INVISIBLE);
+        int result = mMediator.startOnly(PROFILE_USER_ID, PARENT_USER_ID, BG, DEFAULT_DISPLAY);
+        assertStartUserResult(result, USER_ASSIGNMENT_RESULT_SUCCESS_INVISIBLE);
 
         assertCurrentUser(INITIAL_CURRENT_USER_ID);
         assertIsNotCurrentUserOrRunningProfileOfCurrentUser(PROFILE_USER_ID);
         assertStartedProfileGroupIdOf(PROFILE_USER_ID, PARENT_USER_ID);
-        assertIsStartedProfile(PROFILE_USER_ID);
+        assertProfileIsStarted(PROFILE_USER_ID);
+
+        stopUserAndAssertState(USER_ID);
     }
 
     @Test
     public final void testStartUser_profileBg_secondaryDisplay() {
-        int result = mMediator.startUser(PROFILE_USER_ID, PARENT_USER_ID, BG, SECONDARY_DISPLAY_ID);
-        assertStartUserResult(result, START_USER_RESULT_FAILURE);
+        int result = mMediator.startOnly(PROFILE_USER_ID, PARENT_USER_ID, BG, SECONDARY_DISPLAY_ID);
+        assertStartUserResult(result, USER_ASSIGNMENT_RESULT_FAILURE);
 
         assertCurrentUser(INITIAL_CURRENT_USER_ID);
         assertIsNotCurrentUserOrRunningProfileOfCurrentUser(PROFILE_USER_ID);
+
+        stopUserAndAssertState(USER_ID);
     }
 
     @Test
     public final void testStartUser_profileFg() {
-        int result = mMediator.startUser(PROFILE_USER_ID, PARENT_USER_ID, FG, DEFAULT_DISPLAY);
-        assertStartUserResult(result, START_USER_RESULT_FAILURE);
+        int result = mMediator.startOnly(PROFILE_USER_ID, PARENT_USER_ID, FG, DEFAULT_DISPLAY);
+        assertStartUserResult(result, USER_ASSIGNMENT_RESULT_FAILURE);
 
         assertCurrentUser(INITIAL_CURRENT_USER_ID);
         assertIsNotCurrentUserOrRunningProfileOfCurrentUser(PROFILE_USER_ID);
-        assertStartedProfileGroupIdOf(PROFILE_USER_ID, NO_PROFILE_GROUP_ID);
+
+        stopUserAndAssertState(USER_ID);
     }
 
     @Test
     public final void testStartUser_profileFgSecondaryDisplay() {
-        int result = mMediator.startUser(PROFILE_USER_ID, PARENT_USER_ID, FG, SECONDARY_DISPLAY_ID);
+        int result = mMediator.startOnly(PROFILE_USER_ID, PARENT_USER_ID, FG, SECONDARY_DISPLAY_ID);
 
-        assertStartUserResult(result, START_USER_RESULT_FAILURE);
+        assertStartUserResult(result, USER_ASSIGNMENT_RESULT_FAILURE);
         assertCurrentUser(INITIAL_CURRENT_USER_ID);
+
+        stopUserAndAssertState(USER_ID);
     }
 
     @Test
     public final void testGetStartedProfileGroupId_whenStartedWithNoProfileGroupId() {
-        int result = mMediator.startUser(USER_ID, NO_PROFILE_GROUP_ID, FG, DEFAULT_DISPLAY);
-        assertStartUserResult(result, START_USER_RESULT_SUCCESS_VISIBLE);
+        int result = mMediator.startOnly(USER_ID, NO_PROFILE_GROUP_ID, FG, DEFAULT_DISPLAY);
+        assertStartUserResult(result, USER_ASSIGNMENT_RESULT_SUCCESS_VISIBLE);
 
         assertWithMessage("shit").that(mMediator.getStartedProfileGroupId(USER_ID))
                 .isEqualTo(USER_ID);
@@ -386,59 +399,78 @@
                 .isEqualTo(USER_ID);
     }
 
+    /**
+     * Stops the given user and assert the proper state is set.
+     *
+     * <p>This method should be called at the end of tests that starts a user, so it can test
+     * {@code stopUser()} as well (technically speaking, {@code stopUser()} should be tested on its
+     * own methods, but it depends on the user being started at first place, so pragmatically
+     * speaking, it's better to "reuse" such tests for both (start and stop)
+     */
+    private void stopUserAndAssertState(@UserIdInt int userId) {
+        mMediator.stopUser(userId);
+
+        assertUserIsStopped(userId);
+        assertNoUserAssignedToDisplay();
+    }
+
     // TODO(b/244644281): remove if start & assign are merged; if they aren't, add a note explaining
     // it's not meant to be used to test startUser() itself.
     protected void mockCurrentUser(@UserIdInt int userId) {
         Log.d(TAG, "mockCurrentUser(" + userId + ")");
-        int result = mMediator.startUser(userId, userId, FG, DEFAULT_DISPLAY);
-        if (result != START_USER_RESULT_SUCCESS_VISIBLE) {
+        int result = mMediator.startOnly(userId, userId, FG, DEFAULT_DISPLAY);
+        if (result != USER_ASSIGNMENT_RESULT_SUCCESS_VISIBLE) {
             throw new IllegalStateException("Failed to mock current user " + userId
-                    + ": mediator returned " + startUserResultToString(result));
+                    + ": mediator returned " + userAssignmentResultToString(result));
         }
     }
 
-    // TODO(b/244644281): remove if start & assign are merged; if they aren't, add a note explaining
+    // TODO(b/244644281): remove when start & assign are merged; or add a note explaining
     // it's not meant to be used to test startUser() itself.
     protected void startDefaultProfile() {
         mockCurrentUser(PARENT_USER_ID);
         Log.d(TAG, "starting default profile (" + PROFILE_USER_ID + ") in background after starting"
                 + " its parent (" + PARENT_USER_ID + ") on foreground");
 
-        int result = mMediator.startUser(PROFILE_USER_ID, PARENT_USER_ID, BG, DEFAULT_DISPLAY);
-        if (result != START_USER_RESULT_SUCCESS_VISIBLE) {
+        int result = mMediator.startOnly(PROFILE_USER_ID, PARENT_USER_ID, BG, DEFAULT_DISPLAY);
+        if (result != USER_ASSIGNMENT_RESULT_SUCCESS_VISIBLE) {
             throw new IllegalStateException("Failed to start profile user " + PROFILE_USER_ID
-                    + ": mediator returned " + startUserResultToString(result));
+                    + ": mediator returned " + userAssignmentResultToString(result));
         }
     }
 
-    // TODO(b/244644281): remove if start & assign are merged; if they aren't, add a note explaining
+    // TODO(b/244644281): remove when start & assign are merged; or add a note explaining
     // it's not meant to be used to test stopUser() itself.
     protected void stopDefaultProfile() {
         Log.d(TAG, "stopping default profile");
         mMediator.stopUser(PROFILE_USER_ID);
     }
 
-    // TODO(b/244644281): remove if start & assign are merged; if they aren't, add a note explaining
+    // TODO(b/244644281): remove when start & assign are merged; or add a note explaining
     // it's not meant to be used to test assignUserToDisplay() itself.
     protected final void assignUserToDisplay(@UserIdInt int userId, int displayId) {
         Log.d(TAG, "assignUserToDisplay(" + userId + ", " + displayId + ")");
-        int result = mMediator.startUser(userId, userId, BG, displayId);
-        if (result != START_USER_RESULT_SUCCESS_INVISIBLE) {
+        int result = mMediator.startOnly(userId, userId, BG, displayId);
+        if (result != USER_ASSIGNMENT_RESULT_SUCCESS_INVISIBLE) {
             throw new IllegalStateException("Failed to startuser " + userId
-                    + " on background: mediator returned " + startUserResultToString(result));
+                    + " on background: mediator returned " + userAssignmentResultToString(result));
         }
         mMediator.assignUserToDisplay(userId, userId, displayId);
 
     }
 
+    // TODO(b/244644281): remove when start & assign are merged; or rename to
+    // assertNoUserAssignedToSecondaryDisplays
     protected final void assertNoUserAssignedToDisplay() {
-        assertWithMessage("uses on secondary displays")
+        assertWithMessage("users on secondary displays")
                 .that(mMediator.getUsersOnSecondaryDisplays())
                 .isEmpty();
     }
 
+    // TODO(b/244644281): remove when start & assign are merged; or rename to
+    // assertUserAssignedToSecondaryDisplay
     protected final void assertUserAssignedToDisplay(@UserIdInt int userId, int displayId) {
-        assertWithMessage("uses on secondary displays")
+        assertWithMessage("users on secondary displays")
                 .that(mMediator.getUsersOnSecondaryDisplays())
                 .containsExactly(userId, displayId);
     }
@@ -446,24 +478,44 @@
     private void assertCurrentUser(@UserIdInt int userId) {
         assertWithMessage("mediator.getCurrentUserId()").that(mMediator.getCurrentUserId())
                 .isEqualTo(userId);
+        if (userId != INITIAL_CURRENT_USER_ID) {
+            assertUserIsStarted(userId);
+        }
     }
 
-    private void assertIsStartedProfile(@UserIdInt int userId) {
+    private void assertUserIsStarted(@UserIdInt int userId) {
+        assertWithMessage("mediator.isStarted(%s)", userId).that(mMediator.isStartedUser(userId))
+                .isTrue();
+    }
+
+    private void assertUserIsStopped(@UserIdInt int userId) {
+        assertWithMessage("mediator.isStarted(%s)", userId).that(mMediator.isStartedUser(userId))
+                .isFalse();
+    }
+
+    private void assertProfileIsStarted(@UserIdInt int userId) {
         assertWithMessage("mediator.isStartedProfile(%s)", userId)
                 .that(mMediator.isStartedProfile(userId))
                 .isTrue();
+        assertUserIsStarted(userId);
     }
 
-    private void assertStartedProfileGroupIdOf(@UserIdInt int profileId, @UserIdInt int parentId) {
-        assertWithMessage("mediator.getStartedProfileGroupId(%s)", profileId)
-                .that(mMediator.getStartedProfileGroupId(profileId))
-                .isEqualTo(parentId);
+    private void assertStartedProfileGroupIdOf(@UserIdInt int userId,
+            @UserIdInt int profileGroupId) {
+        assertWithMessage("mediator.getStartedProfileGroupId(%s)", userId)
+                .that(mMediator.getStartedProfileGroupId(userId))
+                .isEqualTo(profileGroupId);
     }
 
-    private void assertIsCurrentUserOrRunningProfileOfCurrentUser(int userId) {
+    private void assertIsCurrentUserOrRunningProfileOfCurrentUser(@UserIdInt int userId) {
         assertWithMessage("mediator.isCurrentUserOrRunningProfileOfCurrentUser(%s)", userId)
                 .that(mMediator.isCurrentUserOrRunningProfileOfCurrentUser(userId))
                 .isTrue();
+        if (mMediator.getCurrentUserId() == userId) {
+            assertUserIsStarted(userId);
+        } else {
+            assertProfileIsStarted(userId);
+        }
     }
 
     private void assertIsNotCurrentUserOrRunningProfileOfCurrentUser(int userId) {
@@ -474,8 +526,8 @@
 
     private void assertStartUserResult(int actualResult, int expectedResult) {
         assertWithMessage("startUser() result (where %s=%s and %s=%s)",
-                actualResult, startUserResultToString(actualResult),
-                expectedResult, startUserResultToString(expectedResult))
+                actualResult, userAssignmentResultToString(actualResult),
+                expectedResult, userAssignmentResultToString(expectedResult))
                         .that(actualResult).isEqualTo(expectedResult);
     }
 }
diff --git a/services/tests/servicestests/AndroidManifest.xml b/services/tests/servicestests/AndroidManifest.xml
index 6551bde..6349b21 100644
--- a/services/tests/servicestests/AndroidManifest.xml
+++ b/services/tests/servicestests/AndroidManifest.xml
@@ -107,6 +107,9 @@
 
     <queries>
         <package android:name="com.android.servicestests.apps.suspendtestapp" />
+        <intent>
+            <action android:name="android.media.browse.MediaBrowserService" />
+        </intent>
     </queries>
 
     <!-- Uses API introduced in O (26) -->
diff --git a/services/tests/servicestests/AndroidTest.xml b/services/tests/servicestests/AndroidTest.xml
index 9052f58..9c7ce83 100644
--- a/services/tests/servicestests/AndroidTest.xml
+++ b/services/tests/servicestests/AndroidTest.xml
@@ -33,6 +33,7 @@
         <option name="test-file-name" value="SimpleServiceTestApp1.apk" />
         <option name="test-file-name" value="SimpleServiceTestApp2.apk" />
         <option name="test-file-name" value="SimpleServiceTestApp3.apk" />
+        <option name="test-file-name" value="FakeMediaApp.apk" />
     </target_preparer>
 
     <!-- Create place to store apks -->
diff --git a/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java b/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java
index 80cee50..a49214f 100644
--- a/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java
@@ -39,6 +39,8 @@
 import static com.android.server.am.UserController.USER_START_MSG;
 import static com.android.server.am.UserController.USER_SWITCH_TIMEOUT_MSG;
 import static com.android.server.am.UserController.USER_VISIBILITY_CHANGED_MSG;
+import static com.android.server.pm.UserManagerInternal.USER_ASSIGNMENT_RESULT_SUCCESS_INVISIBLE;
+import static com.android.server.pm.UserManagerInternal.USER_ASSIGNMENT_RESULT_SUCCESS_VISIBLE;
 
 import static com.google.android.collect.Lists.newArrayList;
 import static com.google.android.collect.Sets.newHashSet;
@@ -100,6 +102,7 @@
 import com.android.server.SystemService;
 import com.android.server.am.UserState.KeyEvictedCallback;
 import com.android.server.pm.UserManagerInternal;
+import com.android.server.pm.UserManagerInternal.UserAssignmentResult;
 import com.android.server.pm.UserManagerService;
 import com.android.server.wm.WindowManagerService;
 
@@ -162,10 +165,15 @@
             USER_VISIBILITY_CHANGED_MSG,
             USER_CURRENT_MSG);
 
-    private static final Set<Integer> START_BACKGROUND_USER_MESSAGE_CODES = newHashSet(
+    private static final Set<Integer> START_INVISIBLE_BACKGROUND_USER_MESSAGE_CODES = newHashSet(
             USER_START_MSG,
             REPORT_LOCKED_BOOT_COMPLETE_MSG);
 
+    private static final Set<Integer> START_VISIBLE_BACKGROUND_USER_MESSAGE_CODES = newHashSet(
+            USER_START_MSG,
+            USER_VISIBILITY_CHANGED_MSG,
+            REPORT_LOCKED_BOOT_COMPLETE_MSG);
+
     @Before
     public void setUp() throws Exception {
         runWithDexmakerShareClassLoader(() -> {
@@ -184,6 +192,12 @@
             mockIsUsersOnSecondaryDisplaysEnabled(false);
             // All UserController params are set to default.
 
+            // Starts with a generic assumption that the user starts visible, but on tests where
+            // that's not the case, the test should call mockAssignUserToMainDisplay()
+            doReturn(UserManagerInternal.USER_ASSIGNMENT_RESULT_SUCCESS_VISIBLE)
+                    .when(mInjector.mUserManagerInternalMock)
+                    .assignUserToDisplayOnStart(anyInt(), anyInt(), anyBoolean(), anyInt());
+
             mUserController = new UserController(mInjector);
             mUserController.setAllowUserUnlocking(true);
             setUpUser(TEST_USER_ID, NO_USERINFO_FLAGS);
@@ -211,16 +225,29 @@
 
     @Test
     public void testStartUser_background() {
+        mockAssignUserToMainDisplay(TEST_USER_ID, /* foreground= */ false,
+                USER_ASSIGNMENT_RESULT_SUCCESS_INVISIBLE);
         boolean started = mUserController.startUser(TEST_USER_ID, /* foreground= */ false);
         assertWithMessage("startUser(%s, foreground=false)", TEST_USER_ID).that(started).isTrue();
         verify(mInjector.getWindowManager(), never()).startFreezingScreen(anyInt(), anyInt());
         verify(mInjector.getWindowManager(), never()).setSwitchingUser(anyBoolean());
         verify(mInjector, never()).clearAllLockedTasks(anyString());
-        startBackgroundUserAssertions();
+        startBackgroundUserAssertions(/*visible= */ false);
         verifyUserAssignedToDisplay(TEST_USER_ID, Display.DEFAULT_DISPLAY);
     }
 
     @Test
+    public void testStartUser_displayAssignmentFailed() {
+        doReturn(UserManagerInternal.USER_ASSIGNMENT_RESULT_FAILURE)
+                .when(mInjector.mUserManagerInternalMock)
+                .assignUserToDisplayOnStart(eq(TEST_USER_ID), anyInt(), eq(true), anyInt());
+
+        boolean started = mUserController.startUser(TEST_USER_ID, /* foreground= */ true);
+
+        assertWithMessage("startUser(%s, foreground=true)", TEST_USER_ID).that(started).isFalse();
+    }
+
+    @Test
     public void testStartUserOnSecondaryDisplay_defaultDisplay() {
         assertThrows(IllegalArgumentException.class, () -> mUserController
                 .startUserOnSecondaryDisplay(TEST_USER_ID, Display.DEFAULT_DISPLAY));
@@ -240,7 +267,7 @@
         verify(mInjector.getWindowManager(), never()).startFreezingScreen(anyInt(), anyInt());
         verify(mInjector.getWindowManager(), never()).setSwitchingUser(anyBoolean());
         verify(mInjector, never()).clearAllLockedTasks(anyString());
-        startBackgroundUserAssertions();
+        startBackgroundUserAssertions(/*visible= */ true);
     }
 
     @Test
@@ -266,6 +293,8 @@
 
     @Test
     public void testStartPreCreatedUser_background() throws Exception {
+        mockAssignUserToMainDisplay(TEST_PRE_CREATED_USER_ID, /* foreground= */ false,
+                USER_ASSIGNMENT_RESULT_SUCCESS_INVISIBLE);
         assertTrue(mUserController.startUser(TEST_PRE_CREATED_USER_ID, /* foreground= */ false));
         // Make sure no intents have been fired for pre-created users.
         assertTrue(mInjector.mSentIntents.isEmpty());
@@ -284,8 +313,6 @@
         // binder calls, but their side effects (in this case, that the user is stopped right away)
         assertWithMessage("wrong binder message calls").that(mInjector.mHandler.getMessageCodes())
                 .containsExactly(USER_START_MSG);
-
-        verifyUserNeverAssignedToDisplay();
     }
 
     private void startUserAssertions(
@@ -295,8 +322,10 @@
         assertEquals("Unexpected message sent", expectedMessageCodes, actualCodes);
     }
 
-    private void startBackgroundUserAssertions() {
-        startUserAssertions(START_BACKGROUND_USER_ACTIONS, START_BACKGROUND_USER_MESSAGE_CODES);
+    private void startBackgroundUserAssertions(boolean visible) {
+        startUserAssertions(START_BACKGROUND_USER_ACTIONS,
+                visible ? START_VISIBLE_BACKGROUND_USER_MESSAGE_CODES
+                        : START_INVISIBLE_BACKGROUND_USER_MESSAGE_CODES);
     }
 
     private void startForegroundUserAssertions() {
@@ -680,19 +709,24 @@
 
     @Test
     public void testStartProfile() throws Exception {
+        mockAssignUserToMainDisplay(TEST_PRE_CREATED_USER_ID, /* foreground= */ false,
+                USER_ASSIGNMENT_RESULT_SUCCESS_INVISIBLE);
         setUpAndStartProfileInBackground(TEST_USER_ID1);
 
-        startBackgroundUserAssertions();
+        startBackgroundUserAssertions(/*visible= */ true);
         verifyUserAssignedToDisplay(TEST_USER_ID1, Display.DEFAULT_DISPLAY);
     }
 
     @Test
     public void testStartProfile_whenUsersOnSecondaryDisplaysIsEnabled() throws Exception {
+        mockAssignUserToMainDisplay(TEST_USER_ID1, /* foreground= */ false,
+                USER_ASSIGNMENT_RESULT_SUCCESS_VISIBLE);
+
         mockIsUsersOnSecondaryDisplaysEnabled(true);
 
         setUpAndStartProfileInBackground(TEST_USER_ID1);
 
-        startBackgroundUserAssertions();
+        startBackgroundUserAssertions(/*visible= */ true);
         verifyUserAssignedToDisplay(TEST_USER_ID1, Display.DEFAULT_DISPLAY);
     }
 
@@ -949,22 +983,29 @@
         when(mInjector.isUsersOnSecondaryDisplaysEnabled()).thenReturn(value);
     }
 
+    private void mockAssignUserToMainDisplay(@UserIdInt int userId, boolean foreground,
+            @UserAssignmentResult int result) {
+        when(mInjector.mUserManagerInternalMock.assignUserToDisplayOnStart(eq(userId),
+                /* profileGroupId= */ anyInt(), eq(foreground), eq(Display.DEFAULT_DISPLAY)))
+                        .thenReturn(result);
+    }
+
     private void verifyUserAssignedToDisplay(@UserIdInt int userId, int displayId) {
-        verify(mInjector.getUserManagerInternal()).assignUserToDisplay(eq(userId), anyInt(),
+        verify(mInjector.getUserManagerInternal()).assignUserToDisplayOnStart(eq(userId), anyInt(),
                 anyBoolean(), eq(displayId));
     }
 
     private void verifyUserNeverAssignedToDisplay() {
-        verify(mInjector.getUserManagerInternal(), never()).assignUserToDisplay(anyInt(), anyInt(),
-                anyBoolean(), anyInt());
+        verify(mInjector.getUserManagerInternal(), never()).assignUserToDisplayOnStart(anyInt(),
+                anyInt(), anyBoolean(), anyInt());
     }
 
     private void verifyUserUnassignedFromDisplay(@UserIdInt int userId) {
-        verify(mInjector.getUserManagerInternal()).unassignUserFromDisplay(userId);
+        verify(mInjector.getUserManagerInternal()).unassignUserFromDisplayOnStop(userId);
     }
 
     private void verifyUserUnassignedFromDisplayNeverCalled(@UserIdInt int userId) {
-        verify(mInjector.getUserManagerInternal(), never()).unassignUserFromDisplay(userId);
+        verify(mInjector.getUserManagerInternal(), never()).unassignUserFromDisplayOnStop(userId);
     }
 
     private void verifySystemUserVisibilityChangedNotified(boolean visible) {
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/log/ALSProbeTest.java b/services/tests/servicestests/src/com/android/server/biometrics/log/ALSProbeTest.java
index 0cff4f1..bb00634 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/log/ALSProbeTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/log/ALSProbeTest.java
@@ -125,12 +125,9 @@
         mProbe.destroy();
         mProbe.enable();
 
-        AtomicInteger lux = new AtomicInteger(10);
-        mProbe.awaitNextLux((v) -> lux.set(Math.round(v)), null /* handler */);
-
         verify(mSensorManager, never()).registerListener(any(), any(), anyInt());
         verifyNoMoreInteractions(mSensorManager);
-        assertThat(lux.get()).isLessThan(0);
+        assertThat(mProbe.getMostRecentLux()).isLessThan(0);
     }
 
     @Test
@@ -323,15 +320,27 @@
     }
 
     @Test
-    public void testNoNextLuxWhenDestroyed() {
+    public void testDestroyAllowsAwaitLuxExactlyOnce() {
+        final float lastValue = 5.5f;
         mProbe.destroy();
 
-        AtomicInteger lux = new AtomicInteger(-20);
+        AtomicInteger lux = new AtomicInteger(10);
         mProbe.awaitNextLux((v) -> lux.set(Math.round(v)), null /* handler */);
 
-        assertThat(lux.get()).isEqualTo(-1);
-        verify(mSensorManager, never()).registerListener(
+        verify(mSensorManager).registerListener(
                 mSensorEventListenerCaptor.capture(), any(), anyInt());
+        mSensorEventListenerCaptor.getValue().onSensorChanged(
+                new SensorEvent(mLightSensor, 1, 1, new float[]{lastValue}));
+
+        assertThat(lux.get()).isEqualTo(Math.round(lastValue));
+        verify(mSensorManager).unregisterListener(eq(mSensorEventListenerCaptor.getValue()));
+
+        lux.set(22);
+        mProbe.enable();
+        mProbe.awaitNextLux((v) -> lux.set(Math.round(v)), null /* handler */);
+        mProbe.enable();
+
+        assertThat(lux.get()).isEqualTo(Math.round(lastValue));
         verifyNoMoreInteractions(mSensorManager);
     }
 
diff --git a/services/tests/servicestests/src/com/android/server/camera/CameraServiceProxyTest.java b/services/tests/servicestests/src/com/android/server/camera/CameraServiceProxyTest.java
index ea746d1..faad961 100644
--- a/services/tests/servicestests/src/com/android/server/camera/CameraServiceProxyTest.java
+++ b/services/tests/servicestests/src/com/android/server/camera/CameraServiceProxyTest.java
@@ -30,7 +30,7 @@
 import android.view.Display;
 import android.view.Surface;
 
-import java.util.HashMap;
+import java.util.Map;
 
 @RunWith(JUnit4.class)
 public class CameraServiceProxyTest {
@@ -75,24 +75,22 @@
                 /*ignoreResizableAndSdkCheck*/true)).isEqualTo(
                 CameraMetadata.SCALER_ROTATE_AND_CROP_NONE);
         // Check rotation and lens facing combinations
-        HashMap<Integer, Integer> backFacingMap = new HashMap<Integer, Integer>() {{
-            put(Surface.ROTATION_0, CameraMetadata.SCALER_ROTATE_AND_CROP_NONE);
-            put(Surface.ROTATION_90, CameraMetadata.SCALER_ROTATE_AND_CROP_90);
-            put(Surface.ROTATION_270, CameraMetadata.SCALER_ROTATE_AND_CROP_270);
-            put(Surface.ROTATION_180, CameraMetadata.SCALER_ROTATE_AND_CROP_180);
-        }};
+        Map<Integer, Integer> backFacingMap = Map.of(
+                Surface.ROTATION_0, CameraMetadata.SCALER_ROTATE_AND_CROP_NONE,
+                Surface.ROTATION_90, CameraMetadata.SCALER_ROTATE_AND_CROP_90,
+                Surface.ROTATION_270, CameraMetadata.SCALER_ROTATE_AND_CROP_270,
+                Surface.ROTATION_180, CameraMetadata.SCALER_ROTATE_AND_CROP_180);
         taskInfo.isFixedOrientationPortrait = true;
         backFacingMap.forEach((key, value) -> {
             assertThat(CameraServiceProxy.getCropRotateScale(ctx, ctx.getPackageName(), taskInfo,
                     key, CameraCharacteristics.LENS_FACING_BACK,
                     /*ignoreResizableAndSdkCheck*/true)).isEqualTo(value);
         });
-        HashMap<Integer, Integer> frontFacingMap = new HashMap<Integer, Integer>() {{
-            put(Surface.ROTATION_0, CameraMetadata.SCALER_ROTATE_AND_CROP_NONE);
-            put(Surface.ROTATION_90, CameraMetadata.SCALER_ROTATE_AND_CROP_270);
-            put(Surface.ROTATION_270, CameraMetadata.SCALER_ROTATE_AND_CROP_90);
-            put(Surface.ROTATION_180, CameraMetadata.SCALER_ROTATE_AND_CROP_180);
-        }};
+        Map<Integer, Integer> frontFacingMap = Map.of(
+                Surface.ROTATION_0, CameraMetadata.SCALER_ROTATE_AND_CROP_NONE,
+                Surface.ROTATION_90, CameraMetadata.SCALER_ROTATE_AND_CROP_270,
+                Surface.ROTATION_270, CameraMetadata.SCALER_ROTATE_AND_CROP_90,
+                Surface.ROTATION_180, CameraMetadata.SCALER_ROTATE_AND_CROP_180);
         frontFacingMap.forEach((key, value) -> {
             assertThat(CameraServiceProxy.getCropRotateScale(ctx, ctx.getPackageName(), taskInfo,
                     key, CameraCharacteristics.LENS_FACING_FRONT,
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 5fda3d6..c715a21 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
@@ -16,6 +16,9 @@
 
 package com.android.server.companion.virtual;
 
+import static android.companion.virtual.VirtualDeviceParams.DEVICE_POLICY_CUSTOM;
+import static android.companion.virtual.VirtualDeviceParams.DEVICE_POLICY_DEFAULT;
+import static android.companion.virtual.VirtualDeviceParams.POLICY_TYPE_SENSORS;
 import static android.content.pm.ActivityInfo.FLAG_CAN_DISPLAY_ON_REMOTE_DEVICES;
 
 import static com.google.common.truth.Truth.assertThat;
@@ -44,6 +47,7 @@
 import android.app.admin.DevicePolicyManager;
 import android.companion.AssociationInfo;
 import android.companion.virtual.IVirtualDeviceActivityListener;
+import android.companion.virtual.VirtualDeviceManager;
 import android.companion.virtual.VirtualDeviceParams;
 import android.companion.virtual.audio.IAudioConfigChangedCallback;
 import android.companion.virtual.audio.IAudioRoutingCallback;
@@ -240,6 +244,55 @@
                 mAssociationInfo, new Binder(), /* ownerUid */ 0, /* uniqueId */ 1,
                 mInputController, (int associationId) -> {}, mPendingTrampolineCallback,
                 mActivityListener, mRunningAppsChangedCallback, params);
+        mVdms.addVirtualDevice(mDeviceImpl);
+    }
+
+    @Test
+    public void getDevicePolicy_invalidDeviceId_returnsDefault() {
+        assertThat(
+                mLocalService.getDevicePolicy(
+                        VirtualDeviceManager.INVALID_DEVICE_ID, POLICY_TYPE_SENSORS))
+                .isEqualTo(DEVICE_POLICY_DEFAULT);
+    }
+
+    @Test
+    public void getDevicePolicy_defaultDeviceId_returnsDefault() {
+        assertThat(
+                mLocalService.getDevicePolicy(
+                        VirtualDeviceManager.DEFAULT_DEVICE_ID, POLICY_TYPE_SENSORS))
+                .isEqualTo(DEVICE_POLICY_DEFAULT);
+    }
+
+    @Test
+    public void getDevicePolicy_nonExistentDeviceId_returnsDefault() {
+        assertThat(
+                mLocalService.getDevicePolicy(mDeviceImpl.getDeviceId() + 1, POLICY_TYPE_SENSORS))
+                .isEqualTo(DEVICE_POLICY_DEFAULT);
+    }
+
+    @Test
+    public void getDevicePolicy_unspecifiedPolicy_returnsDefault() {
+        assertThat(
+                mLocalService.getDevicePolicy(mDeviceImpl.getDeviceId(), POLICY_TYPE_SENSORS))
+                .isEqualTo(DEVICE_POLICY_DEFAULT);
+    }
+
+    @Test
+    public void getDevicePolicy_returnsCustom() {
+        VirtualDeviceParams params = new VirtualDeviceParams
+                .Builder()
+                .setBlockedActivities(getBlockedActivities())
+                .addDevicePolicy(POLICY_TYPE_SENSORS, DEVICE_POLICY_CUSTOM)
+                .build();
+        mDeviceImpl = new VirtualDeviceImpl(mContext,
+                mAssociationInfo, new Binder(), /* ownerUid */ 0, /* uniqueId */ 1,
+                mInputController, (int associationId) -> {}, mPendingTrampolineCallback,
+                mActivityListener, mRunningAppsChangedCallback, params);
+        mVdms.addVirtualDevice(mDeviceImpl);
+
+        assertThat(
+                mLocalService.getDevicePolicy(mDeviceImpl.getDeviceId(), POLICY_TYPE_SENSORS))
+                .isEqualTo(DEVICE_POLICY_CUSTOM);
     }
 
     @Test
diff --git a/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceParamsTest.java b/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceParamsTest.java
index 77f1e24..036b6df 100644
--- a/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceParamsTest.java
+++ b/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceParamsTest.java
@@ -37,6 +37,8 @@
         VirtualDeviceParams originalParams = new VirtualDeviceParams.Builder()
                 .setLockState(VirtualDeviceParams.LOCK_STATE_ALWAYS_UNLOCKED)
                 .setUsersWithMatchingAccounts(Set.of(UserHandle.of(123), UserHandle.of(456)))
+                .addDevicePolicy(VirtualDeviceParams.POLICY_TYPE_SENSORS,
+                        VirtualDeviceParams.DEVICE_POLICY_CUSTOM)
                 .build();
         Parcel parcel = Parcel.obtain();
         originalParams.writeToParcel(parcel, 0);
@@ -47,5 +49,7 @@
         assertThat(params.getLockState()).isEqualTo(VirtualDeviceParams.LOCK_STATE_ALWAYS_UNLOCKED);
         assertThat(params.getUsersWithMatchingAccounts())
                 .containsExactly(UserHandle.of(123), UserHandle.of(456));
+        assertThat(params.getDevicePolicy(VirtualDeviceParams.POLICY_TYPE_SENSORS))
+                .isEqualTo(VirtualDeviceParams.DEVICE_POLICY_CUSTOM);
     }
 }
diff --git a/services/tests/servicestests/src/com/android/server/display/AmbientBrightnessStatsTrackerTest.java b/services/tests/servicestests/src/com/android/server/display/AmbientBrightnessStatsTrackerTest.java
index df672c9..2c4fe53 100644
--- a/services/tests/servicestests/src/com/android/server/display/AmbientBrightnessStatsTrackerTest.java
+++ b/services/tests/servicestests/src/com/android/server/display/AmbientBrightnessStatsTrackerTest.java
@@ -424,7 +424,7 @@
 
         @Override
         public LocalDate getLocalDate() {
-            return LocalDate.from(mLocalDate);
+            return mLocalDate;
         }
     }
 
diff --git a/services/tests/servicestests/src/com/android/server/display/TestUtils.java b/services/tests/servicestests/src/com/android/server/display/TestUtils.java
index 0454587..a419b3f 100644
--- a/services/tests/servicestests/src/com/android/server/display/TestUtils.java
+++ b/services/tests/servicestests/src/com/android/server/display/TestUtils.java
@@ -51,6 +51,12 @@
         }
     }
 
+    public static void setMaximumRange(Sensor sensor, float maximumRange) throws Exception {
+        Method setter = Sensor.class.getDeclaredMethod("setRange", Float.TYPE, Float.TYPE);
+        setter.setAccessible(true);
+        setter.invoke(sensor, maximumRange, 1);
+    }
+
     public static Sensor createSensor(int type, String strType) throws Exception {
         Constructor<Sensor> constr = Sensor.class.getDeclaredConstructor();
         constr.setAccessible(true);
@@ -59,6 +65,16 @@
         return sensor;
     }
 
+    public static Sensor createSensor(int type, String strType, float maximumRange)
+            throws Exception {
+        Constructor<Sensor> constr = Sensor.class.getDeclaredConstructor();
+        constr.setAccessible(true);
+        Sensor sensor = constr.newInstance();
+        setSensorType(sensor, type, strType);
+        setMaximumRange(sensor, maximumRange);
+        return sensor;
+    }
+
     /**
      * Create a custom {@link DisplayAddress} to ensure we're not relying on any specific
      * display-address implementation in our code. Intentionally uses default object (reference)
diff --git a/services/tests/servicestests/src/com/android/server/display/brightness/BrightnessEventTest.java b/services/tests/servicestests/src/com/android/server/display/brightness/BrightnessEventTest.java
index fabf535..d332b30 100644
--- a/services/tests/servicestests/src/com/android/server/display/brightness/BrightnessEventTest.java
+++ b/services/tests/servicestests/src/com/android/server/display/brightness/BrightnessEventTest.java
@@ -39,8 +39,6 @@
                 getReason(BrightnessReason.REASON_DOZE, BrightnessReason.MODIFIER_LOW_POWER));
         mBrightnessEvent.setPhysicalDisplayId("test");
         mBrightnessEvent.setLux(100.0f);
-        mBrightnessEvent.setFastAmbientLux(90.0f);
-        mBrightnessEvent.setSlowAmbientLux(85.0f);
         mBrightnessEvent.setPreThresholdLux(150.0f);
         mBrightnessEvent.setTime(System.currentTimeMillis());
         mBrightnessEvent.setInitialBrightness(25.0f);
@@ -50,6 +48,7 @@
         mBrightnessEvent.setRbcStrength(-1);
         mBrightnessEvent.setThermalMax(0.65f);
         mBrightnessEvent.setPowerFactor(0.2f);
+        mBrightnessEvent.setWasShortTermModelActive(true);
         mBrightnessEvent.setHbmMode(BrightnessInfo.HIGH_BRIGHTNESS_MODE_OFF);
         mBrightnessEvent.setFlags(0);
         mBrightnessEvent.setAdjustmentFlags(0);
@@ -69,9 +68,9 @@
         String actualString = mBrightnessEvent.toString(false);
         String expectedString =
                 "BrightnessEvent: disp=1, physDisp=test, brt=0.6, initBrt=25.0, rcmdBrt=0.6,"
-                + " preBrt=NaN, lux=100.0, fastLux=90.0, slowLux=85.0, preLux=150.0, hbmMax=0.62,"
-                + " hbmMode=off, rbcStrength=-1, thrmMax=0.65, powerFactor=0.2, flags=, reason=doze"
-                + " [ low_pwr ], autoBrightness=true";
+                + " preBrt=NaN, lux=100.0, preLux=150.0, hbmMax=0.62, hbmMode=off, rbcStrength=-1,"
+                + " thrmMax=0.65, powerFactor=0.2, wasShortTermModelActive=true, flags=,"
+                + " reason=doze [ low_pwr ], autoBrightness=true";
         assertEquals(expectedString, actualString);
     }
 
diff --git a/services/tests/servicestests/src/com/android/server/inputmethod/InputMethodSubtypeSwitchingControllerTest.java b/services/tests/servicestests/src/com/android/server/inputmethod/InputMethodSubtypeSwitchingControllerTest.java
index 9092ec3..0884b78 100644
--- a/services/tests/servicestests/src/com/android/server/inputmethod/InputMethodSubtypeSwitchingControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/inputmethod/InputMethodSubtypeSwitchingControllerTest.java
@@ -367,6 +367,7 @@
         assertFalse(item_en_us_allcaps.mIsSystemLocale);
     }
 
+    @SuppressWarnings("SelfComparison")
     @Test
     public void testImeSubtypeListComparator() throws Exception {
         final ComponentName imeX1 = new ComponentName("com.example.imeX", "Ime1");
diff --git a/services/tests/servicestests/src/com/android/server/job/JobStoreTest.java b/services/tests/servicestests/src/com/android/server/job/JobStoreTest.java
index 164161e..dc47b5e 100644
--- a/services/tests/servicestests/src/com/android/server/job/JobStoreTest.java
+++ b/services/tests/servicestests/src/com/android/server/job/JobStoreTest.java
@@ -8,7 +8,6 @@
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertThrows;
 import static org.junit.Assert.assertTrue;
-import static org.junit.Assert.fail;
 import static org.mockito.ArgumentMatchers.anyString;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.when;
@@ -20,8 +19,6 @@
 import android.content.pm.PackageManagerInternal;
 import android.net.NetworkRequest;
 import android.os.Build;
-import android.os.Parcel;
-import android.os.Parcelable;
 import android.os.PersistableBundle;
 import android.os.SystemClock;
 import android.test.RenamingDelegatingContext;
@@ -32,7 +29,6 @@
 import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
 
-import com.android.internal.util.HexDump;
 import com.android.server.LocalServices;
 import com.android.server.job.JobStore.JobSet;
 import com.android.server.job.controllers.JobStatus;
@@ -44,7 +40,6 @@
 
 import java.time.Clock;
 import java.time.ZoneOffset;
-import java.util.Arrays;
 import java.util.Iterator;
 
 /**
@@ -143,15 +138,8 @@
 
         assertEquals("Didn't get expected number of persisted tasks.", 1, jobStatusSet.size());
         final JobStatus loadedTaskStatus = jobStatusSet.getAllJobs().get(0);
-        assertTasksEqual(task, loadedTaskStatus.getJob());
+        assertJobsEqual(ts, loadedTaskStatus);
         assertTrue("JobStore#contains invalid.", mTaskStoreUnderTest.containsJob(ts));
-        assertEquals("Different uids.", SOME_UID, loadedTaskStatus.getUid());
-        assertEquals(JobStatus.INTERNAL_FLAG_HAS_FOREGROUND_EXEMPTION,
-                loadedTaskStatus.getInternalFlags());
-        compareTimestampsSubjectToIoLatency("Early run-times not the same after read.",
-                ts.getEarliestRunTime(), loadedTaskStatus.getEarliestRunTime());
-        compareTimestampsSubjectToIoLatency("Late run-times not the same after read.",
-                ts.getLatestRunTimeElapsed(), loadedTaskStatus.getLatestRunTimeElapsed());
     }
 
     @Test
@@ -202,19 +190,10 @@
             loaded2 = tmp;
         }
 
-        assertTasksEqual(task1, loaded1.getJob());
-        assertTasksEqual(task2, loaded2.getJob());
+        assertJobsEqual(taskStatus1, loaded1);
+        assertJobsEqual(taskStatus2, loaded2);
         assertTrue("JobStore#contains invalid.", mTaskStoreUnderTest.containsJob(taskStatus1));
         assertTrue("JobStore#contains invalid.", mTaskStoreUnderTest.containsJob(taskStatus2));
-        // Check that the loaded task has the correct runtimes.
-        compareTimestampsSubjectToIoLatency("Early run-times not the same after read.",
-                taskStatus1.getEarliestRunTime(), loaded1.getEarliestRunTime());
-        compareTimestampsSubjectToIoLatency("Late run-times not the same after read.",
-                taskStatus1.getLatestRunTimeElapsed(), loaded1.getLatestRunTimeElapsed());
-        compareTimestampsSubjectToIoLatency("Early run-times not the same after read.",
-                taskStatus2.getEarliestRunTime(), loaded2.getEarliestRunTime());
-        compareTimestampsSubjectToIoLatency("Late run-times not the same after read.",
-                taskStatus2.getLatestRunTimeElapsed(), loaded2.getLatestRunTimeElapsed());
     }
 
     @Test
@@ -240,7 +219,7 @@
         mTaskStoreUnderTest.readJobMapFromDisk(jobStatusSet, true);
         assertEquals("Incorrect # of persisted tasks.", 1, jobStatusSet.size());
         JobStatus loaded = jobStatusSet.getAllJobs().iterator().next();
-        assertTasksEqual(task, loaded.getJob());
+        assertJobsEqual(taskStatus, loaded);
     }
 
     @Test
@@ -544,71 +523,30 @@
         final JobSet jobStatusSet = new JobSet();
         mTaskStoreUnderTest.readJobMapFromDisk(jobStatusSet, true);
         final JobStatus second = jobStatusSet.getAllJobs().iterator().next();
-        assertTasksEqual(first.getJob(), second.getJob());
+        assertJobsEqual(first, second);
     }
 
     /**
-     * Helper function to throw an error if the provided task and TaskStatus objects are not equal.
+     * Helper function to throw an error if the provided JobStatus objects are not equal.
      */
-    private void assertTasksEqual(JobInfo first, JobInfo second) {
-        assertEquals("Different task ids.", first.getId(), second.getId());
-        assertEquals("Different components.", first.getService(), second.getService());
-        assertEquals("Different periodic status.", first.isPeriodic(), second.isPeriodic());
-        assertEquals("Different period.", first.getIntervalMillis(), second.getIntervalMillis());
-        assertEquals("Different inital backoff.", first.getInitialBackoffMillis(),
-                second.getInitialBackoffMillis());
-        assertEquals("Different backoff policy.", first.getBackoffPolicy(),
-                second.getBackoffPolicy());
+    private void assertJobsEqual(JobStatus expected, JobStatus actual) {
+        assertEquals(expected.getJob(), actual.getJob());
 
-        assertEquals("Invalid charging constraint.", first.isRequireCharging(),
-                second.isRequireCharging());
-        assertEquals("Invalid battery not low constraint.", first.isRequireBatteryNotLow(),
-                second.isRequireBatteryNotLow());
-        assertEquals("Invalid idle constraint.", first.isRequireDeviceIdle(),
-                second.isRequireDeviceIdle());
-        assertEquals("Invalid network type.",
-                first.getNetworkType(), second.getNetworkType());
-        assertEquals("Invalid network.",
-                first.getRequiredNetwork(), second.getRequiredNetwork());
-        assertEquals("Download bytes don't match",
-                first.getEstimatedNetworkDownloadBytes(),
-                second.getEstimatedNetworkDownloadBytes());
-        assertEquals("Upload bytes don't match",
-                first.getEstimatedNetworkUploadBytes(),
-                second.getEstimatedNetworkUploadBytes());
-        assertEquals("Minimum chunk bytes don't match",
-                first.getMinimumNetworkChunkBytes(),
-                second.getMinimumNetworkChunkBytes());
-        assertEquals("Invalid deadline constraint.",
-                first.hasLateConstraint(),
-                second.hasLateConstraint());
-        assertEquals("Invalid delay constraint.",
-                first.hasEarlyConstraint(),
-                second.hasEarlyConstraint());
-        assertEquals("Extras don't match",
-                first.getExtras().toString(), second.getExtras().toString());
-        assertEquals("Transient xtras don't match",
-                first.getTransientExtras().toString(), second.getTransientExtras().toString());
+        // Source UID isn't persisted, but the rest of the app info is.
+        assertEquals("Source package not equal",
+                expected.getSourcePackageName(), actual.getSourcePackageName());
+        assertEquals("Source user not equal", expected.getSourceUserId(), actual.getSourceUserId());
+        assertEquals("Calling UID not equal", expected.getUid(), actual.getUid());
+        assertEquals("Calling user not equal", expected.getUserId(), actual.getUserId());
 
-        // Since people can forget to add tests here for new fields, do one last
-        // validity check based on bits-on-wire equality.
-        final byte[] firstBytes = marshall(first);
-        final byte[] secondBytes = marshall(second);
-        if (!Arrays.equals(firstBytes, secondBytes)) {
-            Log.w(TAG, "First: " + HexDump.dumpHexString(firstBytes));
-            Log.w(TAG, "Second: " + HexDump.dumpHexString(secondBytes));
-            fail("Raw JobInfo aren't equal; see logs for details");
-        }
-    }
+        assertEquals("Internal flags not equal",
+                expected.getInternalFlags(), actual.getInternalFlags());
 
-    private static byte[] marshall(Parcelable p) {
-        final Parcel parcel = Parcel.obtain();
-        try {
-            p.writeToParcel(parcel, 0);
-            return parcel.marshall();
-        } finally {
-            parcel.recycle();
-        }
+        // Check that the loaded task has the correct runtimes.
+        compareTimestampsSubjectToIoLatency("Early run-times not the same after read.",
+                expected.getEarliestRunTime(), actual.getEarliestRunTime());
+        compareTimestampsSubjectToIoLatency("Late run-times not the same after read.",
+                expected.getLatestRunTimeElapsed(), actual.getLatestRunTimeElapsed());
     }
 
     /**
@@ -623,5 +561,4 @@
     }
 
     private static class StubClass {}
-
 }
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/PasswordSlotManagerTestable.java b/services/tests/servicestests/src/com/android/server/locksettings/PasswordSlotManagerTestable.java
index 1e855a9..1eb4fa5 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/PasswordSlotManagerTestable.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/PasswordSlotManagerTestable.java
@@ -58,4 +58,4 @@
         } catch (Exception e) {
         }
     }
-};
+}
diff --git a/services/tests/servicestests/src/com/android/server/media/MediaButtonReceiverHolderTest.java b/services/tests/servicestests/src/com/android/server/media/MediaButtonReceiverHolderTest.java
new file mode 100644
index 0000000..1c4ee69
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/media/MediaButtonReceiverHolderTest.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.media;
+
+import android.app.PendingIntent;
+import android.content.Context;
+import android.content.Intent;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import com.google.common.truth.Truth;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+public class MediaButtonReceiverHolderTest {
+
+    @Test
+    public void createMediaButtonReceiverHolder_resolvesNullComponentName() {
+        Context context = InstrumentationRegistry.getInstrumentation().getContext();
+        Intent intent = new Intent(Intent.ACTION_MEDIA_BUTTON);
+        PendingIntent pi = PendingIntent.getBroadcast(context, /* requestCode= */ 0, intent,
+                PendingIntent.FLAG_IMMUTABLE);
+        MediaButtonReceiverHolder a = MediaButtonReceiverHolder.create(/* userId= */ 0, pi,
+                context.getPackageName());
+        Truth.assertWithMessage("Component name must match PendingIntent creator package.").that(
+                a.getComponentName()).isNull();
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/media/OWNERS b/services/tests/servicestests/src/com/android/server/media/OWNERS
new file mode 100644
index 0000000..55ffde2
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/media/OWNERS
@@ -0,0 +1,2 @@
+# Bug component: 137631
+include platform/frameworks/av:/media/janitors/media_solutions_OWNERS
\ No newline at end of file
diff --git a/services/tests/servicestests/test-apps/FakeMediaApp/Android.bp b/services/tests/servicestests/test-apps/FakeMediaApp/Android.bp
new file mode 100644
index 0000000..a4041b7
--- /dev/null
+++ b/services/tests/servicestests/test-apps/FakeMediaApp/Android.bp
@@ -0,0 +1,37 @@
+// Copyright (C) 2019 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package {
+    // See: http://go/android-license-faq
+    // A large-scale-change added 'default_applicable_licenses' to import
+    // all of the 'license_kinds' from "frameworks_base_license"
+    // to get the below license kinds:
+    //   SPDX-license-identifier-Apache-2.0
+    default_applicable_licenses: ["frameworks_base_license"],
+}
+
+android_test_helper_app {
+    name: "FakeMediaApp",
+
+    sdk_version: "current",
+
+    srcs: ["**/*.java"],
+
+    dex_preopt: {
+        enabled: false,
+    },
+    optimize: {
+        enabled: false,
+    },
+}
diff --git a/services/tests/servicestests/test-apps/FakeMediaApp/AndroidManifest.xml b/services/tests/servicestests/test-apps/FakeMediaApp/AndroidManifest.xml
new file mode 100644
index 0000000..c08ee7a
--- /dev/null
+++ b/services/tests/servicestests/test-apps/FakeMediaApp/AndroidManifest.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+        package="com.android.servicestests.apps.fakemediaapp">
+
+    <application>
+        <receiver
+            android:name=".FakeMediaButtonBroadcastReceiver"
+            android:enabled="true"
+            android:exported="true">
+            <intent-filter>
+                <action android:name="android.intent.action.MEDIA_BUTTON" />
+            </intent-filter>
+        </receiver>
+    </application>
+
+</manifest>
diff --git a/services/tests/servicestests/test-apps/FakeMediaApp/OWNERS b/services/tests/servicestests/test-apps/FakeMediaApp/OWNERS
new file mode 100644
index 0000000..55ffde2
--- /dev/null
+++ b/services/tests/servicestests/test-apps/FakeMediaApp/OWNERS
@@ -0,0 +1,2 @@
+# Bug component: 137631
+include platform/frameworks/av:/media/janitors/media_solutions_OWNERS
\ No newline at end of file
diff --git a/services/tests/servicestests/test-apps/FakeMediaApp/src/FakeMediaButtonBroadcastReceiver.java b/services/tests/servicestests/test-apps/FakeMediaApp/src/FakeMediaButtonBroadcastReceiver.java
new file mode 100644
index 0000000..41f0cf5
--- /dev/null
+++ b/services/tests/servicestests/test-apps/FakeMediaApp/src/FakeMediaButtonBroadcastReceiver.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright 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.servicestests.apps.fakemediaapp;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.util.Log;
+
+public class FakeMediaButtonBroadcastReceiver extends BroadcastReceiver {
+
+    private static final String TAG = "FakeMediaButtonBroadcastReceiver";
+
+    @Override
+    public void onReceive(Context context, Intent intent) {
+        Log.v(TAG, "onReceive not expected");
+    }
+}
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
index 668345d..afec085 100755
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
@@ -1101,6 +1101,10 @@
                 new NotificationChannel("id", "name", IMPORTANCE_HIGH);
         mBinderService.updateNotificationChannelForPackage(PKG, mUid, updatedChannel);
 
+        // pretend only this following part is called by the app (system permissions are required to
+        // update the notification channel on behalf of the user above)
+        mService.isSystemUid = false;
+
         // Recreating with a lower importance leaves channel unchanged.
         final NotificationChannel dupeChannel =
                 new NotificationChannel("id", "name", NotificationManager.IMPORTANCE_LOW);
@@ -1126,6 +1130,46 @@
     }
 
     @Test
+    public void testCreateNotificationChannels_fromAppCannotSetFields() throws Exception {
+        // Confirm that when createNotificationChannels is called from the relevant app and not
+        // system, then it cannot set fields that can't be set by apps
+        mService.isSystemUid = false;
+
+        final NotificationChannel channel =
+                new NotificationChannel("id", "name", IMPORTANCE_DEFAULT);
+        channel.setBypassDnd(true);
+        channel.setAllowBubbles(true);
+
+        mBinderService.createNotificationChannels(PKG,
+                new ParceledListSlice(Arrays.asList(channel)));
+
+        final NotificationChannel createdChannel =
+                mBinderService.getNotificationChannel(PKG, mContext.getUserId(), PKG, "id");
+        assertFalse(createdChannel.canBypassDnd());
+        assertFalse(createdChannel.canBubble());
+    }
+
+    @Test
+    public void testCreateNotificationChannels_fromSystemCanSetFields() throws Exception {
+        // Confirm that when createNotificationChannels is called from system,
+        // then it can set fields that can't be set by apps
+        mService.isSystemUid = true;
+
+        final NotificationChannel channel =
+                new NotificationChannel("id", "name", IMPORTANCE_DEFAULT);
+        channel.setBypassDnd(true);
+        channel.setAllowBubbles(true);
+
+        mBinderService.createNotificationChannels(PKG,
+                new ParceledListSlice(Arrays.asList(channel)));
+
+        final NotificationChannel createdChannel =
+                mBinderService.getNotificationChannel(PKG, mContext.getUserId(), PKG, "id");
+        assertTrue(createdChannel.canBypassDnd());
+        assertTrue(createdChannel.canBubble());
+    }
+
+    @Test
     public void testBlockedNotifications_suspended() throws Exception {
         when(mPackageManager.isPackageSuspendedForUser(anyString(), anyInt())).thenReturn(true);
 
@@ -3088,6 +3132,8 @@
 
     @Test
     public void testDeleteChannelGroupChecksForFgses() throws Exception {
+        // the setup for this test requires it to seem like it's coming from the app
+        mService.isSystemUid = false;
         when(mCompanionMgr.getAssociations(PKG, UserHandle.getUserId(mUid)))
                 .thenReturn(singletonList(mock(AssociationInfo.class)));
         CountDownLatch latch = new CountDownLatch(2);
@@ -3100,7 +3146,7 @@
             ParceledListSlice<NotificationChannel> pls =
                     new ParceledListSlice(ImmutableList.of(notificationChannel));
             try {
-                mBinderService.createNotificationChannelsForPackage(PKG, mUid, pls);
+                mBinderService.createNotificationChannels(PKG, pls);
             } catch (RemoteException e) {
                 throw new RuntimeException(e);
             }
@@ -3119,8 +3165,10 @@
                 ParceledListSlice<NotificationChannel> pls =
                         new ParceledListSlice(ImmutableList.of(notificationChannel));
                 try {
-                mBinderService.createNotificationChannelsForPackage(PKG, mUid, pls);
-                mBinderService.deleteNotificationChannelGroup(PKG, "group");
+                    // Because existing channels won't have their groups overwritten when the call
+                    // is from the app, this call won't take the channel out of the group
+                    mBinderService.createNotificationChannels(PKG, pls);
+                    mBinderService.deleteNotificationChannelGroup(PKG, "group");
                 } catch (RemoteException e) {
                     throw new RuntimeException(e);
                 }
@@ -7605,6 +7653,65 @@
     }
 
     @Test
+    public void testAddAutomaticZenRule_systemCallTakesPackageFromOwner() throws Exception {
+        mService.isSystemUid = true;
+        ZenModeHelper mockZenModeHelper = mock(ZenModeHelper.class);
+        when(mConditionProviders.isPackageOrComponentAllowed(anyString(), anyInt()))
+                .thenReturn(true);
+        mService.setZenHelper(mockZenModeHelper);
+        ComponentName owner = new ComponentName("android", "ProviderName");
+        ZenPolicy zenPolicy = new ZenPolicy.Builder().allowAlarms(true).build();
+        boolean isEnabled = true;
+        AutomaticZenRule rule = new AutomaticZenRule("test", owner, owner, mock(Uri.class),
+                zenPolicy, NotificationManager.INTERRUPTION_FILTER_PRIORITY, isEnabled);
+        mBinderService.addAutomaticZenRule(rule, "com.android.settings");
+
+        // verify that zen mode helper gets passed in a package name of "android"
+        verify(mockZenModeHelper).addAutomaticZenRule(eq("android"), eq(rule), anyString());
+    }
+
+    @Test
+    public void testAddAutomaticZenRule_systemAppIdCallTakesPackageFromOwner() throws Exception {
+        // The multi-user case: where the calling uid doesn't match the system uid, but the calling
+        // *appid* is the system.
+        mService.isSystemUid = false;
+        mService.isSystemAppId = true;
+        ZenModeHelper mockZenModeHelper = mock(ZenModeHelper.class);
+        when(mConditionProviders.isPackageOrComponentAllowed(anyString(), anyInt()))
+                .thenReturn(true);
+        mService.setZenHelper(mockZenModeHelper);
+        ComponentName owner = new ComponentName("android", "ProviderName");
+        ZenPolicy zenPolicy = new ZenPolicy.Builder().allowAlarms(true).build();
+        boolean isEnabled = true;
+        AutomaticZenRule rule = new AutomaticZenRule("test", owner, owner, mock(Uri.class),
+                zenPolicy, NotificationManager.INTERRUPTION_FILTER_PRIORITY, isEnabled);
+        mBinderService.addAutomaticZenRule(rule, "com.android.settings");
+
+        // verify that zen mode helper gets passed in a package name of "android"
+        verify(mockZenModeHelper).addAutomaticZenRule(eq("android"), eq(rule), anyString());
+    }
+
+    @Test
+    public void testAddAutomaticZenRule_nonSystemCallTakesPackageFromArg() throws Exception {
+        mService.isSystemUid = false;
+        mService.isSystemAppId = false;
+        ZenModeHelper mockZenModeHelper = mock(ZenModeHelper.class);
+        when(mConditionProviders.isPackageOrComponentAllowed(anyString(), anyInt()))
+                .thenReturn(true);
+        mService.setZenHelper(mockZenModeHelper);
+        ComponentName owner = new ComponentName("android", "ProviderName");
+        ZenPolicy zenPolicy = new ZenPolicy.Builder().allowAlarms(true).build();
+        boolean isEnabled = true;
+        AutomaticZenRule rule = new AutomaticZenRule("test", owner, owner, mock(Uri.class),
+                zenPolicy, NotificationManager.INTERRUPTION_FILTER_PRIORITY, isEnabled);
+        mBinderService.addAutomaticZenRule(rule, "another.package");
+
+        // verify that zen mode helper gets passed in the package name from the arg, not the owner
+        verify(mockZenModeHelper).addAutomaticZenRule(
+                eq("another.package"), eq(rule), anyString());
+    }
+
+    @Test
     public void testAreNotificationsEnabledForPackage() throws Exception {
         mBinderService.areNotificationsEnabledForPackage(mContext.getPackageName(),
                 mUid);
@@ -8622,7 +8729,7 @@
         assertEquals("friend", friendChannel.getConversationId());
         assertEquals(null, original.getConversationId());
         assertEquals(original.canShowBadge(), friendChannel.canShowBadge());
-        assertFalse(friendChannel.canBubble()); // can't be modified by app
+        assertEquals(original.canBubble(), friendChannel.canBubble()); // called by system
         assertFalse(original.getId().equals(friendChannel.getId()));
         assertNotNull(friendChannel.getId());
     }
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java
index 0f93598..b64b281 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java
@@ -2727,7 +2727,7 @@
 
     @Test
     public void testCreateChannel_addToGroup() {
-        NotificationChannelGroup group = new NotificationChannelGroup("group", "");
+        NotificationChannelGroup group = new NotificationChannelGroup("group", "group");
         mHelper.createNotificationChannelGroup(PKG_N_MR1, UID_N_MR1, group, true);
         NotificationChannel nc = new NotificationChannel("id", "hello", IMPORTANCE_DEFAULT);
         assertTrue(mHelper.createNotificationChannel(PKG_N_MR1, UID_N_MR1, nc, true, false));
@@ -3177,8 +3177,8 @@
 
     @Test
     public void testGetNotificationChannelGroupWithChannels() throws Exception {
-        NotificationChannelGroup group = new NotificationChannelGroup("group", "");
-        NotificationChannelGroup other = new NotificationChannelGroup("something else", "");
+        NotificationChannelGroup group = new NotificationChannelGroup("group", "group");
+        NotificationChannelGroup other = new NotificationChannelGroup("something else", "name");
         mHelper.createNotificationChannelGroup(PKG_N_MR1, UID_N_MR1, group, true);
         mHelper.createNotificationChannelGroup(PKG_N_MR1, UID_N_MR1, other, true);
 
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/TestableNotificationManagerService.java b/services/tests/uiservicestests/src/com/android/server/notification/TestableNotificationManagerService.java
index 8cf74fb..61a6985 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/TestableNotificationManagerService.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/TestableNotificationManagerService.java
@@ -32,6 +32,7 @@
 public class TestableNotificationManagerService extends NotificationManagerService {
     int countSystemChecks = 0;
     boolean isSystemUid = true;
+    boolean isSystemAppId = true;
     int countLogSmartSuggestionsVisible = 0;
     Set<Integer> mChannelToastsSent = new HashSet<>();
 
@@ -58,6 +59,12 @@
     }
 
     @Override
+    protected boolean isCallingAppIdSystem() {
+        countSystemChecks++;
+        return isSystemUid || isSystemAppId;
+    }
+
+    @Override
     protected boolean isCallerSystemOrPhone() {
         countSystemChecks++;
         return isSystemUid;
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java
index ba61980..49edde5 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java
@@ -1672,6 +1672,36 @@
     }
 
     @Test
+    public void testAddAutomaticZenRule_claimedSystemOwner() {
+        // Make sure anything that claims to have a "system" owner but not actually part of the
+        // system package still gets limited on number of rules
+        for (int i = 0; i < RULE_LIMIT_PER_PACKAGE; i++) {
+            ScheduleInfo si = new ScheduleInfo();
+            si.startHour = i;
+            AutomaticZenRule zenRule = new AutomaticZenRule("name" + i,
+                    new ComponentName("android", "ScheduleConditionProvider" + i),
+                    null, // configuration activity
+                    ZenModeConfig.toScheduleConditionId(si),
+                    new ZenPolicy.Builder().build(),
+                    NotificationManager.INTERRUPTION_FILTER_PRIORITY, true);
+            String id = mZenModeHelperSpy.addAutomaticZenRule("pkgname", zenRule, "test");
+            assertNotNull(id);
+        }
+        try {
+            AutomaticZenRule zenRule = new AutomaticZenRule("name",
+                    new ComponentName("android", "ScheduleConditionProviderFinal"),
+                    null, // configuration activity
+                    ZenModeConfig.toScheduleConditionId(new ScheduleInfo()),
+                    new ZenPolicy.Builder().build(),
+                    NotificationManager.INTERRUPTION_FILTER_PRIORITY, true);
+            String id = mZenModeHelperSpy.addAutomaticZenRule("pkgname", zenRule, "test");
+            fail("allowed too many rules to be created");
+        } catch (IllegalArgumentException e) {
+            // yay
+        }
+    }
+
+    @Test
     public void testAddAutomaticZenRule_CA() {
         AutomaticZenRule zenRule = new AutomaticZenRule("name",
                 null,
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java
index 07dba00..fc1989e 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java
@@ -1262,6 +1262,26 @@
     }
 
     @Test
+    public void testRecycleTaskWakeUpWhenDreaming() {
+        doNothing().when(mWm.mAtmService.mTaskSupervisor).wakeUp(anyString());
+        doReturn(true).when(mWm.mAtmService).isDreaming();
+        final ActivityStarter starter = prepareStarter(0 /* flags */);
+        final ActivityRecord target = new ActivityBuilder(mAtm).setCreateTask(true).build();
+        starter.mStartActivity = target;
+        target.mVisibleRequested = false;
+        target.setTurnScreenOn(true);
+        // Assume the flag was consumed by relayout.
+        target.setCurrentLaunchCanTurnScreenOn(false);
+        startActivityInner(starter, target, null /* source */, null /* options */,
+                null /* inTask */, null /* inTaskFragment */);
+        // The flag should be set again when resuming (from recycleTask) the target as top.
+        assertTrue(target.currentLaunchCanTurnScreenOn());
+        // In real case, dream activity has a higher priority (TaskDisplayArea#getPriority) that
+        // will be put at a higher z-order. So it relies on wakeUp() to be dismissed.
+        verify(mWm.mAtmService.mTaskSupervisor).wakeUp(anyString());
+    }
+
+    @Test
     public void testTargetTaskInSplitScreen() {
         final ActivityStarter starter =
                 prepareStarter(FLAG_ACTIVITY_LAUNCH_ADJACENT, false /* mockGetRootTask */);
diff --git a/services/tests/wmtests/src/com/android/server/wm/FrameRateSelectionPriorityTests.java b/services/tests/wmtests/src/com/android/server/wm/FrameRateSelectionPriorityTests.java
index 13ebc93..0568b38 100644
--- a/services/tests/wmtests/src/com/android/server/wm/FrameRateSelectionPriorityTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/FrameRateSelectionPriorityTests.java
@@ -25,6 +25,7 @@
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
 
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotNull;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.when;
@@ -36,6 +37,8 @@
 import android.view.Surface;
 import android.view.SurfaceControl;
 
+import com.android.server.wm.RefreshRatePolicy.FrameRateVote;
+
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -50,12 +53,18 @@
 @Presubmit
 @RunWith(WindowTestRunner.class)
 public class FrameRateSelectionPriorityTests extends WindowTestsBase {
-    private static final float FLOAT_TOLERANCE = 0.01f;
     private static final int LOW_MODE_ID = 3;
 
     private DisplayPolicy mDisplayPolicy = mock(DisplayPolicy.class);
     private RefreshRatePolicy mRefreshRatePolicy;
     private HighRefreshRateDenylist mDenylist = mock(HighRefreshRateDenylist.class);
+    private FrameRateVote mTempFrameRateVote = new FrameRateVote();
+
+    private static final FrameRateVote FRAME_RATE_VOTE_NONE = new FrameRateVote();
+    private static final FrameRateVote FRAME_RATE_VOTE_60_EXACT =
+            new FrameRateVote(60, Surface.FRAME_RATE_COMPATIBILITY_EXACT);
+    private static final FrameRateVote FRAME_RATE_VOTE_60_PREFERRED =
+            new FrameRateVote(60, Surface.FRAME_RATE_COMPATIBILITY_DEFAULT);
 
     WindowState createWindow(String name) {
         WindowState window = createWindow(null, TYPE_APPLICATION, name);
@@ -85,12 +94,12 @@
         assertNotNull("Window state is created", appWindow);
 
         assertEquals(appWindow.mFrameRateSelectionPriority, RefreshRatePolicy.LAYER_PRIORITY_UNSET);
-        assertEquals(appWindow.mAppPreferredFrameRate, 0, FLOAT_TOLERANCE);
+        assertEquals(appWindow.mFrameRateVote, FRAME_RATE_VOTE_NONE);
 
         appWindow.updateFrameRateSelectionPriorityIfNeeded();
         // Priority doesn't change.
         assertEquals(appWindow.mFrameRateSelectionPriority, RefreshRatePolicy.LAYER_PRIORITY_UNSET);
-        assertEquals(appWindow.mAppPreferredFrameRate, 0, FLOAT_TOLERANCE);
+        assertEquals(appWindow.mFrameRateVote, FRAME_RATE_VOTE_NONE);
 
         // Call the function a few times.
         appWindow.updateFrameRateSelectionPriorityIfNeeded();
@@ -109,16 +118,15 @@
         assertEquals(appWindow.mFrameRateSelectionPriority, RefreshRatePolicy.LAYER_PRIORITY_UNSET);
         assertEquals(appWindow.getDisplayContent().getDisplayPolicy().getRefreshRatePolicy()
                 .getPreferredModeId(appWindow), 0);
-        assertEquals(appWindow.mAppPreferredFrameRate, 0, FLOAT_TOLERANCE);
-        assertEquals(appWindow.getDisplayContent().getDisplayPolicy().getRefreshRatePolicy()
-                .getPreferredRefreshRate(appWindow), 0, FLOAT_TOLERANCE);
-
-        assertEquals(appWindow.mAppPreferredFrameRate, 0, FLOAT_TOLERANCE);
+        assertEquals(appWindow.mFrameRateVote, FRAME_RATE_VOTE_NONE);
+        assertFalse(appWindow.getDisplayContent().getDisplayPolicy().getRefreshRatePolicy()
+                .updateFrameRateVote(appWindow));
+        assertEquals(appWindow.mFrameRateVote, FRAME_RATE_VOTE_NONE);
 
         appWindow.updateFrameRateSelectionPriorityIfNeeded();
         // Priority stays MAX_VALUE.
         assertEquals(appWindow.mFrameRateSelectionPriority, RefreshRatePolicy.LAYER_PRIORITY_UNSET);
-        assertEquals(appWindow.mAppPreferredFrameRate, 0, FLOAT_TOLERANCE);
+        assertEquals(appWindow.mFrameRateVote, FRAME_RATE_VOTE_NONE);
         verify(appWindow.getPendingTransaction(), never()).setFrameRateSelectionPriority(
                 appWindow.getSurfaceControl(), RefreshRatePolicy.LAYER_PRIORITY_UNSET);
 
@@ -127,7 +135,7 @@
         appWindow.updateFrameRateSelectionPriorityIfNeeded();
         // Priority changes to 1.
         assertEquals(appWindow.mFrameRateSelectionPriority, 1);
-        assertEquals(appWindow.mAppPreferredFrameRate, 0, FLOAT_TOLERANCE);
+        assertEquals(appWindow.mFrameRateVote, FRAME_RATE_VOTE_NONE);
         verify(appWindow.getPendingTransaction()).setFrameRateSelectionPriority(
                 appWindow.getSurfaceControl(), 1);
         verify(appWindow.getPendingTransaction(), never()).setFrameRate(
@@ -138,27 +146,27 @@
     public void testApplicationInFocusWithModeId() {
         final WindowState appWindow = createWindow("appWindow");
         assertEquals(appWindow.mFrameRateSelectionPriority, RefreshRatePolicy.LAYER_PRIORITY_UNSET);
-        assertEquals(appWindow.mAppPreferredFrameRate, 0, FLOAT_TOLERANCE);
+        assertEquals(appWindow.mFrameRateVote, FRAME_RATE_VOTE_NONE);
 
         // Application is in focus.
         appWindow.mToken.mDisplayContent.mCurrentFocus = appWindow;
         appWindow.updateFrameRateSelectionPriorityIfNeeded();
         // Priority changes.
         assertEquals(appWindow.mFrameRateSelectionPriority, 1);
-        assertEquals(appWindow.mAppPreferredFrameRate, 0, FLOAT_TOLERANCE);
+        assertEquals(appWindow.mFrameRateVote, FRAME_RATE_VOTE_NONE);
         // Update the mode ID to a requested number.
         appWindow.mAttrs.preferredDisplayModeId = 1;
         appWindow.updateFrameRateSelectionPriorityIfNeeded();
         // Priority changes.
         assertEquals(appWindow.mFrameRateSelectionPriority, 0);
-        assertEquals(appWindow.mAppPreferredFrameRate, 60, FLOAT_TOLERANCE);
+        assertEquals(appWindow.mFrameRateVote, FRAME_RATE_VOTE_60_EXACT);
 
         // Remove the mode ID request.
         appWindow.mAttrs.preferredDisplayModeId = 0;
         appWindow.updateFrameRateSelectionPriorityIfNeeded();
         // Priority changes.
         assertEquals(appWindow.mFrameRateSelectionPriority, 1);
-        assertEquals(appWindow.mAppPreferredFrameRate, 0, FLOAT_TOLERANCE);
+        assertEquals(appWindow.mFrameRateVote, FRAME_RATE_VOTE_NONE);
 
         // Verify we called actions on Transactions correctly.
         verify(appWindow.getPendingTransaction(), never()).setFrameRateSelectionPriority(
@@ -175,7 +183,7 @@
     public void testApplicationNotInFocusWithModeId() {
         final WindowState appWindow = createWindow("appWindow");
         assertEquals(appWindow.mFrameRateSelectionPriority, RefreshRatePolicy.LAYER_PRIORITY_UNSET);
-        assertEquals(appWindow.mAppPreferredFrameRate, 0, FLOAT_TOLERANCE);
+        assertEquals(appWindow.mFrameRateVote, FRAME_RATE_VOTE_NONE);
 
         final WindowState inFocusWindow = createWindow("inFocus");
         appWindow.mToken.mDisplayContent.mCurrentFocus = inFocusWindow;
@@ -183,14 +191,14 @@
         appWindow.updateFrameRateSelectionPriorityIfNeeded();
         // The window is not in focus.
         assertEquals(appWindow.mFrameRateSelectionPriority, RefreshRatePolicy.LAYER_PRIORITY_UNSET);
-        assertEquals(appWindow.mAppPreferredFrameRate, 0, FLOAT_TOLERANCE);
+        assertEquals(appWindow.mFrameRateVote, FRAME_RATE_VOTE_NONE);
 
         // Update the mode ID to a requested number.
         appWindow.mAttrs.preferredDisplayModeId = 1;
         appWindow.updateFrameRateSelectionPriorityIfNeeded();
         // Priority changes.
         assertEquals(appWindow.mFrameRateSelectionPriority, 2);
-        assertEquals(appWindow.mAppPreferredFrameRate, 60, FLOAT_TOLERANCE);
+        assertEquals(appWindow.mFrameRateVote, FRAME_RATE_VOTE_60_EXACT);
 
         verify(appWindow.getPendingTransaction()).setFrameRateSelectionPriority(
                 appWindow.getSurfaceControl(), RefreshRatePolicy.LAYER_PRIORITY_UNSET);
@@ -204,7 +212,7 @@
     public void testApplicationNotInFocusWithoutModeId() {
         final WindowState appWindow = createWindow("appWindow");
         assertEquals(appWindow.mFrameRateSelectionPriority, RefreshRatePolicy.LAYER_PRIORITY_UNSET);
-        assertEquals(appWindow.mAppPreferredFrameRate, 0, FLOAT_TOLERANCE);
+        assertEquals(appWindow.mFrameRateVote, FRAME_RATE_VOTE_NONE);
 
         final WindowState inFocusWindow = createWindow("inFocus");
         appWindow.mToken.mDisplayContent.mCurrentFocus = inFocusWindow;
@@ -212,14 +220,14 @@
         appWindow.updateFrameRateSelectionPriorityIfNeeded();
         // The window is not in focus.
         assertEquals(appWindow.mFrameRateSelectionPriority, RefreshRatePolicy.LAYER_PRIORITY_UNSET);
-        assertEquals(appWindow.mAppPreferredFrameRate, 0, FLOAT_TOLERANCE);
+        assertEquals(appWindow.mFrameRateVote, FRAME_RATE_VOTE_NONE);
 
         // Make sure that the mode ID is not set.
         appWindow.mAttrs.preferredDisplayModeId = 0;
         appWindow.updateFrameRateSelectionPriorityIfNeeded();
         // Priority doesn't change.
         assertEquals(appWindow.mFrameRateSelectionPriority, RefreshRatePolicy.LAYER_PRIORITY_UNSET);
-        assertEquals(appWindow.mAppPreferredFrameRate, 0, FLOAT_TOLERANCE);
+        assertEquals(appWindow.mFrameRateVote, FRAME_RATE_VOTE_NONE);
 
         verify(appWindow.getPendingTransaction()).setFrameRateSelectionPriority(
                 appWindow.getSurfaceControl(), RefreshRatePolicy.LAYER_PRIORITY_UNSET);
@@ -237,11 +245,10 @@
         when(mDenylist.isDenylisted("com.android.test")).thenReturn(true);
 
         assertEquals(0, mRefreshRatePolicy.getPreferredModeId(appWindow));
-        assertEquals(60, mRefreshRatePolicy.getPreferredRefreshRate(appWindow), FLOAT_TOLERANCE);
 
         appWindow.updateFrameRateSelectionPriorityIfNeeded();
         assertEquals(RefreshRatePolicy.LAYER_PRIORITY_UNSET, appWindow.mFrameRateSelectionPriority);
-        assertEquals(60, appWindow.mAppPreferredFrameRate, FLOAT_TOLERANCE);
+        assertEquals(FRAME_RATE_VOTE_60_EXACT, appWindow.mFrameRateVote);
 
         // Call the function a few times.
         appWindow.updateFrameRateSelectionPriorityIfNeeded();
@@ -262,19 +269,19 @@
                 .thenReturn(DisplayManager.SWITCHING_TYPE_NONE);
 
         assertEquals(appWindow.mFrameRateSelectionPriority, RefreshRatePolicy.LAYER_PRIORITY_UNSET);
-        assertEquals(appWindow.mAppPreferredFrameRate, 0, FLOAT_TOLERANCE);
+        assertEquals(appWindow.mFrameRateVote, FRAME_RATE_VOTE_NONE);
 
         // Update the mode ID to a requested number.
         appWindow.mAttrs.preferredDisplayModeId = 1;
         appWindow.updateFrameRateSelectionPriorityIfNeeded();
 
-        assertEquals(appWindow.mAppPreferredFrameRate, 0, FLOAT_TOLERANCE);
+        assertEquals(appWindow.mFrameRateVote, FRAME_RATE_VOTE_NONE);
 
         // Remove the mode ID request.
         appWindow.mAttrs.preferredDisplayModeId = 0;
         appWindow.updateFrameRateSelectionPriorityIfNeeded();
 
-        assertEquals(appWindow.mAppPreferredFrameRate, 0, FLOAT_TOLERANCE);
+        assertEquals(appWindow.mFrameRateVote, FRAME_RATE_VOTE_NONE);
 
         verify(appWindow.getPendingTransaction()).setFrameRateSelectionPriority(
                 appWindow.getSurfaceControl(), RefreshRatePolicy.LAYER_PRIORITY_UNSET);
@@ -292,11 +299,10 @@
         appWindow.mAttrs.preferredRefreshRate = 60;
 
         assertEquals(0, mRefreshRatePolicy.getPreferredModeId(appWindow));
-        assertEquals(60, mRefreshRatePolicy.getPreferredRefreshRate(appWindow), FLOAT_TOLERANCE);
 
         appWindow.updateFrameRateSelectionPriorityIfNeeded();
         assertEquals(RefreshRatePolicy.LAYER_PRIORITY_UNSET, appWindow.mFrameRateSelectionPriority);
-        assertEquals(60, appWindow.mAppPreferredFrameRate, FLOAT_TOLERANCE);
+        assertEquals(FRAME_RATE_VOTE_60_PREFERRED, appWindow.mFrameRateVote);
 
         // Call the function a few times.
         appWindow.updateFrameRateSelectionPriorityIfNeeded();
@@ -307,6 +313,6 @@
                 any(SurfaceControl.class), anyInt());
         verify(appWindow.getPendingTransaction(), times(1)).setFrameRate(
                 appWindow.getSurfaceControl(), 60,
-                Surface.FRAME_RATE_COMPATIBILITY_EXACT, Surface.CHANGE_FRAME_RATE_ALWAYS);
+                Surface.FRAME_RATE_COMPATIBILITY_DEFAULT, Surface.CHANGE_FRAME_RATE_ALWAYS);
     }
 }
diff --git a/services/tests/wmtests/src/com/android/server/wm/RefreshRatePolicyTest.java b/services/tests/wmtests/src/com/android/server/wm/RefreshRatePolicyTest.java
index 9d2eb26..bcaf886 100644
--- a/services/tests/wmtests/src/com/android/server/wm/RefreshRatePolicyTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/RefreshRatePolicyTest.java
@@ -16,22 +16,29 @@
 
 package com.android.server.wm;
 
+import static android.view.SurfaceControl.RefreshRateRange.FLOAT_TOLERANCE;
 import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
 
 import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_APP_TRANSITION;
 
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.when;
 
+import android.hardware.display.DisplayManager;
 import android.os.Parcel;
 import android.platform.test.annotations.Presubmit;
 import android.view.Display.Mode;
+import android.view.Surface;
 import android.view.WindowManager.LayoutParams;
 
 import androidx.test.filters.FlakyTest;
 import androidx.test.filters.SmallTest;
 
+import com.android.server.wm.RefreshRatePolicy.FrameRateVote;
+
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -45,7 +52,6 @@
 @RunWith(WindowTestRunner.class)
 @FlakyTest
 public class RefreshRatePolicyTest extends WindowTestsBase {
-    private static final float FLOAT_TOLERANCE = 0.01f;
     private static final int HI_MODE_ID = 1;
     private static final float HI_REFRESH_RATE = 90;
 
@@ -57,6 +63,19 @@
 
     private RefreshRatePolicy mPolicy;
     private HighRefreshRateDenylist mDenylist = mock(HighRefreshRateDenylist.class);
+    private FrameRateVote mTempFrameRateVote = new FrameRateVote();
+
+    private static final FrameRateVote FRAME_RATE_VOTE_NONE = new FrameRateVote();
+    private static final FrameRateVote FRAME_RATE_VOTE_DENY_LIST =
+            new FrameRateVote(LOW_REFRESH_RATE, Surface.FRAME_RATE_COMPATIBILITY_EXACT);
+    private static final FrameRateVote FRAME_RATE_VOTE_LOW_EXACT =
+            new FrameRateVote(LOW_REFRESH_RATE, Surface.FRAME_RATE_COMPATIBILITY_EXACT);
+    private static final FrameRateVote FRAME_RATE_VOTE_HI_EXACT =
+            new FrameRateVote(HI_REFRESH_RATE, Surface.FRAME_RATE_COMPATIBILITY_EXACT);
+    private static final FrameRateVote FRAME_RATE_VOTE_LOW_PREFERRED =
+            new FrameRateVote(LOW_REFRESH_RATE, Surface.FRAME_RATE_COMPATIBILITY_DEFAULT);
+    private static final FrameRateVote FRAME_RATE_VOTE_HI_PREFERRED =
+            new FrameRateVote(HI_REFRESH_RATE, Surface.FRAME_RATE_COMPATIBILITY_DEFAULT);
 
     // Parcel and Unparcel the LayoutParams in the window state to test the path the object
     // travels from the app's process to system server
@@ -89,6 +108,8 @@
     WindowState createWindow(String name) {
         WindowState window = createWindow(null, TYPE_BASE_APPLICATION, name);
         when(window.getDisplayInfo()).thenReturn(mDisplayInfo);
+        when(window.mWmService.mDisplayManagerInternal.getRefreshRateSwitchingType())
+                .thenReturn(DisplayManager.SWITCHING_TYPE_WITHIN_GROUPS);
         return window;
     }
 
@@ -98,20 +119,23 @@
         cameraUsingWindow.mAttrs.packageName = "com.android.test";
         parcelLayoutParams(cameraUsingWindow);
         assertEquals(0, mPolicy.getPreferredModeId(cameraUsingWindow));
-        assertEquals(0, mPolicy.getPreferredRefreshRate(cameraUsingWindow), FLOAT_TOLERANCE);
+        assertFalse(mPolicy.updateFrameRateVote(cameraUsingWindow));
+        assertEquals(FRAME_RATE_VOTE_NONE, cameraUsingWindow.mFrameRateVote);
         assertEquals(0, mPolicy.getPreferredMinRefreshRate(cameraUsingWindow), FLOAT_TOLERANCE);
         assertEquals(0, mPolicy.getPreferredMaxRefreshRate(cameraUsingWindow), FLOAT_TOLERANCE);
         mPolicy.addRefreshRateRangeForPackage("com.android.test",
                 LOW_REFRESH_RATE, LOW_REFRESH_RATE);
         assertEquals(0, mPolicy.getPreferredModeId(cameraUsingWindow));
-        assertEquals(0, mPolicy.getPreferredRefreshRate(cameraUsingWindow), FLOAT_TOLERANCE);
+        assertFalse(mPolicy.updateFrameRateVote(cameraUsingWindow));
+        assertEquals(FRAME_RATE_VOTE_NONE, cameraUsingWindow.mFrameRateVote);
         assertEquals(LOW_REFRESH_RATE,
                 mPolicy.getPreferredMinRefreshRate(cameraUsingWindow), FLOAT_TOLERANCE);
         assertEquals(LOW_REFRESH_RATE,
                 mPolicy.getPreferredMaxRefreshRate(cameraUsingWindow), FLOAT_TOLERANCE);
         mPolicy.removeRefreshRateRangeForPackage("com.android.test");
         assertEquals(0, mPolicy.getPreferredModeId(cameraUsingWindow));
-        assertEquals(0, mPolicy.getPreferredRefreshRate(cameraUsingWindow), FLOAT_TOLERANCE);
+        assertFalse(mPolicy.updateFrameRateVote(cameraUsingWindow));
+        assertEquals(FRAME_RATE_VOTE_NONE, cameraUsingWindow.mFrameRateVote);
         assertEquals(0, mPolicy.getPreferredMinRefreshRate(cameraUsingWindow), FLOAT_TOLERANCE);
         assertEquals(0, mPolicy.getPreferredMaxRefreshRate(cameraUsingWindow), FLOAT_TOLERANCE);
     }
@@ -122,20 +146,23 @@
         cameraUsingWindow.mAttrs.packageName = "com.android.test";
         parcelLayoutParams(cameraUsingWindow);
         assertEquals(0, mPolicy.getPreferredModeId(cameraUsingWindow));
-        assertEquals(0, mPolicy.getPreferredRefreshRate(cameraUsingWindow), FLOAT_TOLERANCE);
+        assertFalse(mPolicy.updateFrameRateVote(cameraUsingWindow));
+        assertEquals(FRAME_RATE_VOTE_NONE, cameraUsingWindow.mFrameRateVote);
         assertEquals(0, mPolicy.getPreferredMinRefreshRate(cameraUsingWindow), FLOAT_TOLERANCE);
         assertEquals(0, mPolicy.getPreferredMaxRefreshRate(cameraUsingWindow), FLOAT_TOLERANCE);
         mPolicy.addRefreshRateRangeForPackage("com.android.test",
                 LOW_REFRESH_RATE, MID_REFRESH_RATE);
         assertEquals(0, mPolicy.getPreferredModeId(cameraUsingWindow));
-        assertEquals(0, mPolicy.getPreferredRefreshRate(cameraUsingWindow), FLOAT_TOLERANCE);
+        assertFalse(mPolicy.updateFrameRateVote(cameraUsingWindow));
+        assertEquals(FRAME_RATE_VOTE_NONE, cameraUsingWindow.mFrameRateVote);
         assertEquals(LOW_REFRESH_RATE,
                 mPolicy.getPreferredMinRefreshRate(cameraUsingWindow), FLOAT_TOLERANCE);
         assertEquals(MID_REFRESH_RATE,
                 mPolicy.getPreferredMaxRefreshRate(cameraUsingWindow), FLOAT_TOLERANCE);
         mPolicy.removeRefreshRateRangeForPackage("com.android.test");
         assertEquals(0, mPolicy.getPreferredModeId(cameraUsingWindow));
-        assertEquals(0, mPolicy.getPreferredRefreshRate(cameraUsingWindow), FLOAT_TOLERANCE);
+        assertFalse(mPolicy.updateFrameRateVote(cameraUsingWindow));
+        assertEquals(FRAME_RATE_VOTE_NONE, cameraUsingWindow.mFrameRateVote);
         assertEquals(0, mPolicy.getPreferredMinRefreshRate(cameraUsingWindow), FLOAT_TOLERANCE);
         assertEquals(0, mPolicy.getPreferredMaxRefreshRate(cameraUsingWindow), FLOAT_TOLERANCE);
     }
@@ -146,20 +173,23 @@
         cameraUsingWindow.mAttrs.packageName = "com.android.test";
         parcelLayoutParams(cameraUsingWindow);
         assertEquals(0, mPolicy.getPreferredModeId(cameraUsingWindow));
-        assertEquals(0, mPolicy.getPreferredRefreshRate(cameraUsingWindow), FLOAT_TOLERANCE);
+        assertFalse(mPolicy.updateFrameRateVote(cameraUsingWindow));
+        assertEquals(FRAME_RATE_VOTE_NONE, cameraUsingWindow.mFrameRateVote);
         assertEquals(0, mPolicy.getPreferredMinRefreshRate(cameraUsingWindow), FLOAT_TOLERANCE);
         assertEquals(0, mPolicy.getPreferredMaxRefreshRate(cameraUsingWindow), FLOAT_TOLERANCE);
         mPolicy.addRefreshRateRangeForPackage("com.android.test",
                 LOW_REFRESH_RATE - 10, HI_REFRESH_RATE + 10);
         assertEquals(0, mPolicy.getPreferredModeId(cameraUsingWindow));
-        assertEquals(0, mPolicy.getPreferredRefreshRate(cameraUsingWindow), FLOAT_TOLERANCE);
+        assertFalse(mPolicy.updateFrameRateVote(cameraUsingWindow));
+        assertEquals(FRAME_RATE_VOTE_NONE, cameraUsingWindow.mFrameRateVote);
         assertEquals(LOW_REFRESH_RATE,
                 mPolicy.getPreferredMinRefreshRate(cameraUsingWindow), FLOAT_TOLERANCE);
         assertEquals(HI_REFRESH_RATE,
                 mPolicy.getPreferredMaxRefreshRate(cameraUsingWindow), FLOAT_TOLERANCE);
         mPolicy.removeRefreshRateRangeForPackage("com.android.test");
         assertEquals(0, mPolicy.getPreferredModeId(cameraUsingWindow));
-        assertEquals(0, mPolicy.getPreferredRefreshRate(cameraUsingWindow), FLOAT_TOLERANCE);
+        assertFalse(mPolicy.updateFrameRateVote(cameraUsingWindow));
+        assertEquals(FRAME_RATE_VOTE_NONE, cameraUsingWindow.mFrameRateVote);
         assertEquals(0, mPolicy.getPreferredMinRefreshRate(cameraUsingWindow), FLOAT_TOLERANCE);
         assertEquals(0, mPolicy.getPreferredMaxRefreshRate(cameraUsingWindow), FLOAT_TOLERANCE);
     }
@@ -171,8 +201,8 @@
         parcelLayoutParams(denylistedWindow);
         when(mDenylist.isDenylisted("com.android.test")).thenReturn(true);
         assertEquals(0, mPolicy.getPreferredModeId(denylistedWindow));
-        assertEquals(LOW_REFRESH_RATE,
-                mPolicy.getPreferredRefreshRate(denylistedWindow), FLOAT_TOLERANCE);
+        assertTrue(mPolicy.updateFrameRateVote(denylistedWindow));
+        assertEquals(FRAME_RATE_VOTE_DENY_LIST, denylistedWindow.mFrameRateVote);
         assertEquals(0, mPolicy.getPreferredMinRefreshRate(denylistedWindow), FLOAT_TOLERANCE);
         assertEquals(0, mPolicy.getPreferredMaxRefreshRate(denylistedWindow), FLOAT_TOLERANCE);
     }
@@ -185,8 +215,8 @@
         parcelLayoutParams(overrideWindow);
         when(mDenylist.isDenylisted("com.android.test")).thenReturn(true);
         assertEquals(HI_MODE_ID, mPolicy.getPreferredModeId(overrideWindow));
-        assertEquals(HI_REFRESH_RATE,
-                mPolicy.getPreferredRefreshRate(overrideWindow), FLOAT_TOLERANCE);
+        assertTrue(mPolicy.updateFrameRateVote(overrideWindow));
+        assertEquals(FRAME_RATE_VOTE_HI_EXACT, overrideWindow.mFrameRateVote);
         assertEquals(0, mPolicy.getPreferredMinRefreshRate(overrideWindow), FLOAT_TOLERANCE);
         assertEquals(0, mPolicy.getPreferredMaxRefreshRate(overrideWindow), FLOAT_TOLERANCE);
     }
@@ -199,8 +229,8 @@
         parcelLayoutParams(overrideWindow);
         when(mDenylist.isDenylisted("com.android.test")).thenReturn(true);
         assertEquals(0, mPolicy.getPreferredModeId(overrideWindow));
-        assertEquals(HI_REFRESH_RATE,
-                mPolicy.getPreferredRefreshRate(overrideWindow), FLOAT_TOLERANCE);
+        assertTrue(mPolicy.updateFrameRateVote(overrideWindow));
+        assertEquals(FRAME_RATE_VOTE_HI_PREFERRED, overrideWindow.mFrameRateVote);
         assertEquals(0, mPolicy.getPreferredMinRefreshRate(overrideWindow), FLOAT_TOLERANCE);
         assertEquals(0, mPolicy.getPreferredMaxRefreshRate(overrideWindow), FLOAT_TOLERANCE);
     }
@@ -214,8 +244,8 @@
         mPolicy.addRefreshRateRangeForPackage("com.android.test",
                 LOW_REFRESH_RATE, LOW_REFRESH_RATE);
         assertEquals(HI_MODE_ID, mPolicy.getPreferredModeId(overrideWindow));
-        assertEquals(HI_REFRESH_RATE,
-                mPolicy.getPreferredRefreshRate(overrideWindow), FLOAT_TOLERANCE);
+        assertTrue(mPolicy.updateFrameRateVote(overrideWindow));
+        assertEquals(FRAME_RATE_VOTE_HI_EXACT, overrideWindow.mFrameRateVote);
         assertEquals(LOW_REFRESH_RATE,
                 mPolicy.getPreferredMinRefreshRate(overrideWindow), FLOAT_TOLERANCE);
         assertEquals(LOW_REFRESH_RATE,
@@ -231,8 +261,8 @@
         mPolicy.addRefreshRateRangeForPackage("com.android.test",
                 LOW_REFRESH_RATE, LOW_REFRESH_RATE);
         assertEquals(0, mPolicy.getPreferredModeId(overrideWindow));
-        assertEquals(HI_REFRESH_RATE,
-                mPolicy.getPreferredRefreshRate(overrideWindow), FLOAT_TOLERANCE);
+        assertTrue(mPolicy.updateFrameRateVote(overrideWindow));
+        assertEquals(FRAME_RATE_VOTE_HI_PREFERRED, overrideWindow.mFrameRateVote);
         assertEquals(LOW_REFRESH_RATE,
                 mPolicy.getPreferredMinRefreshRate(overrideWindow), FLOAT_TOLERANCE);
         assertEquals(LOW_REFRESH_RATE,
@@ -246,8 +276,8 @@
         overrideWindow.mAttrs.preferredDisplayModeId = LOW_MODE_ID;
         parcelLayoutParams(overrideWindow);
         assertEquals(LOW_MODE_ID, mPolicy.getPreferredModeId(overrideWindow));
-        assertEquals(LOW_REFRESH_RATE,
-                mPolicy.getPreferredRefreshRate(overrideWindow), FLOAT_TOLERANCE);
+        assertTrue(mPolicy.updateFrameRateVote(overrideWindow));
+        assertEquals(FRAME_RATE_VOTE_LOW_EXACT, overrideWindow.mFrameRateVote);
         assertEquals(0, mPolicy.getPreferredMinRefreshRate(overrideWindow), FLOAT_TOLERANCE);
         assertEquals(0, mPolicy.getPreferredMaxRefreshRate(overrideWindow), FLOAT_TOLERANCE);
 
@@ -255,7 +285,8 @@
                 overrideWindow.getPendingTransaction(), mock(AnimationAdapter.class),
                 false /* hidden */, ANIMATION_TYPE_APP_TRANSITION);
         assertEquals(0, mPolicy.getPreferredModeId(overrideWindow));
-        assertEquals(0, mPolicy.getPreferredRefreshRate(overrideWindow), FLOAT_TOLERANCE);
+        assertTrue(mPolicy.updateFrameRateVote(overrideWindow));
+        assertEquals(FRAME_RATE_VOTE_NONE, overrideWindow.mFrameRateVote);
         assertEquals(0, mPolicy.getPreferredMinRefreshRate(overrideWindow), FLOAT_TOLERANCE);
         assertEquals(0, mPolicy.getPreferredMaxRefreshRate(overrideWindow), FLOAT_TOLERANCE);
     }
@@ -267,8 +298,8 @@
         overrideWindow.mAttrs.preferredRefreshRate = LOW_REFRESH_RATE;
         parcelLayoutParams(overrideWindow);
         assertEquals(0, mPolicy.getPreferredModeId(overrideWindow));
-        assertEquals(LOW_REFRESH_RATE,
-                mPolicy.getPreferredRefreshRate(overrideWindow), FLOAT_TOLERANCE);
+        assertTrue(mPolicy.updateFrameRateVote(overrideWindow));
+        assertEquals(FRAME_RATE_VOTE_LOW_PREFERRED, overrideWindow.mFrameRateVote);
         assertEquals(0, mPolicy.getPreferredMinRefreshRate(overrideWindow), FLOAT_TOLERANCE);
         assertEquals(0, mPolicy.getPreferredMaxRefreshRate(overrideWindow), FLOAT_TOLERANCE);
 
@@ -276,7 +307,8 @@
                 overrideWindow.getPendingTransaction(), mock(AnimationAdapter.class),
                 false /* hidden */, ANIMATION_TYPE_APP_TRANSITION);
         assertEquals(0, mPolicy.getPreferredModeId(overrideWindow));
-        assertEquals(0, mPolicy.getPreferredRefreshRate(overrideWindow), FLOAT_TOLERANCE);
+        assertTrue(mPolicy.updateFrameRateVote(overrideWindow));
+        assertEquals(FRAME_RATE_VOTE_NONE, overrideWindow.mFrameRateVote);
         assertEquals(0, mPolicy.getPreferredMinRefreshRate(overrideWindow), FLOAT_TOLERANCE);
         assertEquals(0, mPolicy.getPreferredMaxRefreshRate(overrideWindow), FLOAT_TOLERANCE);
     }
@@ -288,8 +320,8 @@
         parcelLayoutParams(window);
         when(mDenylist.isDenylisted("com.android.test")).thenReturn(true);
         assertEquals(0, mPolicy.getPreferredModeId(window));
-        assertEquals(LOW_REFRESH_RATE,
-                mPolicy.getPreferredRefreshRate(window), FLOAT_TOLERANCE);
+        assertTrue(mPolicy.updateFrameRateVote(window));
+        assertEquals(FRAME_RATE_VOTE_DENY_LIST, window.mFrameRateVote);
         assertEquals(0, mPolicy.getPreferredMinRefreshRate(window), FLOAT_TOLERANCE);
         assertEquals(0, mPolicy.getPreferredMaxRefreshRate(window), FLOAT_TOLERANCE);
 
@@ -297,7 +329,8 @@
                 window.getPendingTransaction(), mock(AnimationAdapter.class),
                 false /* hidden */, ANIMATION_TYPE_APP_TRANSITION);
         assertEquals(0, mPolicy.getPreferredModeId(window));
-        assertEquals(0, mPolicy.getPreferredRefreshRate(window), FLOAT_TOLERANCE);
+        assertTrue(mPolicy.updateFrameRateVote(window));
+        assertEquals(FRAME_RATE_VOTE_NONE, window.mFrameRateVote);
         assertEquals(0, mPolicy.getPreferredMinRefreshRate(window), FLOAT_TOLERANCE);
         assertEquals(0, mPolicy.getPreferredMaxRefreshRate(window), FLOAT_TOLERANCE);
     }
@@ -311,7 +344,8 @@
         mPolicy.addRefreshRateRangeForPackage("com.android.test",
                 LOW_REFRESH_RATE, LOW_REFRESH_RATE);
         assertEquals(0, mPolicy.getPreferredModeId(cameraUsingWindow));
-        assertEquals(0, mPolicy.getPreferredRefreshRate(cameraUsingWindow), FLOAT_TOLERANCE);
+        assertFalse(mPolicy.updateFrameRateVote(cameraUsingWindow));
+        assertEquals(FRAME_RATE_VOTE_NONE, cameraUsingWindow.mFrameRateVote);
         assertEquals(LOW_REFRESH_RATE,
                 mPolicy.getPreferredMinRefreshRate(cameraUsingWindow), FLOAT_TOLERANCE);
         assertEquals(LOW_REFRESH_RATE,
@@ -321,7 +355,8 @@
                 cameraUsingWindow.getPendingTransaction(), mock(AnimationAdapter.class),
                 false /* hidden */, ANIMATION_TYPE_APP_TRANSITION);
         assertEquals(0, mPolicy.getPreferredModeId(cameraUsingWindow));
-        assertEquals(0, mPolicy.getPreferredRefreshRate(cameraUsingWindow), FLOAT_TOLERANCE);
+        assertFalse(mPolicy.updateFrameRateVote(cameraUsingWindow));
+        assertEquals(FRAME_RATE_VOTE_NONE, cameraUsingWindow.mFrameRateVote);
         assertEquals(0, mPolicy.getPreferredMinRefreshRate(cameraUsingWindow), FLOAT_TOLERANCE);
         assertEquals(0, mPolicy.getPreferredMaxRefreshRate(cameraUsingWindow), FLOAT_TOLERANCE);
     }
@@ -332,7 +367,8 @@
         window.mAttrs.preferredMaxDisplayRefreshRate = LOW_REFRESH_RATE;
         parcelLayoutParams(window);
         assertEquals(0, mPolicy.getPreferredModeId(window));
-        assertEquals(0, mPolicy.getPreferredRefreshRate(window), FLOAT_TOLERANCE);
+        assertFalse(mPolicy.updateFrameRateVote(window));
+        assertEquals(FRAME_RATE_VOTE_NONE, window.mFrameRateVote);
         assertEquals(0, mPolicy.getPreferredMinRefreshRate(window), FLOAT_TOLERANCE);
         assertEquals(LOW_REFRESH_RATE, mPolicy.getPreferredMaxRefreshRate(window), FLOAT_TOLERANCE);
 
@@ -340,7 +376,8 @@
                 window.getPendingTransaction(), mock(AnimationAdapter.class),
                 false /* hidden */, ANIMATION_TYPE_APP_TRANSITION);
         assertEquals(0, mPolicy.getPreferredModeId(window));
-        assertEquals(0, mPolicy.getPreferredRefreshRate(window), FLOAT_TOLERANCE);
+        assertFalse(mPolicy.updateFrameRateVote(window));
+        assertEquals(FRAME_RATE_VOTE_NONE, window.mFrameRateVote);
         assertEquals(0, mPolicy.getPreferredMinRefreshRate(window), FLOAT_TOLERANCE);
         assertEquals(0, mPolicy.getPreferredMaxRefreshRate(window), FLOAT_TOLERANCE);
     }
@@ -351,7 +388,8 @@
         window.mAttrs.preferredMinDisplayRefreshRate = LOW_REFRESH_RATE;
         parcelLayoutParams(window);
         assertEquals(0, mPolicy.getPreferredModeId(window));
-        assertEquals(0, mPolicy.getPreferredRefreshRate(window), FLOAT_TOLERANCE);
+        assertFalse(mPolicy.updateFrameRateVote(window));
+        assertEquals(FRAME_RATE_VOTE_NONE, window.mFrameRateVote);
         assertEquals(LOW_REFRESH_RATE, mPolicy.getPreferredMinRefreshRate(window), FLOAT_TOLERANCE);
         assertEquals(0, mPolicy.getPreferredMaxRefreshRate(window), FLOAT_TOLERANCE);
 
@@ -359,7 +397,8 @@
                 window.getPendingTransaction(), mock(AnimationAdapter.class),
                 false /* hidden */, ANIMATION_TYPE_APP_TRANSITION);
         assertEquals(0, mPolicy.getPreferredModeId(window));
-        assertEquals(0, mPolicy.getPreferredRefreshRate(window), FLOAT_TOLERANCE);
+        assertFalse(mPolicy.updateFrameRateVote(window));
+        assertEquals(FRAME_RATE_VOTE_NONE, window.mFrameRateVote);
         assertEquals(0, mPolicy.getPreferredMinRefreshRate(window), FLOAT_TOLERANCE);
         assertEquals(0, mPolicy.getPreferredMaxRefreshRate(window), FLOAT_TOLERANCE);
     }
@@ -370,8 +409,92 @@
         window.mAttrs.preferredRefreshRate = LOW_REFRESH_RATE;
         parcelLayoutParams(window);
         assertEquals(0, mPolicy.getPreferredModeId(window));
-        assertEquals(LOW_REFRESH_RATE, mPolicy.getPreferredRefreshRate(window), FLOAT_TOLERANCE);
+        assertTrue(mPolicy.updateFrameRateVote(window));
+        assertEquals(FRAME_RATE_VOTE_LOW_PREFERRED, window.mFrameRateVote);
         assertEquals(0, mPolicy.getPreferredMinRefreshRate(window), FLOAT_TOLERANCE);
         assertEquals(0, mPolicy.getPreferredMaxRefreshRate(window), FLOAT_TOLERANCE);
     }
+
+    @Test
+    public void testSwitchingTypeForExactVote() {
+        final WindowState window = createWindow("window");
+        window.mAttrs.preferredDisplayModeId = HI_MODE_ID;
+        parcelLayoutParams(window);
+
+        when(window.mWmService.mDisplayManagerInternal.getRefreshRateSwitchingType())
+                .thenReturn(DisplayManager.SWITCHING_TYPE_NONE);
+        assertFalse(mPolicy.updateFrameRateVote(window));
+        assertEquals(FRAME_RATE_VOTE_NONE, window.mFrameRateVote);
+
+        when(window.mWmService.mDisplayManagerInternal.getRefreshRateSwitchingType())
+                .thenReturn(DisplayManager.SWITCHING_TYPE_WITHIN_GROUPS);
+        assertTrue(mPolicy.updateFrameRateVote(window));
+        assertEquals(FRAME_RATE_VOTE_HI_EXACT, window.mFrameRateVote);
+
+        when(window.mWmService.mDisplayManagerInternal.getRefreshRateSwitchingType())
+                .thenReturn(DisplayManager.SWITCHING_TYPE_ACROSS_AND_WITHIN_GROUPS);
+        assertFalse(mPolicy.updateFrameRateVote(window));
+        assertEquals(FRAME_RATE_VOTE_HI_EXACT, window.mFrameRateVote);
+
+        when(window.mWmService.mDisplayManagerInternal.getRefreshRateSwitchingType())
+                .thenReturn(DisplayManager.SWITCHING_TYPE_RENDER_FRAME_RATE_ONLY);
+        assertTrue(mPolicy.updateFrameRateVote(window));
+        assertEquals(FRAME_RATE_VOTE_NONE, window.mFrameRateVote);
+    }
+
+    @Test
+    public void testSwitchingTypeForPreferredVote() {
+        final WindowState window = createWindow("window");
+        window.mAttrs.preferredRefreshRate = HI_REFRESH_RATE;
+        parcelLayoutParams(window);
+
+        when(window.mWmService.mDisplayManagerInternal.getRefreshRateSwitchingType())
+                .thenReturn(DisplayManager.SWITCHING_TYPE_NONE);
+        assertFalse(mPolicy.updateFrameRateVote(window));
+        assertEquals(FRAME_RATE_VOTE_NONE, window.mFrameRateVote);
+
+        when(window.mWmService.mDisplayManagerInternal.getRefreshRateSwitchingType())
+                .thenReturn(DisplayManager.SWITCHING_TYPE_WITHIN_GROUPS);
+        assertTrue(mPolicy.updateFrameRateVote(window));
+        assertEquals(FRAME_RATE_VOTE_HI_PREFERRED, window.mFrameRateVote);
+
+        when(window.mWmService.mDisplayManagerInternal.getRefreshRateSwitchingType())
+                .thenReturn(DisplayManager.SWITCHING_TYPE_ACROSS_AND_WITHIN_GROUPS);
+        assertFalse(mPolicy.updateFrameRateVote(window));
+        assertEquals(FRAME_RATE_VOTE_HI_PREFERRED, window.mFrameRateVote);
+
+        when(window.mWmService.mDisplayManagerInternal.getRefreshRateSwitchingType())
+                .thenReturn(DisplayManager.SWITCHING_TYPE_RENDER_FRAME_RATE_ONLY);
+        assertFalse(mPolicy.updateFrameRateVote(window));
+        assertEquals(FRAME_RATE_VOTE_HI_PREFERRED, window.mFrameRateVote);
+    }
+
+    @Test
+    public void testSwitchingTypeForDenylist() {
+        when(mDenylist.isDenylisted("com.android.test")).thenReturn(true);
+
+        final WindowState window = createWindow("window");
+        window.mAttrs.packageName = "com.android.test";
+        parcelLayoutParams(window);
+
+        when(window.mWmService.mDisplayManagerInternal.getRefreshRateSwitchingType())
+                .thenReturn(DisplayManager.SWITCHING_TYPE_NONE);
+        assertFalse(mPolicy.updateFrameRateVote(window));
+        assertEquals(FRAME_RATE_VOTE_NONE, window.mFrameRateVote);
+
+        when(window.mWmService.mDisplayManagerInternal.getRefreshRateSwitchingType())
+                .thenReturn(DisplayManager.SWITCHING_TYPE_WITHIN_GROUPS);
+        assertTrue(mPolicy.updateFrameRateVote(window));
+        assertEquals(FRAME_RATE_VOTE_LOW_EXACT, window.mFrameRateVote);
+
+        when(window.mWmService.mDisplayManagerInternal.getRefreshRateSwitchingType())
+                .thenReturn(DisplayManager.SWITCHING_TYPE_ACROSS_AND_WITHIN_GROUPS);
+        assertFalse(mPolicy.updateFrameRateVote(window));
+        assertEquals(FRAME_RATE_VOTE_LOW_EXACT, window.mFrameRateVote);
+
+        when(window.mWmService.mDisplayManagerInternal.getRefreshRateSwitchingType())
+                .thenReturn(DisplayManager.SWITCHING_TYPE_RENDER_FRAME_RATE_ONLY);
+        assertTrue(mPolicy.updateFrameRateVote(window));
+        assertEquals(FRAME_RATE_VOTE_NONE, window.mFrameRateVote);
+    }
 }
diff --git a/services/tests/wmtests/src/com/android/server/wm/SurfaceSyncGroupTest.java b/services/tests/wmtests/src/com/android/server/wm/SurfaceSyncGroupTest.java
index 846a506..5e1fae0 100644
--- a/services/tests/wmtests/src/com/android/server/wm/SurfaceSyncGroupTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/SurfaceSyncGroupTest.java
@@ -192,16 +192,17 @@
     }
 
     private static class SyncTarget implements SurfaceSyncGroup.SyncTarget {
-        private SurfaceSyncGroup.SyncBufferCallback mSyncBufferCallback;
+        private SurfaceSyncGroup.TransactionReadyCallback mTransactionReadyCallback;
 
         @Override
-        public void onReadyToSync(SurfaceSyncGroup.SyncBufferCallback syncBufferCallback) {
-            mSyncBufferCallback = syncBufferCallback;
+        public void onAddedToSyncGroup(SurfaceSyncGroup parentSyncGroup,
+                SurfaceSyncGroup.TransactionReadyCallback transactionReadyCallback) {
+            mTransactionReadyCallback = transactionReadyCallback;
         }
 
         void onBufferReady() {
             SurfaceControl.Transaction t = new StubTransaction();
-            mSyncBufferCallback.onBufferReady(t);
+            mTransactionReadyCallback.onTransactionReady(t);
         }
     }
 }
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskTests.java b/services/tests/wmtests/src/com/android/server/wm/TaskTests.java
index 66bf78b..0b6cea2 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskTests.java
@@ -433,6 +433,24 @@
     }
 
     @Test
+    public void testPropagateFocusedStateToRootTask() {
+        final Task rootTask = createTask(mDefaultDisplay);
+        final Task leafTask = createTaskInRootTask(rootTask, 0 /* userId */);
+
+        final ActivityRecord activity = createActivityRecord(leafTask);
+
+        leafTask.getDisplayContent().setFocusedApp(activity);
+
+        assertTrue(leafTask.getTaskInfo().isFocused);
+        assertTrue(rootTask.getTaskInfo().isFocused);
+
+        leafTask.getDisplayContent().setFocusedApp(null);
+
+        assertFalse(leafTask.getTaskInfo().isFocused);
+        assertFalse(rootTask.getTaskInfo().isFocused);
+    }
+
+    @Test
     public void testReturnsToHomeRootTask() throws Exception {
         final Task task = createTask(1);
         spyOn(task);
diff --git a/services/usb/java/com/android/server/usb/UsbService.java b/services/usb/java/com/android/server/usb/UsbService.java
index 86f877f..72f6cc3 100644
--- a/services/usb/java/com/android/server/usb/UsbService.java
+++ b/services/usb/java/com/android/server/usb/UsbService.java
@@ -504,6 +504,15 @@
     }
 
     @Override
+    public boolean hasDevicePermissionWithIdentity(UsbDevice device, String packageName,
+            int pid, int uid) {
+        mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_USB, null);
+
+        final int userId = UserHandle.getUserId(uid);
+        return getPermissionsForUser(userId).hasPermission(device, packageName, pid, uid);
+    }
+
+    @Override
     public boolean hasAccessoryPermission(UsbAccessory accessory) {
         final int uid = Binder.getCallingUid();
         final int pid = Binder.getCallingPid();
@@ -518,6 +527,14 @@
     }
 
     @Override
+    public boolean hasAccessoryPermissionWithIdentity(UsbAccessory accessory, int pid, int uid) {
+        mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_USB, null);
+
+        final int userId = UserHandle.getUserId(uid);
+        return getPermissionsForUser(userId).hasPermission(accessory, pid, uid);
+    }
+
+    @Override
     public void requestDevicePermission(UsbDevice device, String packageName, PendingIntent pi) {
         final int uid = Binder.getCallingUid();
         final int pid = Binder.getCallingPid();
diff --git a/services/usb/java/com/android/server/usb/descriptors/UsbVCInputTerminal.java b/services/usb/java/com/android/server/usb/descriptors/UsbVCInputTerminal.java
index df63795..7a41b50 100644
--- a/services/usb/java/com/android/server/usb/descriptors/UsbVCInputTerminal.java
+++ b/services/usb/java/com/android/server/usb/descriptors/UsbVCInputTerminal.java
@@ -46,4 +46,4 @@
         // TODO Add reporting specific to this descriptor
         super.report(canvas);
     }
-};
+}
diff --git a/services/usb/java/com/android/server/usb/descriptors/UsbVCOutputTerminal.java b/services/usb/java/com/android/server/usb/descriptors/UsbVCOutputTerminal.java
index 4aa8ca2..32275a6 100644
--- a/services/usb/java/com/android/server/usb/descriptors/UsbVCOutputTerminal.java
+++ b/services/usb/java/com/android/server/usb/descriptors/UsbVCOutputTerminal.java
@@ -46,4 +46,4 @@
         super.report(canvas);
         // TODO Add reporting specific to this descriptor
     }
-};
+}
diff --git a/services/usb/java/com/android/server/usb/descriptors/UsbVCProcessingUnit.java b/services/usb/java/com/android/server/usb/descriptors/UsbVCProcessingUnit.java
index 5ce842e..0692066 100644
--- a/services/usb/java/com/android/server/usb/descriptors/UsbVCProcessingUnit.java
+++ b/services/usb/java/com/android/server/usb/descriptors/UsbVCProcessingUnit.java
@@ -47,4 +47,4 @@
         super.report(canvas);
         // TODO Add reporting specific to this descriptor
     }
-};
+}
diff --git a/services/usb/java/com/android/server/usb/descriptors/UsbVCSelectorUnit.java b/services/usb/java/com/android/server/usb/descriptors/UsbVCSelectorUnit.java
index 8e9b0d8..604dd66 100644
--- a/services/usb/java/com/android/server/usb/descriptors/UsbVCSelectorUnit.java
+++ b/services/usb/java/com/android/server/usb/descriptors/UsbVCSelectorUnit.java
@@ -47,4 +47,4 @@
         super.report(canvas);
         // TODO Add reporting specific to this descriptor
     }
-};
+}
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordAudioStreamManager.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordAudioStreamManager.java
index f921118..d5eea1f 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordAudioStreamManager.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordAudioStreamManager.java
@@ -163,7 +163,7 @@
 
     private static class SingleAudioStreamCopyTask implements Callable<Void> {
         // TODO: Make this buffer size customizable from updateState()
-        private static final int COPY_BUFFER_LENGTH = 1_024;
+        private static final int COPY_BUFFER_LENGTH = 2_560;
 
         private final String mStreamTaskId;
         private final ParcelFileDescriptor mAudioSource;
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java
index 0721c28..3e49aed 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java
@@ -1321,4 +1321,4 @@
 
     private static final String OP_MESSAGE =
             "Providing hotword detection result to VoiceInteractionService";
-};
+}
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java
index 5d1901d..0a660b0 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java
@@ -639,7 +639,7 @@
     private void logDetectorCreateEventIfNeeded(IHotwordRecognitionStatusCallback callback,
             int detectorType, boolean isCreated, int voiceInteractionServiceUid) {
         if (callback != null) {
-            HotwordMetricsLogger.writeDetectorCreateEvent(detectorType, true,
+            HotwordMetricsLogger.writeDetectorCreateEvent(detectorType, isCreated,
                     voiceInteractionServiceUid);
         }
 
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionSessionConnection.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionSessionConnection.java
index f8bc499..763024f 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionSessionConnection.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionSessionConnection.java
@@ -913,4 +913,4 @@
             }
         }
     };
-};
+}
diff --git a/telephony/common/com/google/android/mms/pdu/EncodedStringValue.java b/telephony/common/com/google/android/mms/pdu/EncodedStringValue.java
index 8b01cb3..2787d83 100644
--- a/telephony/common/com/google/android/mms/pdu/EncodedStringValue.java
+++ b/telephony/common/com/google/android/mms/pdu/EncodedStringValue.java
@@ -199,7 +199,6 @@
      */
     @Override
     public Object clone() throws CloneNotSupportedException {
-        super.clone();
         int len = mData.length;
         byte[] dstBytes = new byte[len];
         System.arraycopy(mData, 0, dstBytes, 0, len);
diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java
index 936fad5..120d76d 100644
--- a/telephony/java/android/telephony/CarrierConfigManager.java
+++ b/telephony/java/android/telephony/CarrierConfigManager.java
@@ -7920,7 +7920,8 @@
                     KEY_XCAP_OVER_UT_SUPPORTED_RATS_INT_ARRAY,
                     new int[] {
                         AccessNetworkType.EUTRAN,
-                        AccessNetworkType.IWLAN
+                        AccessNetworkType.IWLAN,
+                        AccessNetworkType.NGRAN
                     });
             defaults.putString(KEY_UT_AS_SERVER_FQDN_STRING, "");
             defaults.putBoolean(KEY_TERMINAL_BASED_CALL_WAITING_DEFAULT_ENABLED_BOOL, true);
@@ -8698,6 +8699,15 @@
     public static final String KEY_VONR_ENABLED_BOOL = "vonr_enabled_bool";
 
     /**
+     * Boolean indicating the default VoNR user preference setting.
+     * If true, the VoNR setting will be enabled. If false, it will be disabled initially.
+     *
+     * Enabled by default.
+     *
+     */
+    public static final String KEY_VONR_ON_BY_DEFAULT_BOOL = "vonr_on_by_default_bool";
+
+    /**
      * Determine whether unthrottle data retry when tracking area code (TAC/LAC) from cell changes
      *
      * @hide
@@ -9520,6 +9530,7 @@
         sDefaults.putBoolean(KEY_UNTHROTTLE_DATA_RETRY_WHEN_TAC_CHANGES_BOOL, false);
         sDefaults.putBoolean(KEY_VONR_SETTING_VISIBILITY_BOOL, true);
         sDefaults.putBoolean(KEY_VONR_ENABLED_BOOL, false);
+        sDefaults.putBoolean(KEY_VONR_ON_BY_DEFAULT_BOOL, true);
         sDefaults.putIntArray(KEY_SUPPORTED_PREMIUM_CAPABILITIES_INT_ARRAY, new int[] {});
         sDefaults.putLong(KEY_PREMIUM_CAPABILITY_NOTIFICATION_DISPLAY_TIMEOUT_MILLIS_LONG,
                 TimeUnit.MINUTES.toMillis(30));
diff --git a/telephony/java/android/telephony/CellIdentity.java b/telephony/java/android/telephony/CellIdentity.java
index 06cfd67..6e3cfac 100644
--- a/telephony/java/android/telephony/CellIdentity.java
+++ b/telephony/java/android/telephony/CellIdentity.java
@@ -107,7 +107,7 @@
 
         if ((mMccStr != null && mMncStr == null) || (mMccStr == null && mMncStr != null)) {
             AnomalyReporter.reportAnomaly(
-                    UUID.fromString("a3ab0b9d-f2aa-4baf-911d-7096c0d4645a"),
+                    UUID.fromString("e257ae06-ac0a-44c0-ba63-823b9f07b3e4"),
                     "CellIdentity Missing Half of PLMN ID");
         }
 
diff --git a/telephony/java/android/telephony/DataFailCause.java b/telephony/java/android/telephony/DataFailCause.java
index 23835a7..b83b400 100644
--- a/telephony/java/android/telephony/DataFailCause.java
+++ b/telephony/java/android/telephony/DataFailCause.java
@@ -1620,29 +1620,26 @@
                 // If we are not able to find the configuration from carrier config, use the default
                 // ones.
                 if (permanentFailureSet == null) {
-                    permanentFailureSet = new HashSet<Integer>() {
-                        {
-                            add(OPERATOR_BARRED);
-                            add(MISSING_UNKNOWN_APN);
-                            add(UNKNOWN_PDP_ADDRESS_TYPE);
-                            add(USER_AUTHENTICATION);
-                            add(ACTIVATION_REJECT_GGSN);
-                            add(SERVICE_OPTION_NOT_SUPPORTED);
-                            add(SERVICE_OPTION_NOT_SUBSCRIBED);
-                            add(NSAPI_IN_USE);
-                            add(ONLY_IPV4_ALLOWED);
-                            add(ONLY_IPV6_ALLOWED);
-                            add(PROTOCOL_ERRORS);
-                            add(RADIO_POWER_OFF);
-                            add(TETHERED_CALL_ACTIVE);
-                            add(RADIO_NOT_AVAILABLE);
-                            add(UNACCEPTABLE_NETWORK_PARAMETER);
-                            add(SIGNAL_LOST);
-                            add(DUPLICATE_CID);
-                            add(MATCH_ALL_RULE_NOT_ALLOWED);
-                            add(ALL_MATCHING_RULES_FAILED);
-                        }
-                    };
+                    permanentFailureSet = new HashSet<Integer>();
+                    permanentFailureSet.add(OPERATOR_BARRED);
+                    permanentFailureSet.add(MISSING_UNKNOWN_APN);
+                    permanentFailureSet.add(UNKNOWN_PDP_ADDRESS_TYPE);
+                    permanentFailureSet.add(USER_AUTHENTICATION);
+                    permanentFailureSet.add(ACTIVATION_REJECT_GGSN);
+                    permanentFailureSet.add(SERVICE_OPTION_NOT_SUPPORTED);
+                    permanentFailureSet.add(SERVICE_OPTION_NOT_SUBSCRIBED);
+                    permanentFailureSet.add(NSAPI_IN_USE);
+                    permanentFailureSet.add(ONLY_IPV4_ALLOWED);
+                    permanentFailureSet.add(ONLY_IPV6_ALLOWED);
+                    permanentFailureSet.add(PROTOCOL_ERRORS);
+                    permanentFailureSet.add(RADIO_POWER_OFF);
+                    permanentFailureSet.add(TETHERED_CALL_ACTIVE);
+                    permanentFailureSet.add(RADIO_NOT_AVAILABLE);
+                    permanentFailureSet.add(UNACCEPTABLE_NETWORK_PARAMETER);
+                    permanentFailureSet.add(SIGNAL_LOST);
+                    permanentFailureSet.add(DUPLICATE_CID);
+                    permanentFailureSet.add(MATCH_ALL_RULE_NOT_ALLOWED);
+                    permanentFailureSet.add(ALL_MATCHING_RULES_FAILED);
                 }
 
                 permanentFailureSet.add(NO_RETRY_FAILURE);
diff --git a/telephony/java/android/telephony/DomainSelectionService.java b/telephony/java/android/telephony/DomainSelectionService.java
index 383561a..c352f2b 100644
--- a/telephony/java/android/telephony/DomainSelectionService.java
+++ b/telephony/java/android/telephony/DomainSelectionService.java
@@ -28,7 +28,6 @@
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.os.RemoteException;
-import android.telephony.AccessNetworkConstants.RadioAccessNetworkType;
 import android.telephony.Annotation.DisconnectCauses;
 import android.telephony.Annotation.PreciseDisconnectCauses;
 import android.telephony.ims.ImsReasonInfo;
@@ -703,9 +702,9 @@
         }
 
         @Override
-        public void onDomainSelected(@RadioAccessNetworkType int accessNetworkType) {
+        public void onDomainSelected(@NetworkRegistrationInfo.Domain int domain) {
             try {
-                mCallback.onDomainSelected(accessNetworkType);
+                mCallback.onDomainSelected(domain);
             } catch (Exception e) {
                 Rlog.e(TAG, "onDomainSelected e=" + e);
             }
@@ -835,7 +834,14 @@
         return Runnable::run;
     }
 
-    private @NonNull Executor getCachedExecutor() {
+    /**
+     * Gets the {@link Executor} which executes methods of this service.
+     * This method should be private when this service is implemented in a separated process
+     * other than telephony framework.
+     * @return {@link Executor} instance.
+     * @hide
+     */
+    public @NonNull Executor getCachedExecutor() {
         synchronized (mExecutorLock) {
             if (mExecutor == null) {
                 Executor e = getExecutor();
diff --git a/telephony/java/android/telephony/RadioAccessSpecifier.java b/telephony/java/android/telephony/RadioAccessSpecifier.java
index a403095..9511db6 100644
--- a/telephony/java/android/telephony/RadioAccessSpecifier.java
+++ b/telephony/java/android/telephony/RadioAccessSpecifier.java
@@ -161,9 +161,17 @@
     }
 
     @Override
-    public int hashCode () {
+    public int hashCode() {
         return ((mRadioAccessNetwork * 31)
                 + (Arrays.hashCode(mBands) * 37)
                 + (Arrays.hashCode(mChannels)) * 39);
     }
+
+    @Override
+    public String toString() {
+        return "RadioAccessSpecifier[mRadioAccessNetwork="
+                + AccessNetworkConstants.AccessNetworkType.toString(mRadioAccessNetwork)
+                + ", mBands=" + Arrays.toString(mBands)
+                + ", mChannels=" + Arrays.toString(mChannels) + "]";
+    }
 }
diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java
index 541573c..73551b9 100644
--- a/telephony/java/android/telephony/TelephonyManager.java
+++ b/telephony/java/android/telephony/TelephonyManager.java
@@ -12530,7 +12530,7 @@
             Log.e(TAG, "Error calling ITelephony#getServiceStateForSubscriber", e);
         } catch (NullPointerException e) {
             AnomalyReporter.reportAnomaly(
-                    UUID.fromString("a3ab0b9d-f2aa-4baf-911d-7096c0d4645a"),
+                    UUID.fromString("e2bed88e-def9-476e-bd71-3e572a8de6d1"),
                     "getServiceStateForSubscriber " + subId + " NPE");
         }
         return null;
diff --git a/telephony/java/android/telephony/WwanSelectorCallback.java b/telephony/java/android/telephony/WwanSelectorCallback.java
index 489a589..b3682ca 100644
--- a/telephony/java/android/telephony/WwanSelectorCallback.java
+++ b/telephony/java/android/telephony/WwanSelectorCallback.java
@@ -18,7 +18,6 @@
 
 import android.annotation.NonNull;
 import android.os.CancellationSignal;
-import android.telephony.AccessNetworkConstants.RadioAccessNetworkType;
 import android.telephony.DomainSelectionService.EmergencyScanType;
 
 import java.util.List;
@@ -46,7 +45,7 @@
      * Notifies the FW that the domain has been selected. After this method is called,
      * this interface can be discarded.
      *
-     * @param accessNetworkType the selected network type.
+     * @param domain The selected domain.
      */
-    void onDomainSelected(@RadioAccessNetworkType int accessNetworkType);
+    void onDomainSelected(@NetworkRegistrationInfo.Domain int domain);
 }
diff --git a/telephony/java/android/telephony/data/QosBearerFilter.java b/telephony/java/android/telephony/data/QosBearerFilter.java
index 0ab7b61..a0d9c1bd 100644
--- a/telephony/java/android/telephony/data/QosBearerFilter.java
+++ b/telephony/java/android/telephony/data/QosBearerFilter.java
@@ -130,6 +130,10 @@
         return precedence;
     }
 
+    public int getProtocol() {
+        return protocol;
+    }
+
     public static class PortRange implements Parcelable {
         int start;
         int end;
diff --git a/telephony/java/android/telephony/ims/ImsService.java b/telephony/java/android/telephony/ims/ImsService.java
index a3cbb4a..33c86d8 100644
--- a/telephony/java/android/telephony/ims/ImsService.java
+++ b/telephony/java/android/telephony/ims/ImsService.java
@@ -50,7 +50,6 @@
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
-import java.util.HashMap;
 import java.util.Map;
 import java.util.NoSuchElementException;
 import java.util.concurrent.CancellationException;
@@ -179,10 +178,9 @@
      * Used for logging purposes, see {@link #getCapabilitiesString(long)}
      * @hide
      */
-    private static final Map<Long, String> CAPABILITIES_LOG_MAP = new HashMap<Long, String>() {{
-            put(CAPABILITY_EMERGENCY_OVER_MMTEL, "EMERGENCY_OVER_MMTEL");
-            put(CAPABILITY_SIP_DELEGATE_CREATION, "SIP_DELEGATE_CREATION");
-        }};
+    private static final Map<Long, String> CAPABILITIES_LOG_MAP = Map.of(
+            CAPABILITY_EMERGENCY_OVER_MMTEL, "EMERGENCY_OVER_MMTEL",
+            CAPABILITY_SIP_DELEGATE_CREATION, "SIP_DELEGATE_CREATION");
 
     /**
      * The intent that must be defined as an intent-filter in the AndroidManifest of the ImsService.
diff --git a/telephony/java/android/telephony/ims/RegistrationManager.java b/telephony/java/android/telephony/ims/RegistrationManager.java
index 090d413..9996b86 100644
--- a/telephony/java/android/telephony/ims/RegistrationManager.java
+++ b/telephony/java/android/telephony/ims/RegistrationManager.java
@@ -78,24 +78,22 @@
     /**@hide*/
     // Translate ImsRegistrationImplBase API to new AccessNetworkConstant because WLAN
     // and WWAN are more accurate constants.
-    Map<Integer, Integer> IMS_REG_TO_ACCESS_TYPE_MAP =
-            new HashMap<Integer, Integer>() {{
-                // Map NONE to -1 to make sure that we handle the REGISTRATION_TECH_NONE
-                // case, since it is defined.
-                put(ImsRegistrationImplBase.REGISTRATION_TECH_NONE,
-                        AccessNetworkConstants.TRANSPORT_TYPE_INVALID);
-                put(ImsRegistrationImplBase.REGISTRATION_TECH_LTE,
-                        AccessNetworkConstants.TRANSPORT_TYPE_WWAN);
-                put(ImsRegistrationImplBase.REGISTRATION_TECH_NR,
-                        AccessNetworkConstants.TRANSPORT_TYPE_WWAN);
-                put(ImsRegistrationImplBase.REGISTRATION_TECH_IWLAN,
-                        AccessNetworkConstants.TRANSPORT_TYPE_WLAN);
-                /* As the cross sim will be using ePDG tunnel over internet, it behaves
-                   like IWLAN in most cases. Hence setting the access type as IWLAN
-                 */
-                put(ImsRegistrationImplBase.REGISTRATION_TECH_CROSS_SIM,
-                        AccessNetworkConstants.TRANSPORT_TYPE_WLAN);
-            }};
+    Map<Integer, Integer> IMS_REG_TO_ACCESS_TYPE_MAP = Map.of(
+            // Map NONE to -1 to make sure that we handle the REGISTRATION_TECH_NONE
+            // case, since it is defined.
+            ImsRegistrationImplBase.REGISTRATION_TECH_NONE,
+                    AccessNetworkConstants.TRANSPORT_TYPE_INVALID,
+            ImsRegistrationImplBase.REGISTRATION_TECH_LTE,
+                    AccessNetworkConstants.TRANSPORT_TYPE_WWAN,
+            ImsRegistrationImplBase.REGISTRATION_TECH_NR,
+                    AccessNetworkConstants.TRANSPORT_TYPE_WWAN,
+            ImsRegistrationImplBase.REGISTRATION_TECH_IWLAN,
+                    AccessNetworkConstants.TRANSPORT_TYPE_WLAN,
+            /* As the cross sim will be using ePDG tunnel over internet, it behaves
+               like IWLAN in most cases. Hence setting the access type as IWLAN
+             */
+            ImsRegistrationImplBase.REGISTRATION_TECH_CROSS_SIM,
+                    AccessNetworkConstants.TRANSPORT_TYPE_WLAN);
 
     /** @hide */
     @NonNull
diff --git a/telephony/java/android/telephony/ims/feature/ImsFeature.java b/telephony/java/android/telephony/ims/feature/ImsFeature.java
index a42327b..174675f 100644
--- a/telephony/java/android/telephony/ims/feature/ImsFeature.java
+++ b/telephony/java/android/telephony/ims/feature/ImsFeature.java
@@ -34,7 +34,6 @@
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
-import java.util.HashMap;
 import java.util.Map;
 
 /**
@@ -85,11 +84,10 @@
      * Used for logging purposes.
      * @hide
      */
-    public static final Map<Integer, String> FEATURE_LOG_MAP = new HashMap<Integer, String>() {{
-            put(FEATURE_EMERGENCY_MMTEL, "EMERGENCY_MMTEL");
-            put(FEATURE_MMTEL, "MMTEL");
-            put(FEATURE_RCS, "RCS");
-        }};
+    public static final Map<Integer, String> FEATURE_LOG_MAP = Map.of(
+            FEATURE_EMERGENCY_MMTEL, "EMERGENCY_MMTEL",
+            FEATURE_MMTEL, "MMTEL",
+            FEATURE_RCS, "RCS");
 
     /**
      * Integer values defining IMS features that are supported in ImsFeature.
@@ -145,11 +143,10 @@
      * Used for logging purposes.
      * @hide
      */
-    public static final Map<Integer, String> STATE_LOG_MAP = new HashMap<Integer, String>() {{
-            put(STATE_UNAVAILABLE, "UNAVAILABLE");
-            put(STATE_INITIALIZING, "INITIALIZING");
-            put(STATE_READY, "READY");
-        }};
+    public static final Map<Integer, String> STATE_LOG_MAP = Map.of(
+            STATE_UNAVAILABLE, "UNAVAILABLE",
+            STATE_INITIALIZING, "INITIALIZING",
+            STATE_READY, "READY");
 
     /**
      * Integer values defining the result codes that should be returned from
diff --git a/telephony/java/com/android/internal/telephony/IWwanSelectorCallback.aidl b/telephony/java/com/android/internal/telephony/IWwanSelectorCallback.aidl
index 65d994b..339fbee 100644
--- a/telephony/java/com/android/internal/telephony/IWwanSelectorCallback.aidl
+++ b/telephony/java/com/android/internal/telephony/IWwanSelectorCallback.aidl
@@ -21,6 +21,6 @@
 oneway interface IWwanSelectorCallback {
     void onRequestEmergencyNetworkScan(in int[] preferredNetworks,
             int scanType, in IWwanSelectorResultCallback cb);
-    void onDomainSelected(int accessNetworkType);
+    void onDomainSelected(int domain);
     void onCancel();
 }
diff --git a/telephony/java/com/android/internal/telephony/RILConstants.java b/telephony/java/com/android/internal/telephony/RILConstants.java
index 9f612e6..340ee72 100644
--- a/telephony/java/com/android/internal/telephony/RILConstants.java
+++ b/telephony/java/com/android/internal/telephony/RILConstants.java
@@ -542,6 +542,7 @@
     int RIL_REQUEST_STOP_IMS_TRAFFIC = 236;
     int RIL_REQUEST_SEND_ANBR_QUERY = 237;
     int RIL_REQUEST_TRIGGER_EPS_FALLBACK = 238;
+    int RIL_REQUEST_SET_NULL_CIPHER_AND_INTEGRITY_ENABLED = 239;
 
     /* Responses begin */
     int RIL_RESPONSE_ACKNOWLEDGEMENT = 800;
diff --git a/tests/CanvasCompare/src/com/android/test/hwuicompare/DisplayModifier.java b/tests/CanvasCompare/src/com/android/test/hwuicompare/DisplayModifier.java
index 0f4e122..4bcf5a4 100644
--- a/tests/CanvasCompare/src/com/android/test/hwuicompare/DisplayModifier.java
+++ b/tests/CanvasCompare/src/com/android/test/hwuicompare/DisplayModifier.java
@@ -16,14 +16,16 @@
 
 package com.android.test.hwuicompare;
 
-import java.util.LinkedHashMap;
-import java.util.Map.Entry;
+import static java.util.Map.entry;
 
 import android.graphics.Canvas;
 import android.graphics.Paint;
 import android.graphics.RectF;
 import android.util.Log;
 
+import java.util.Map;
+import java.util.Map.Entry;
+
 public abstract class DisplayModifier {
 
     // automated tests ignore any combination of operations that don't together return TOTAL_MASK
@@ -76,41 +78,36 @@
     };
 
     @SuppressWarnings("serial")
-    private static final LinkedHashMap<String, LinkedHashMap<String, DisplayModifier>> gMaps = new LinkedHashMap<String, LinkedHashMap<String, DisplayModifier>>() {
-        {
-            put("aa", new LinkedHashMap<String, DisplayModifier>() {
-                {
-                    put("true", new DisplayModifier() {
+    private static final Map<String, Map<String, DisplayModifier>> gMaps = Map.of(
+            "aa", Map.of(
+                    "true", new DisplayModifier() {
                         @Override
                         public void modifyDrawing(Paint paint, Canvas canvas) {
                             paint.setAntiAlias(true);
                         }
-                    });
-                    put("false", new DisplayModifier() {
+                    },
+                    "false", new DisplayModifier() {
                         @Override
                         public void modifyDrawing(Paint paint, Canvas canvas) {
                             paint.setAntiAlias(false);
                         }
-                    });
-                }
-            });
-            put("style", new LinkedHashMap<String, DisplayModifier>() {
-                {
-                    put("fill", new DisplayModifier() {
+                    }),
+            "style", Map.of(
+                    "fill", new DisplayModifier() {
                         @Override
                         public void modifyDrawing(Paint paint, Canvas canvas) {
                             paint.setStyle(Paint.Style.FILL);
                         }
-                    });
-                    put("stroke", new DisplayModifier() {
+                    },
+                    "stroke", new DisplayModifier() {
                         @Override
                         public void modifyDrawing(Paint paint, Canvas canvas) {
                             paint.setStyle(Paint.Style.STROKE);
                         }
                         @Override
                         protected int mask() { return SWEEP_STROKE_WIDTH_BIT; }
-                    });
-                    put("fillAndStroke", new DisplayModifier() {
+                    },
+                    "fillAndStroke", new DisplayModifier() {
                         @Override
                         public void modifyDrawing(Paint paint, Canvas canvas) {
                             paint.setStyle(Paint.Style.FILL_AND_STROKE);
@@ -118,131 +115,118 @@
 
                         @Override
                         protected int mask() { return SWEEP_STROKE_WIDTH_BIT; }
-                    });
-                }
-            });
-            put("strokeWidth", new LinkedHashMap<String, DisplayModifier>() {
-                {
-                    put("hair", new DisplayModifier() {
+                    }),
+            "strokeWidth", Map.of(
+                    "hair", new DisplayModifier() {
                         @Override
                         public void modifyDrawing(Paint paint, Canvas canvas) {
                             paint.setStrokeWidth(0);
                         }
                         @Override
                         protected int mask() { return SWEEP_STROKE_WIDTH_BIT; }
-                    });
-                    put("0.3", new DisplayModifier() {
+                    },
+                    "0.3", new DisplayModifier() {
                         @Override
                         public void modifyDrawing(Paint paint, Canvas canvas) {
                             paint.setStrokeWidth(0.3f);
                         }
-                    });
-                    put("1", new DisplayModifier() {
+                    },
+                    "1", new DisplayModifier() {
                         @Override
                         public void modifyDrawing(Paint paint, Canvas canvas) {
                             paint.setStrokeWidth(1);
                         }
-                    });
-                    put("5", new DisplayModifier() {
+                    },
+                    "5", new DisplayModifier() {
                         @Override
                         public void modifyDrawing(Paint paint, Canvas canvas) {
                             paint.setStrokeWidth(5);
                         }
-                    });
-                    put("30", new DisplayModifier() {
+                    },
+                    "30", new DisplayModifier() {
                         @Override
                         public void modifyDrawing(Paint paint, Canvas canvas) {
                             paint.setStrokeWidth(30);
                         }
-                    });
-                }
-            });
-            put("strokeCap", new LinkedHashMap<String, DisplayModifier>() {
-                {
-                    put("butt", new DisplayModifier() {
+                    }),
+            "strokeCap", Map.of(
+                    "butt", new DisplayModifier() {
                         @Override
                         public void modifyDrawing(Paint paint, Canvas canvas) {
                             paint.setStrokeCap(Paint.Cap.BUTT);
                         }
                         @Override
                         protected int mask() { return SWEEP_STROKE_CAP_BIT; }
-                    });
-                    put("round", new DisplayModifier() {
+                    },
+                    "round", new DisplayModifier() {
                         @Override
                         public void modifyDrawing(Paint paint, Canvas canvas) {
                             paint.setStrokeCap(Paint.Cap.ROUND);
                         }
-                    });
-                    put("square", new DisplayModifier() {
+                    },
+                    "square", new DisplayModifier() {
                         @Override
                         public void modifyDrawing(Paint paint, Canvas canvas) {
                             paint.setStrokeCap(Paint.Cap.SQUARE);
                         }
-                    });
-                }
-            });
-            put("strokeJoin", new LinkedHashMap<String, DisplayModifier>() {
-                {
-                    put("bevel", new DisplayModifier() {
+                    }),
+            "strokeJoin", Map.of(
+                    "bevel", new DisplayModifier() {
                         @Override
                         public void modifyDrawing(Paint paint, Canvas canvas) {
                             paint.setStrokeJoin(Paint.Join.BEVEL);
                         }
                         @Override
                         protected int mask() { return SWEEP_STROKE_JOIN_BIT; }
-                    });
-                    put("round", new DisplayModifier() {
+                    },
+                    "round", new DisplayModifier() {
                         @Override
                         public void modifyDrawing(Paint paint, Canvas canvas) {
                             paint.setStrokeJoin(Paint.Join.ROUND);
                         }
-                    });
-                    put("miter", new DisplayModifier() {
+                    },
+                    "miter", new DisplayModifier() {
                         @Override
                         public void modifyDrawing(Paint paint, Canvas canvas) {
                             paint.setStrokeJoin(Paint.Join.MITER);
                         }
-                    });
+                    }),
                     // TODO: add miter0, miter1 etc to test miter distances
-                }
-            });
-
-            put("transform", new LinkedHashMap<String, DisplayModifier>() {
-                {
-                    put("noTransform", new DisplayModifier() {
+            "transform", Map.of(
+                    "noTransform", new DisplayModifier() {
                         @Override
                         public void modifyDrawing(Paint paint, Canvas canvas) {}
                         @Override
                         protected int mask() { return SWEEP_TRANSFORM_BIT; };
-                    });
-                    put("rotate5", new DisplayModifier() {
+                    },
+                    "rotate5", new DisplayModifier() {
                         @Override
                         public void modifyDrawing(Paint paint, Canvas canvas) {
                             canvas.rotate(5);
                         }
-                    });
-                    put("rotate45", new DisplayModifier() {
+                    },
+                    "rotate45", new DisplayModifier() {
                         @Override
                         public void modifyDrawing(Paint paint, Canvas canvas) {
                             canvas.rotate(45);
                         }
-                    });
-                    put("rotate90", new DisplayModifier() {
+                    },
+                    "rotate90", new DisplayModifier() {
                         @Override
                         public void modifyDrawing(Paint paint, Canvas canvas) {
                             canvas.rotate(90);
                             canvas.translate(0, -200);
                         }
-                    });
-                    put("scale2x2", new DisplayModifier() {
+                    },
+                    "scale2x2", new DisplayModifier() {
                         @Override
                         public void modifyDrawing(Paint paint, Canvas canvas) {
                             canvas.scale(2, 2);
                         }
                         @Override
                         protected int mask() { return SWEEP_TRANSFORM_BIT; };
-                    });
-                    put("rot20scl1x4", new DisplayModifier() {
+                    },
+                    "rot20scl1x4", new DisplayModifier() {
                         @Override
                         public void modifyDrawing(Paint paint, Canvas canvas) {
                             canvas.rotate(20);
@@ -250,180 +234,167 @@
                         }
                         @Override
                         protected int mask() { return SWEEP_TRANSFORM_BIT; };
-                    });
-                }
-            });
-
-            put("shader", new LinkedHashMap<String, DisplayModifier>() {
-                {
-                    put("noShader", new DisplayModifier() {
+                    }),
+            "shader", Map.ofEntries(
+                    entry("noShader", new DisplayModifier() {
                         @Override
                         public void modifyDrawing(Paint paint, Canvas canvas) {}
                         @Override
                         protected int mask() { return SWEEP_SHADER_BIT; };
-                    });
-                    put("repeatShader", new DisplayModifier() {
+                    }),
+                    entry("repeatShader", new DisplayModifier() {
                         @Override
                         public void modifyDrawing(Paint paint, Canvas canvas) {
                             paint.setShader(ResourceModifiers.instance().mRepeatShader);
                         }
                         @Override
                         protected int mask() { return SWEEP_SHADER_BIT; };
-                    });
-                    put("translatedShader", new DisplayModifier() {
+                    }),
+                    entry("translatedShader", new DisplayModifier() {
                         @Override
                         public void modifyDrawing(Paint paint, Canvas canvas) {
                             paint.setShader(ResourceModifiers.instance().mTranslatedShader);
                         }
-                    });
-                    put("scaledShader", new DisplayModifier() {
+                    }),
+                    entry("scaledShader", new DisplayModifier() {
                         @Override
                         public void modifyDrawing(Paint paint, Canvas canvas) {
                             paint.setShader(ResourceModifiers.instance().mScaledShader);
                         }
-                    });
-                    put("horGradient", new DisplayModifier() {
+                    }),
+                    entry("horGradient", new DisplayModifier() {
                         @Override
                         public void modifyDrawing(Paint paint, Canvas canvas) {
                             paint.setShader(ResourceModifiers.instance().mHorGradient);
                         }
-                    });
-                    put("diagGradient", new DisplayModifier() {
+                    }),
+                    entry("diagGradient", new DisplayModifier() {
                         @Override
                         public void modifyDrawing(Paint paint, Canvas canvas) {
                             paint.setShader(ResourceModifiers.instance().mDiagGradient);
                         }
                         @Override
                         protected int mask() { return SWEEP_SHADER_BIT; };
-                    });
-                    put("vertGradient", new DisplayModifier() {
+                    }),
+                    entry("vertGradient", new DisplayModifier() {
                         @Override
                         public void modifyDrawing(Paint paint, Canvas canvas) {
                             paint.setShader(ResourceModifiers.instance().mVertGradient);
                         }
-                    });
-                    put("radGradient", new DisplayModifier() {
+                    }),
+                    entry("radGradient", new DisplayModifier() {
                         @Override
                         public void modifyDrawing(Paint paint, Canvas canvas) {
                             paint.setShader(ResourceModifiers.instance().mRadGradient);
                         }
-                    });
-                    put("sweepGradient", new DisplayModifier() {
+                    }),
+                    entry("sweepGradient", new DisplayModifier() {
                         @Override
                         public void modifyDrawing(Paint paint, Canvas canvas) {
                             paint.setShader(ResourceModifiers.instance().mSweepGradient);
                         }
-                    });
-                    put("composeShader", new DisplayModifier() {
+                    }),
+                    entry("composeShader", new DisplayModifier() {
                         @Override
                         public void modifyDrawing(Paint paint, Canvas canvas) {
                             paint.setShader(ResourceModifiers.instance().mComposeShader);
                         }
-                    });
-                    put("bad composeShader", new DisplayModifier() {
+                    }),
+                    entry("bad composeShader", new DisplayModifier() {
                         @Override
                         public void modifyDrawing(Paint paint, Canvas canvas) {
                             paint.setShader(ResourceModifiers.instance().mBadComposeShader);
                         }
-                    });
-                    put("bad composeShader 2", new DisplayModifier() {
+                    }),
+                    entry("bad composeShader 2", new DisplayModifier() {
                         @Override
                         public void modifyDrawing(Paint paint, Canvas canvas) {
                             paint.setShader(ResourceModifiers.instance().mAnotherBadComposeShader);
                         }
-                    });
-                }
-            });
-
-            // FINAL MAP: DOES ACTUAL DRAWING
-            put("drawing", new LinkedHashMap<String, DisplayModifier>() {
-                {
-                    put("roundRect", new DisplayModifier() {
+                    })),
+            "drawing", Map.ofEntries(
+                    entry("roundRect", new DisplayModifier() {
                         @Override
                         public void modifyDrawing(Paint paint, Canvas canvas) {
                             canvas.drawRoundRect(gRect, 20, 20, paint);
                         }
-                    });
-                    put("rect", new DisplayModifier() {
+                    }),
+                    entry("rect", new DisplayModifier() {
                         @Override
                         public void modifyDrawing(Paint paint, Canvas canvas) {
                             canvas.drawRect(gRect, paint);
                         }
                         @Override
                         protected int mask() { return SWEEP_SHADER_BIT | SWEEP_STROKE_CAP_BIT; };
-                    });
-                    put("circle", new DisplayModifier() {
+                    }),
+                    entry("circle", new DisplayModifier() {
                         @Override
                         public void modifyDrawing(Paint paint, Canvas canvas) {
                             canvas.drawCircle(100, 100, 75, paint);
                         }
-                    });
-                    put("oval", new DisplayModifier() {
+                    }),
+                    entry("oval", new DisplayModifier() {
                         @Override
                         public void modifyDrawing(Paint paint, Canvas canvas) {
                             canvas.drawOval(gRect, paint);
                         }
-                    });
-                    put("lines", new DisplayModifier() {
+                    }),
+                    entry("lines", new DisplayModifier() {
                         @Override
                         public void modifyDrawing(Paint paint, Canvas canvas) {
                             canvas.drawLines(gLinePts, paint);
                         }
                         @Override
                         protected int mask() { return SWEEP_STROKE_CAP_BIT; };
-                    });
-                    put("plusPoints", new DisplayModifier() {
+                    }),
+                    entry("plusPoints", new DisplayModifier() {
                         @Override
                         public void modifyDrawing(Paint paint, Canvas canvas) {
                             canvas.drawPoints(gPts, paint);
                         }
-                    });
-                    put("text", new DisplayModifier() {
+                    }),
+                    entry("text", new DisplayModifier() {
                         @Override
                         public void modifyDrawing(Paint paint, Canvas canvas) {
                             paint.setTextSize(36);
                             canvas.drawText("TEXTTEST", 0, 50, paint);
                         }
-                    });
-                    put("shadowtext", new DisplayModifier() {
+                    }),
+                    entry("shadowtext", new DisplayModifier() {
                         @Override
                         public void modifyDrawing(Paint paint, Canvas canvas) {
                             paint.setTextSize(36);
                             paint.setShadowLayer(3.0f, 0.0f, 3.0f, 0xffff00ff);
                             canvas.drawText("TEXTTEST", 0, 50, paint);
                         }
-                    });
-                    put("bitmapMesh", new DisplayModifier() {
+                    }),
+                    entry("bitmapMesh", new DisplayModifier() {
                         @Override
                         public void modifyDrawing(Paint paint, Canvas canvas) {
                             canvas.drawBitmapMesh(ResourceModifiers.instance().mBitmap, 3, 3,
                                     ResourceModifiers.instance().mBitmapVertices, 0, null, 0, null);
                         }
-                    });
-                    put("arc", new DisplayModifier() {
+                    }),
+                    entry("arc", new DisplayModifier() {
                         @Override
                         public void modifyDrawing(Paint paint, Canvas canvas) {
                             canvas.drawArc(gRect, 260, 285, false, paint);
                         }
                         @Override
                         protected int mask() { return SWEEP_STROKE_CAP_BIT; };
-                    });
-                    put("arcFromCenter", new DisplayModifier() {
+                    }),
+                    entry("arcFromCenter", new DisplayModifier() {
                         @Override
                         public void modifyDrawing(Paint paint, Canvas canvas) {
                             canvas.drawArc(gRect, 260, 285, true, paint);
                         }
                         @Override
                         protected int mask() { return SWEEP_STROKE_JOIN_BIT; };
-                    });
-                }
-            });
+                    })));
             // WARNING: DON'T PUT MORE MAPS BELOW THIS
-        }
-    };
 
-    private static LinkedHashMap<String, DisplayModifier> getMapAtIndex(int index) {
-        for (LinkedHashMap<String, DisplayModifier> map : gMaps.values()) {
+    private static Map<String, DisplayModifier> getMapAtIndex(int index) {
+        for (Map<String, DisplayModifier> map : gMaps.values()) {
             if (index == 0) {
                 return map;
             }
@@ -439,7 +410,7 @@
     private static boolean stepInternal(boolean forward) {
         int modifierMapIndex = gMaps.size() - 1;
         while (modifierMapIndex >= 0) {
-            LinkedHashMap<String, DisplayModifier> map = getMapAtIndex(modifierMapIndex);
+            Map<String, DisplayModifier> map = getMapAtIndex(modifierMapIndex);
             mIndices[modifierMapIndex] += (forward ? 1 : -1);
 
             if (mIndices[modifierMapIndex] >= 0 && mIndices[modifierMapIndex] < map.size()) {
@@ -471,7 +442,7 @@
     private static boolean checkModificationStateMask() {
         int operatorMask = 0x0;
         int mapIndex = 0;
-        for (LinkedHashMap<String, DisplayModifier> map : gMaps.values()) {
+        for (Map<String, DisplayModifier> map : gMaps.values()) {
             int displayModifierIndex = mIndices[mapIndex];
             for (Entry<String, DisplayModifier> modifierEntry : map.entrySet()) {
                 if (displayModifierIndex == 0) {
@@ -488,7 +459,7 @@
 
     public static void apply(Paint paint, Canvas canvas) {
         int mapIndex = 0;
-        for (LinkedHashMap<String, DisplayModifier> map : gMaps.values()) {
+        for (Map<String, DisplayModifier> map : gMaps.values()) {
             int displayModifierIndex = mIndices[mapIndex];
             for (Entry<String, DisplayModifier> modifierEntry : map.entrySet()) {
                 if (displayModifierIndex == 0) {
@@ -510,7 +481,7 @@
         String[][] keys = new String[gMaps.size()][];
 
         int i = 0;
-        for (LinkedHashMap<String, DisplayModifier> map : gMaps.values()) {
+        for (Map<String, DisplayModifier> map : gMaps.values()) {
             keys[i] = new String[map.size()];
             int j = 0;
             for (String key : map.keySet()) {
diff --git a/tests/HierarchyViewerTest/src/com/android/test/hierarchyviewer/ViewDumpParser.java b/tests/HierarchyViewerTest/src/com/android/test/hierarchyviewer/ViewDumpParser.java
index 2ad0da9..8b9c020 100644
--- a/tests/HierarchyViewerTest/src/com/android/test/hierarchyviewer/ViewDumpParser.java
+++ b/tests/HierarchyViewerTest/src/com/android/test/hierarchyviewer/ViewDumpParser.java
@@ -58,7 +58,7 @@
         Object hash = getProperty(props, "__hash__");
 
         if (name instanceof String && hash instanceof Integer) {
-            return String.format(Locale.US, "%s@%x", name, hash);
+            return String.format(Locale.US, "%s@%x", name, (Integer) hash);
         } else {
             return null;
         }
diff --git a/tests/Internal/src/com/android/internal/os/TimeoutRecordTest.java b/tests/Internal/src/com/android/internal/os/TimeoutRecordTest.java
new file mode 100644
index 0000000..0f96634
--- /dev/null
+++ b/tests/Internal/src/com/android/internal/os/TimeoutRecordTest.java
@@ -0,0 +1,131 @@
+/*
+ * 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.internal.os;
+
+import android.content.ComponentName;
+import android.content.Intent;
+import android.platform.test.annotations.Presubmit;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+import androidx.test.filters.SmallTest;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/** Tests for {@link TimeoutRecord}. */
+@SmallTest
+@Presubmit
+@RunWith(JUnit4.class)
+public class TimeoutRecordTest {
+
+    @Test
+    public void forBroadcastReceiver_returnsCorrectTimeoutRecord() {
+        Intent intent = new Intent(Intent.ACTION_MAIN);
+        intent.setComponent(ComponentName.createRelative("com.example.app", "ExampleClass"));
+
+        TimeoutRecord record = TimeoutRecord.forBroadcastReceiver(intent);
+
+        assertNotNull(record);
+        assertEquals(record.mKind, TimeoutRecord.TimeoutKind.BROADCAST_RECEIVER);
+        assertEquals(record.mReason,
+                "Broadcast of Intent { act=android.intent.action.MAIN cmp=com.example"
+                        + ".app/ExampleClass }");
+        assertTrue(record.mEndTakenBeforeLocks);
+    }
+
+    @Test
+    public void forBroadcastReceiver_withTimeoutDurationMs_returnsCorrectTimeoutRecord() {
+        Intent intent = new Intent(Intent.ACTION_MAIN);
+        intent.setComponent(ComponentName.createRelative("com.example.app", "ExampleClass"));
+
+        TimeoutRecord record = TimeoutRecord.forBroadcastReceiver(intent, 1000L);
+
+        assertNotNull(record);
+        assertEquals(record.mKind, TimeoutRecord.TimeoutKind.BROADCAST_RECEIVER);
+        assertEquals(record.mReason,
+                "Broadcast of Intent { act=android.intent.action.MAIN cmp=com.example"
+                        + ".app/ExampleClass }, waited 1000ms");
+        assertTrue(record.mEndTakenBeforeLocks);
+    }
+
+    @Test
+    public void forInputDispatchNoFocusedWindow_returnsCorrectTimeoutRecord() {
+        TimeoutRecord record = TimeoutRecord.forInputDispatchNoFocusedWindow("Test ANR reason");
+
+        assertNotNull(record);
+        assertEquals(record.mKind, TimeoutRecord.TimeoutKind.INPUT_DISPATCH_NO_FOCUSED_WINDOW);
+        assertEquals(record.mReason,
+                "Test ANR reason");
+        assertTrue(record.mEndTakenBeforeLocks);
+    }
+
+    @Test
+    public void forInputDispatchWindowUnresponsive_returnsCorrectTimeoutRecord() {
+        TimeoutRecord record = TimeoutRecord.forInputDispatchWindowUnresponsive("Test ANR reason");
+
+        assertNotNull(record);
+        assertEquals(record.mKind, TimeoutRecord.TimeoutKind.INPUT_DISPATCH_WINDOW_UNRESPONSIVE);
+        assertEquals(record.mReason, "Test ANR reason");
+        assertTrue(record.mEndTakenBeforeLocks);
+    }
+
+    @Test
+    public void forServiceExec_returnsCorrectTimeoutRecord() {
+        TimeoutRecord record = TimeoutRecord.forServiceExec("Test ANR reason");
+
+        assertNotNull(record);
+        assertEquals(record.mKind, TimeoutRecord.TimeoutKind.SERVICE_EXEC);
+        assertEquals(record.mReason, "Test ANR reason");
+        assertTrue(record.mEndTakenBeforeLocks);
+    }
+
+    @Test
+    public void forServiceStartWithEndTime_returnsCorrectTimeoutRecord() {
+        TimeoutRecord record = TimeoutRecord.forServiceStartWithEndTime("Test ANR reason", 1000L);
+
+        assertNotNull(record);
+        assertEquals(record.mKind, TimeoutRecord.TimeoutKind.SERVICE_START);
+        assertEquals(record.mReason, "Test ANR reason");
+        assertEquals(record.mEndUptimeMillis, 1000L);
+        assertTrue(record.mEndTakenBeforeLocks);
+    }
+
+    @Test
+    public void forContentProvider_returnsCorrectTimeoutRecord() {
+        TimeoutRecord record = TimeoutRecord.forContentProvider("Test ANR reason");
+
+        assertNotNull(record);
+        assertEquals(record.mKind, TimeoutRecord.TimeoutKind.CONTENT_PROVIDER);
+        assertEquals(record.mReason, "Test ANR reason");
+        assertFalse(record.mEndTakenBeforeLocks);
+    }
+
+    @Test
+    public void forApp_returnsCorrectTimeoutRecord() {
+        TimeoutRecord record = TimeoutRecord.forApp("Test ANR reason");
+
+        assertNotNull(record);
+        assertEquals(record.mKind, TimeoutRecord.TimeoutKind.APP_REGISTERED);
+        assertEquals(record.mReason, "Test ANR reason");
+        assertFalse(record.mEndTakenBeforeLocks);
+    }
+}
diff --git a/tests/JankBench/app/src/main/java/com/android/benchmark/app/HomeActivity.java b/tests/JankBench/app/src/main/java/com/android/benchmark/app/HomeActivity.java
index 4de51fb..43dc9de 100644
--- a/tests/JankBench/app/src/main/java/com/android/benchmark/app/HomeActivity.java
+++ b/tests/JankBench/app/src/main/java/com/android/benchmark/app/HomeActivity.java
@@ -140,9 +140,9 @@
         handleNextBenchmark();
     }
 
+    @SuppressWarnings("MissingSuperCall") // TODO: Fix me
     @Override
     protected void onActivityResult(int requestCode, int resultCode, Intent data) {
-
     }
 
     private void handleNextBenchmark() {
diff --git a/tests/JankBench/app/src/main/java/com/android/benchmark/app/RunLocalBenchmarksActivity.java b/tests/JankBench/app/src/main/java/com/android/benchmark/app/RunLocalBenchmarksActivity.java
index c16efbd..d015a56 100644
--- a/tests/JankBench/app/src/main/java/com/android/benchmark/app/RunLocalBenchmarksActivity.java
+++ b/tests/JankBench/app/src/main/java/com/android/benchmark/app/RunLocalBenchmarksActivity.java
@@ -367,6 +367,7 @@
         }
     }
 
+    @SuppressWarnings("MissingSuperCall") // TODO: Fix me
     @Override
     protected void onActivityResult(int requestCode, int resultCode, Intent data) {
         switch (requestCode) {
diff --git a/tests/MirrorSurfaceTest/src/com/google/android/test/mirrorsurface/MirrorSurfaceActivity.java b/tests/MirrorSurfaceTest/src/com/google/android/test/mirrorsurface/MirrorSurfaceActivity.java
index 8afe841..17fa210 100644
--- a/tests/MirrorSurfaceTest/src/com/google/android/test/mirrorsurface/MirrorSurfaceActivity.java
+++ b/tests/MirrorSurfaceTest/src/com/google/android/test/mirrorsurface/MirrorSurfaceActivity.java
@@ -295,8 +295,8 @@
     private void updateMirror(Rect displayFrame, float scale) {
         if (displayFrame.isEmpty()) {
             Rect bounds = mWindowBounds;
-            int defaultCropW = Math.round(bounds.width() / 2);
-            int defaultCropH = Math.round(bounds.height() / 2);
+            int defaultCropW = bounds.width() / 2;
+            int defaultCropH = bounds.height() / 2;
             displayFrame.set(0, 0, defaultCropW, defaultCropH);
         }
 
diff --git a/tests/RenderThreadTest/src/com/example/renderthread/MainActivity.java b/tests/RenderThreadTest/src/com/example/renderthread/MainActivity.java
index 241206d..65b7549 100644
--- a/tests/RenderThreadTest/src/com/example/renderthread/MainActivity.java
+++ b/tests/RenderThreadTest/src/com/example/renderthread/MainActivity.java
@@ -24,18 +24,14 @@
     static final String KEY_NAME = "name";
     static final String KEY_CLASS = "clazz";
 
-    static Map<String,?> make(String name) {
-        Map<String,Object> ret = new HashMap<String,Object>();
-        ret.put(KEY_NAME, name);
-        return ret;
-    }
-
-    @SuppressWarnings("serial")
-    static final ArrayList<Map<String,?>> SAMPLES = new ArrayList<Map<String,?>>() {{
+    static final ArrayList<Map<String, ?>> SAMPLES = new ArrayList<>();
+    static {
         for (int i = 1; i < 25; i++) {
-            add(make("List Item: " + i));
+            Map<String, Object> sample = new HashMap<String, Object>();
+            sample.put(KEY_NAME, "List Item: " + i);
+            SAMPLES.add(sample);
         }
-    }};
+    }
 
     Handler mHandler = new Handler();
 
diff --git a/tests/SmokeTestApps/src/com/android/smoketest/triggers/CrashyApp.java b/tests/SmokeTestApps/src/com/android/smoketest/triggers/CrashyApp.java
index c11b0f3..f85fb0f 100644
--- a/tests/SmokeTestApps/src/com/android/smoketest/triggers/CrashyApp.java
+++ b/tests/SmokeTestApps/src/com/android/smoketest/triggers/CrashyApp.java
@@ -30,6 +30,7 @@
         setContentView(tv);
     }
 
+    @SuppressWarnings("ReturnValueIgnored")
     @Override
     public void onResume() {
         ((String) null).length();
diff --git a/tests/vcn/java/com/android/server/VcnManagementServiceTest.java b/tests/vcn/java/com/android/server/VcnManagementServiceTest.java
index f924b2e..ad06830 100644
--- a/tests/vcn/java/com/android/server/VcnManagementServiceTest.java
+++ b/tests/vcn/java/com/android/server/VcnManagementServiceTest.java
@@ -637,8 +637,7 @@
         final BroadcastReceiver receiver = getPackageChangeReceiver();
 
         verify(mMockContext).registerReceiver(any(), argThat(filter -> {
-            return filter.hasAction(Intent.ACTION_PACKAGE_REMOVED)
-                    && filter.hasAction(Intent.ACTION_PACKAGE_REMOVED);
+            return filter.hasAction(Intent.ACTION_PACKAGE_REMOVED);
         }), any(), any());
 
         receiver.onReceive(mMockContext, new Intent(Intent.ACTION_PACKAGE_REMOVED));
diff --git a/tools/lint/checks/src/main/java/com/google/android/lint/AndroidFrameworkIssueRegistry.kt b/tools/lint/checks/src/main/java/com/google/android/lint/AndroidFrameworkIssueRegistry.kt
index 4d69d26..741655b 100644
--- a/tools/lint/checks/src/main/java/com/google/android/lint/AndroidFrameworkIssueRegistry.kt
+++ b/tools/lint/checks/src/main/java/com/google/android/lint/AndroidFrameworkIssueRegistry.kt
@@ -20,6 +20,7 @@
 import com.android.tools.lint.client.api.Vendor
 import com.android.tools.lint.detector.api.CURRENT_API
 import com.google.android.lint.aidl.EnforcePermissionDetector
+import com.google.android.lint.aidl.EnforcePermissionHelperDetector
 import com.google.android.lint.aidl.ManualPermissionCheckDetector
 import com.google.android.lint.parcel.SaferParcelChecker
 import com.google.auto.service.AutoService
@@ -38,6 +39,7 @@
         CallingSettingsNonUserGetterMethodsDetector.ISSUE_NON_USER_GETTER_CALLED,
         EnforcePermissionDetector.ISSUE_MISSING_ENFORCE_PERMISSION,
         EnforcePermissionDetector.ISSUE_MISMATCHING_ENFORCE_PERMISSION,
+        EnforcePermissionHelperDetector.ISSUE_ENFORCE_PERMISSION_HELPER,
         ManualPermissionCheckDetector.ISSUE_USE_ENFORCE_PERMISSION_ANNOTATION,
         SaferParcelChecker.ISSUE_UNSAFE_API_USAGE,
         PackageVisibilityDetector.ISSUE_PACKAGE_NAME_NO_PACKAGE_VISIBILITY_FILTERS,
diff --git a/tools/lint/checks/src/main/java/com/google/android/lint/aidl/EnforcePermissionHelperDetector.kt b/tools/lint/checks/src/main/java/com/google/android/lint/aidl/EnforcePermissionHelperDetector.kt
new file mode 100644
index 0000000..3c2ea1d
--- /dev/null
+++ b/tools/lint/checks/src/main/java/com/google/android/lint/aidl/EnforcePermissionHelperDetector.kt
@@ -0,0 +1,130 @@
+/*
+ * 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.google.android.lint.aidl
+
+import com.android.tools.lint.client.api.UElementHandler
+import com.android.tools.lint.detector.api.Category
+import com.android.tools.lint.detector.api.Detector
+import com.android.tools.lint.detector.api.Implementation
+import com.android.tools.lint.detector.api.Issue
+import com.android.tools.lint.detector.api.JavaContext
+import com.android.tools.lint.detector.api.Scope
+import com.android.tools.lint.detector.api.Severity
+import com.android.tools.lint.detector.api.SourceCodeScanner
+import com.intellij.psi.PsiElement
+import org.jetbrains.uast.UBlockExpression
+import org.jetbrains.uast.UDeclarationsExpression
+import org.jetbrains.uast.UExpression
+import org.jetbrains.uast.UElement
+import org.jetbrains.uast.UMethod
+
+class EnforcePermissionHelperDetector : Detector(), SourceCodeScanner {
+    override fun getApplicableUastTypes(): List<Class<out UElement?>> =
+            listOf(UMethod::class.java)
+
+    override fun createUastHandler(context: JavaContext): UElementHandler = AidlStubHandler(context)
+
+    private inner class AidlStubHandler(val context: JavaContext) : UElementHandler() {
+        override fun visitMethod(node: UMethod) {
+            if (!node.hasAnnotation(ANNOTATION_ENFORCE_PERMISSION)) return
+
+            val targetExpression = "super.${node.name}$HELPER_SUFFIX()"
+
+            val body = node.uastBody as? UBlockExpression
+            if (body == null) {
+                context.report(
+                        ISSUE_ENFORCE_PERMISSION_HELPER,
+                        context.getLocation(node),
+                        "Method must start with $targetExpression",
+                )
+                return
+            }
+
+            val firstExpression = body.expressions.firstOrNull()
+            if (firstExpression == null) {
+                context.report(
+                    ISSUE_ENFORCE_PERMISSION_HELPER,
+                    context.getLocation(node),
+                    "Method must start with $targetExpression",
+                )
+                return
+            }
+
+            val firstExpressionSource = firstExpression.asSourceString()
+                    .filterNot(Char::isWhitespace)
+
+            if (firstExpressionSource != targetExpression) {
+                val locationTarget = getLocationTarget(firstExpression)
+                val expressionLocation = context.getLocation(locationTarget)
+                val indent = " ".repeat(expressionLocation.start?.column ?: 0)
+
+                val fix = fix()
+                    .replace()
+                    .range(expressionLocation)
+                    .beginning()
+                    .with("$targetExpression;\n\n$indent")
+                    .reformat(true)
+                    .autoFix()
+                    .build()
+
+                context.report(
+                    ISSUE_ENFORCE_PERMISSION_HELPER,
+                    context.getLocation(node),
+                    "Method must start with $targetExpression",
+                    fix
+                )
+            }
+        }
+    }
+
+    companion object {
+        private const val HELPER_SUFFIX = "_enforcePermission"
+
+        private const val EXPLANATION = """
+            When @EnforcePermission is applied, the AIDL compiler generates a Stub method to do the
+            permission check called yourMethodName$HELPER_SUFFIX.
+
+            You must call this method as the first expression in your implementation.
+            """
+
+        val ISSUE_ENFORCE_PERMISSION_HELPER: Issue = Issue.create(
+                id = "MissingEnforcePermissionHelper",
+                briefDescription = """Missing permission-enforcing method call in AIDL method 
+                    |annotated with @EnforcePermission""".trimMargin(),
+                explanation = EXPLANATION,
+                category = Category.SECURITY,
+                priority = 6,
+                severity = Severity.ERROR,
+                implementation = Implementation(
+                        EnforcePermissionHelperDetector::class.java,
+                        Scope.JAVA_FILE_SCOPE
+                )
+        )
+
+        /**
+         * handles an edge case with UDeclarationsExpression, where sourcePsi is null,
+         * resulting in an incorrect Location if used directly
+         */
+        private fun getLocationTarget(firstExpression: UExpression): PsiElement? {
+            if (firstExpression.sourcePsi != null) return firstExpression.sourcePsi
+            if (firstExpression is UDeclarationsExpression) {
+                return firstExpression.declarations.firstOrNull()?.sourcePsi
+            }
+            return null
+        }
+    }
+}
diff --git a/tools/lint/checks/src/test/java/com/google/android/lint/aidl/EnforcePermissionHelperDetectorTest.kt b/tools/lint/checks/src/test/java/com/google/android/lint/aidl/EnforcePermissionHelperDetectorTest.kt
new file mode 100644
index 0000000..31e4846
--- /dev/null
+++ b/tools/lint/checks/src/test/java/com/google/android/lint/aidl/EnforcePermissionHelperDetectorTest.kt
@@ -0,0 +1,159 @@
+/*
+* 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.google.android.lint.aidl
+
+import com.android.tools.lint.checks.infrastructure.LintDetectorTest
+import com.android.tools.lint.checks.infrastructure.TestLintTask
+
+class EnforcePermissionHelperDetectorTest : LintDetectorTest() {
+    override fun getDetector() = EnforcePermissionHelperDetector()
+    override fun getIssues() = listOf(
+        EnforcePermissionHelperDetector.ISSUE_ENFORCE_PERMISSION_HELPER)
+
+    override fun lint(): TestLintTask = super.lint().allowMissingSdk()
+
+    fun testFirstExpressionIsFunctionCall() {
+        lint().files(
+            java(
+                """
+                    import android.content.Context;
+                    import android.test.ITest;
+                    public class Foo extends ITest.Stub {
+                        private Context mContext;
+                        @Override
+                        @android.annotation.EnforcePermission("android.Manifest.permission.READ_CONTACTS")
+                        public void test() throws android.os.RemoteException {
+                            Binder.getCallingUid();
+                        }
+                    }
+                """
+            ).indented(),
+            *stubs
+        )
+            .run()
+            .expect(
+                """
+                src/Foo.java:5: Error: Method must start with super.test_enforcePermission() [MissingEnforcePermissionHelper]
+                    @Override
+                    ^
+                1 errors, 0 warnings
+                """
+            )
+            .expectFixDiffs(
+                """
+                Autofix for src/Foo.java line 5: Replace with super.test_enforcePermission();...:
+                @@ -8 +8
+                +         super.test_enforcePermission();
+                +
+                """
+            )
+    }
+
+    fun testFirstExpressionIsVariableDeclaration() {
+        lint().files(
+            java(
+                """
+                import android.content.Context;
+                import android.test.ITest;
+                public class Foo extends ITest.Stub {
+                    private Context mContext;
+                    @Override
+                    @android.annotation.EnforcePermission("android.Manifest.permission.READ_CONTACTS")
+                    public void test() throws android.os.RemoteException {
+                        String foo = "bar";
+                        Binder.getCallingUid();
+                    }
+                }
+                """
+            ).indented(),
+            *stubs
+        )
+            .run()
+            .expect(
+                """
+                src/Foo.java:5: Error: Method must start with super.test_enforcePermission() [MissingEnforcePermissionHelper]
+                    @Override
+                    ^
+                1 errors, 0 warnings
+                """
+            )
+            .expectFixDiffs(
+                """
+                Autofix for src/Foo.java line 5: Replace with super.test_enforcePermission();...:
+                @@ -8 +8
+                +         super.test_enforcePermission();
+                +
+                """
+            )
+    }
+
+    fun testMethodIsEmpty() {
+        lint().files(
+            java(
+                """
+                import android.content.Context;
+                import android.test.ITest;
+                public class Foo extends ITest.Stub {
+                    private Context mContext;
+                    @Override
+                    @android.annotation.EnforcePermission("android.Manifest.permission.READ_CONTACTS")
+                    public void test() throws android.os.RemoteException {}
+                }
+                """
+            ).indented(),
+            *stubs
+        )
+            .run()
+            .expect(
+                """
+                src/Foo.java:5: Error: Method must start with super.test_enforcePermission() [MissingEnforcePermissionHelper]
+                    @Override
+                    ^
+                1 errors, 0 warnings
+                """
+            )
+    }
+
+    fun testOkay() {
+        lint().files(
+            java(
+                """
+                import android.content.Context;
+                import android.test.ITest;
+                public class Foo extends ITest.Stub {
+                    private Context mContext;
+                    @Override
+                    @android.annotation.EnforcePermission("android.Manifest.permission.READ_CONTACTS")
+                    public void test() throws android.os.RemoteException {
+                        super.test_enforcePermission();
+                    }
+                }
+                """
+            ).indented(),
+            *stubs
+        )
+            .run()
+            .expectClean()
+    }
+
+    companion object {
+        val stubs = arrayOf(aidlStub, contextStub, binderStub)
+    }
+}
+
+
+
diff --git a/tools/lint/checks/src/test/java/com/google/android/lint/aidl/ManualPermissionCheckDetectorTest.kt b/tools/lint/checks/src/test/java/com/google/android/lint/aidl/ManualPermissionCheckDetectorTest.kt
index a968f5e..d4a3497 100644
--- a/tools/lint/checks/src/test/java/com/google/android/lint/aidl/ManualPermissionCheckDetectorTest.kt
+++ b/tools/lint/checks/src/test/java/com/google/android/lint/aidl/ManualPermissionCheckDetectorTest.kt
@@ -17,7 +17,6 @@
 package com.google.android.lint.aidl
 
 import com.android.tools.lint.checks.infrastructure.LintDetectorTest
-import com.android.tools.lint.checks.infrastructure.TestFile
 import com.android.tools.lint.checks.infrastructure.TestLintTask
 import com.android.tools.lint.checks.infrastructure.TestMode
 import com.android.tools.lint.detector.api.Detector
@@ -361,82 +360,6 @@
 
 
     companion object {
-        private val aidlStub: TestFile = java(
-            """
-               package android.test;
-               public interface ITest extends android.os.IInterface {
-                    public static abstract class Stub extends android.os.Binder implements android.test.ITest {}
-                    public void test() throws android.os.RemoteException;
-               }
-            """
-        ).indented()
-
-        private val contextStub: TestFile = java(
-            """
-                package android.content;
-                public class Context {
-                    @android.content.pm.PermissionMethod
-                    public void enforceCallingOrSelfPermission(@android.content.pm.PermissionName String permission, String message) {}
-                }
-            """
-        ).indented()
-
-        private val binderStub: TestFile = java(
-            """
-                package android.os;
-                public class Binder {
-                    public static int getCallingUid() {}
-                }
-            """
-        ).indented()
-
-        private val permissionMethodStub: TestFile = java(
-            """
-                package android.content.pm;
-
-                import static java.lang.annotation.ElementType.METHOD;
-                import static java.lang.annotation.RetentionPolicy.CLASS;
-
-                import java.lang.annotation.Retention;
-                import java.lang.annotation.Target;
-
-                @Retention(CLASS)
-                @Target({METHOD})
-                public @interface PermissionMethod {}
-            """
-        ).indented()
-
-        private val permissionNameStub: TestFile = java(
-            """
-                package android.content.pm;
-
-                import static java.lang.annotation.ElementType.FIELD;
-                import static java.lang.annotation.ElementType.LOCAL_VARIABLE;
-                import static java.lang.annotation.ElementType.METHOD;
-                import static java.lang.annotation.ElementType.PARAMETER;
-                import static java.lang.annotation.RetentionPolicy.CLASS;
-
-                import java.lang.annotation.Retention;
-                import java.lang.annotation.Target;
-
-                @Retention(CLASS)
-                @Target({PARAMETER, METHOD, LOCAL_VARIABLE, FIELD})
-                public @interface PermissionName {}
-            """
-        ).indented()
-
-        private val manifestStub: TestFile = java(
-            """
-                package android;
-
-                public final class Manifest {
-                    public static final class permission {
-                        public static final String READ_CONTACTS="android.permission.READ_CONTACTS";
-                    }
-                }
-            """.trimIndent()
-        )
-
         val stubs = arrayOf(
             aidlStub,
             contextStub,
diff --git a/tools/lint/checks/src/test/java/com/google/android/lint/aidl/Stubs.kt b/tools/lint/checks/src/test/java/com/google/android/lint/aidl/Stubs.kt
new file mode 100644
index 0000000..bd6b195
--- /dev/null
+++ b/tools/lint/checks/src/test/java/com/google/android/lint/aidl/Stubs.kt
@@ -0,0 +1,80 @@
+package com.google.android.lint.aidl
+
+import com.android.tools.lint.checks.infrastructure.LintDetectorTest.java
+import com.android.tools.lint.checks.infrastructure.TestFile
+
+val aidlStub: TestFile = java(
+    """
+        package android.test;
+        public interface ITest extends android.os.IInterface {
+            public static abstract class Stub extends android.os.Binder implements android.test.ITest {}
+            public void test() throws android.os.RemoteException;
+        }
+    """
+).indented()
+
+val contextStub: TestFile = java(
+    """
+        package android.content;
+        public class Context {
+            @android.content.pm.PermissionMethod
+            public void enforceCallingOrSelfPermission(@android.content.pm.PermissionName String permission, String message) {}
+        }
+    """
+).indented()
+
+val binderStub: TestFile = java(
+    """
+        package android.os;
+        public class Binder {
+            public static int getCallingUid() {}
+        }
+    """
+).indented()
+
+val permissionMethodStub: TestFile = java(
+"""
+        package android.content.pm;
+
+        import static java.lang.annotation.ElementType.METHOD;
+        import static java.lang.annotation.RetentionPolicy.CLASS;
+
+        import java.lang.annotation.Retention;
+        import java.lang.annotation.Target;
+
+        @Retention(CLASS)
+        @Target({METHOD})
+        public @interface PermissionMethod {}
+    """
+).indented()
+
+val permissionNameStub: TestFile = java(
+"""
+        package android.content.pm;
+
+        import static java.lang.annotation.ElementType.FIELD;
+        import static java.lang.annotation.ElementType.LOCAL_VARIABLE;
+        import static java.lang.annotation.ElementType.METHOD;
+        import static java.lang.annotation.ElementType.PARAMETER;
+        import static java.lang.annotation.RetentionPolicy.CLASS;
+
+        import java.lang.annotation.Retention;
+        import java.lang.annotation.Target;
+
+        @Retention(CLASS)
+        @Target({PARAMETER, METHOD, LOCAL_VARIABLE, FIELD})
+        public @interface PermissionName {}
+    """
+).indented()
+
+val manifestStub: TestFile = java(
+    """
+        package android;
+
+        public final class Manifest {
+            public static final class permission {
+                public static final String READ_CONTACTS="android.permission.READ_CONTACTS";
+            }
+        }
+    """.trimIndent()
+)
\ No newline at end of file
diff --git a/wifi/tests/src/android/net/wifi/nl80211/WifiNl80211ManagerTest.java b/wifi/tests/src/android/net/wifi/nl80211/WifiNl80211ManagerTest.java
index a750696..5012622 100644
--- a/wifi/tests/src/android/net/wifi/nl80211/WifiNl80211ManagerTest.java
+++ b/wifi/tests/src/android/net/wifi/nl80211/WifiNl80211ManagerTest.java
@@ -117,13 +117,12 @@
     private static final byte[] TEST_PSK =
             new byte[]{'T', 'e', 's', 't'};
 
-    private static final Set<Integer> SCAN_FREQ_SET =
-            new HashSet<Integer>() {{
-                add(2410);
-                add(2450);
-                add(5050);
-                add(5200);
-            }};
+    private static final Set<Integer> SCAN_FREQ_SET = Set.of(
+            2410,
+            2450,
+            5050,
+            5200);
+
     private static final String TEST_QUOTED_SSID_1 = "\"testSsid1\"";
     private static final String TEST_QUOTED_SSID_2 = "\"testSsid2\"";
     private static final int[] TEST_FREQUENCIES_1 = {};
@@ -131,13 +130,11 @@
     private static final MacAddress TEST_RAW_MAC_BYTES = MacAddress.fromBytes(
             new byte[]{0x00, 0x01, 0x02, 0x03, 0x04, 0x05});
 
-    private static final List<byte[]> SCAN_HIDDEN_NETWORK_SSID_LIST =
-            new ArrayList<byte[]>() {{
-                add(LocalNativeUtil.byteArrayFromArrayList(
-                        LocalNativeUtil.decodeSsid(TEST_QUOTED_SSID_1)));
-                add(LocalNativeUtil.byteArrayFromArrayList(
-                        LocalNativeUtil.decodeSsid(TEST_QUOTED_SSID_2)));
-            }};
+    private static final List<byte[]> SCAN_HIDDEN_NETWORK_SSID_LIST = List.of(
+            LocalNativeUtil.byteArrayFromArrayList(
+                    LocalNativeUtil.decodeSsid(TEST_QUOTED_SSID_1)),
+            LocalNativeUtil.byteArrayFromArrayList(
+                    LocalNativeUtil.decodeSsid(TEST_QUOTED_SSID_2)));
 
     private static final PnoSettings TEST_PNO_SETTINGS = new PnoSettings();
     static {