Merge "Create SeekBar with icon buttons for common usage" into tm-qpr-dev
diff --git a/core/java/android/os/BatteryStats.java b/core/java/android/os/BatteryStats.java
index 3d5c34c..76475f2 100644
--- a/core/java/android/os/BatteryStats.java
+++ b/core/java/android/os/BatteryStats.java
@@ -649,8 +649,11 @@
             return Uid.PROCESS_STATE_NONEXISTENT;
         } else if (procState == ActivityManager.PROCESS_STATE_TOP) {
             return Uid.PROCESS_STATE_TOP;
-        } else if (ActivityManager.isForegroundService(procState)) {
-            // State when app has put itself in the foreground.
+        } else if (procState == ActivityManager.PROCESS_STATE_BOUND_TOP) {
+            return Uid.PROCESS_STATE_BACKGROUND;
+        } else if (procState == ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE) {
+            return Uid.PROCESS_STATE_FOREGROUND_SERVICE;
+        } else if (procState == ActivityManager.PROCESS_STATE_BOUND_FOREGROUND_SERVICE) {
             return Uid.PROCESS_STATE_FOREGROUND_SERVICE;
         } else if (procState <= ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND) {
             // Persistent and other foreground states go here.
diff --git a/core/java/android/os/UidBatteryConsumer.java b/core/java/android/os/UidBatteryConsumer.java
index 91d231e..787b609 100644
--- a/core/java/android/os/UidBatteryConsumer.java
+++ b/core/java/android/os/UidBatteryConsumer.java
@@ -51,8 +51,7 @@
     }
 
     /**
-     * The state of an application when it is either running a foreground (top) activity
-     * or a foreground service.
+     * The state of an application when it is either running a foreground (top) activity.
      */
     public static final int STATE_FOREGROUND = 0;
 
@@ -64,7 +63,8 @@
      * {@link android.app.ActivityManager#PROCESS_STATE_TRANSIENT_BACKGROUND},
      * {@link android.app.ActivityManager#PROCESS_STATE_BACKUP},
      * {@link android.app.ActivityManager#PROCESS_STATE_SERVICE},
-     * {@link android.app.ActivityManager#PROCESS_STATE_RECEIVER}.
+     * {@link android.app.ActivityManager#PROCESS_STATE_RECEIVER},
+     * {@link android.app.ActivityManager#PROCESS_STATE_FOREGROUND_SERVICE}.
      */
     public static final int STATE_BACKGROUND = 1;
 
diff --git a/core/java/android/service/dreams/DreamService.java b/core/java/android/service/dreams/DreamService.java
index 6a4710f..d79ea89 100644
--- a/core/java/android/service/dreams/DreamService.java
+++ b/core/java/android/service/dreams/DreamService.java
@@ -234,6 +234,7 @@
     private boolean mCanDoze;
     private boolean mDozing;
     private boolean mWindowless;
+    private boolean mOverlayFinishing;
     private int mDozeScreenState = Display.STATE_UNKNOWN;
     private int mDozeScreenBrightness = PowerManager.BRIGHTNESS_DEFAULT;
 
@@ -1051,6 +1052,7 @@
         // We must unbind from any overlay connection if we are unbound before finishing.
         if (mOverlayConnection != null) {
             mOverlayConnection.unbind();
+            mOverlayConnection = null;
         }
 
         return super.onUnbind(intent);
@@ -1067,7 +1069,9 @@
         // If there is an active overlay connection, signal that the dream is ending before
         // continuing. Note that the overlay cannot rely on the unbound state, since another dream
         // might have bound to it in the meantime.
-        if (mOverlayConnection != null) {
+        if (mOverlayConnection != null && !mOverlayFinishing) {
+            // Set mOverlayFinish to true to only allow this consumer to be added once.
+            mOverlayFinishing = true;
             mOverlayConnection.addConsumer(overlay -> {
                 try {
                     overlay.endDream();
@@ -1300,9 +1304,10 @@
      * Must run on mHandler.
      *
      * @param dreamToken Token for this dream service.
-     * @param started A callback that will be invoked once onDreamingStarted has completed.
+     * @param started    A callback that will be invoked once onDreamingStarted has completed.
      */
-    private void attach(IBinder dreamToken, boolean canDoze, IRemoteCallback started) {
+    private void attach(IBinder dreamToken, boolean canDoze, boolean isPreviewMode,
+            IRemoteCallback started) {
         if (mDreamToken != null) {
             Slog.e(mTag, "attach() called when dream with token=" + mDreamToken
                     + " already attached");
@@ -1350,7 +1355,8 @@
             i.putExtra(DreamActivity.EXTRA_CALLBACK, new DreamActivityCallbacks(mDreamToken));
             final ServiceInfo serviceInfo = fetchServiceInfo(this,
                     new ComponentName(this, getClass()));
-            i.putExtra(DreamActivity.EXTRA_DREAM_TITLE, fetchDreamLabel(this, serviceInfo));
+            i.putExtra(DreamActivity.EXTRA_DREAM_TITLE,
+                    fetchDreamLabel(this, serviceInfo, isPreviewMode));
 
             try {
                 if (!ActivityTaskManager.getService().startDreamActivity(i)) {
@@ -1466,10 +1472,18 @@
 
     @Nullable
     private static CharSequence fetchDreamLabel(Context context,
-            @Nullable ServiceInfo serviceInfo) {
-        if (serviceInfo == null) return null;
+            @Nullable ServiceInfo serviceInfo,
+            boolean isPreviewMode) {
+        if (serviceInfo == null) {
+            return null;
+        }
         final PackageManager pm = context.getPackageManager();
-        return serviceInfo.loadLabel(pm);
+        final CharSequence dreamLabel = serviceInfo.loadLabel(pm);
+        if (!isPreviewMode || dreamLabel == null) {
+            return dreamLabel;
+        }
+        // When in preview mode, return a special label indicating the dream is in preview.
+        return context.getResources().getString(R.string.dream_preview_title, dreamLabel);
     }
 
     @Nullable
@@ -1525,8 +1539,9 @@
     final class DreamServiceWrapper extends IDreamService.Stub {
         @Override
         public void attach(final IBinder dreamToken, final boolean canDoze,
-                IRemoteCallback started) {
-            mHandler.post(() -> DreamService.this.attach(dreamToken, canDoze, started));
+                final boolean isPreviewMode, IRemoteCallback started) {
+            mHandler.post(
+                    () -> DreamService.this.attach(dreamToken, canDoze, isPreviewMode, started));
         }
 
         @Override
diff --git a/core/java/android/service/dreams/IDreamService.aidl b/core/java/android/service/dreams/IDreamService.aidl
index ce04354..8b5d875 100644
--- a/core/java/android/service/dreams/IDreamService.aidl
+++ b/core/java/android/service/dreams/IDreamService.aidl
@@ -22,7 +22,7 @@
  * @hide
  */
 oneway interface IDreamService {
-    void attach(IBinder windowToken, boolean canDoze, IRemoteCallback started);
+    void attach(IBinder windowToken, boolean canDoze, boolean isPreviewMode, IRemoteCallback started);
     void detach();
     void wakeUp();
 }
diff --git a/core/java/com/android/internal/os/BatteryUsageStatsProvider.java b/core/java/com/android/internal/os/BatteryUsageStatsProvider.java
index 09e409b..ac4976f 100644
--- a/core/java/com/android/internal/os/BatteryUsageStatsProvider.java
+++ b/core/java/com/android/internal/os/BatteryUsageStatsProvider.java
@@ -298,18 +298,16 @@
                 BatteryStats.Uid.PROCESS_STATE_FOREGROUND, realtimeUs,
                 BatteryStats.STATS_SINCE_CHARGED);
 
-        totalForegroundDurationUs += uid.getProcessStateTime(
-                BatteryStats.Uid.PROCESS_STATE_FOREGROUND_SERVICE, realtimeUs,
-                BatteryStats.STATS_SINCE_CHARGED);
-
         return totalForegroundDurationUs / 1000;
     }
 
     private long getProcessBackgroundTimeMs(BatteryStats.Uid uid, long realtimeUs) {
-        return uid.getProcessStateTime(BatteryStats.Uid.PROCESS_STATE_BACKGROUND, realtimeUs,
-                BatteryStats.STATS_SINCE_CHARGED) / 1000;
+        return (uid.getProcessStateTime(BatteryStats.Uid.PROCESS_STATE_BACKGROUND,
+                realtimeUs, BatteryStats.STATS_SINCE_CHARGED)
+                + uid.getProcessStateTime(BatteryStats.Uid.PROCESS_STATE_FOREGROUND_SERVICE,
+                realtimeUs, BatteryStats.STATS_SINCE_CHARGED))
+                / 1000;
     }
-
     private BatteryUsageStats getAggregatedBatteryUsageStats(BatteryUsageStatsQuery query) {
         final boolean includePowerModels = (query.getFlags()
                 & BatteryUsageStatsQuery.FLAG_BATTERY_USAGE_STATS_INCLUDE_POWER_MODELS) != 0;
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index ba54183..2091c05 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -975,6 +975,11 @@
     <!-- Description for the capability of an accessibility service to take screenshot. [CHAR LIMIT=NONE] -->
     <string name="capability_desc_canTakeScreenshot">Can take a screenshot of the display.</string>
 
+    <!-- Dream -->
+
+    <!-- The title to use when a dream is opened in preview mode. [CHAR LIMIT=NONE] -->
+    <string name="dream_preview_title">Preview, <xliff:g id="dream_name" example="Clock">%1$s</xliff:g></string>
+
     <!--  Permissions -->
 
     <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index ad5b9c0..9cc8aa8 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -4285,6 +4285,8 @@
   <java-symbol type="string" name="capability_desc_canTakeScreenshot" />
   <java-symbol type="string" name="capability_title_canTakeScreenshot" />
 
+  <java-symbol type="string" name="dream_preview_title" />
+
   <java-symbol type="string" name="config_servicesExtensionPackage" />
 
   <!-- For app process exit info tracking -->
diff --git a/core/tests/coretests/src/com/android/internal/os/BatteryStatsNoteTest.java b/core/tests/coretests/src/com/android/internal/os/BatteryStatsNoteTest.java
index 52feac5..4c9b2b7 100644
--- a/core/tests/coretests/src/com/android/internal/os/BatteryStatsNoteTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/BatteryStatsNoteTest.java
@@ -360,6 +360,7 @@
         // map of ActivityManager process states and how long to simulate run time in each state
         Map<Integer, Integer> stateRuntimeMap = new HashMap<Integer, Integer>();
         stateRuntimeMap.put(ActivityManager.PROCESS_STATE_TOP, 1111);
+        stateRuntimeMap.put(ActivityManager.PROCESS_STATE_BOUND_TOP, 7382);
         stateRuntimeMap.put(ActivityManager.PROCESS_STATE_BOUND_FOREGROUND_SERVICE, 1234);
         stateRuntimeMap.put(ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE, 2468);
         stateRuntimeMap.put(ActivityManager.PROCESS_STATE_TOP_SLEEPING, 7531);
@@ -396,7 +397,8 @@
 
         actualRunTimeUs = uid.getProcessStateTime(BatteryStats.Uid.PROCESS_STATE_FOREGROUND_SERVICE,
                 elapsedTimeUs, STATS_SINCE_CHARGED);
-        expectedRunTimeMs = stateRuntimeMap.get(ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE);
+        expectedRunTimeMs = stateRuntimeMap.get(ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE)
+                + stateRuntimeMap.get(ActivityManager.PROCESS_STATE_BOUND_FOREGROUND_SERVICE);
         assertEquals(expectedRunTimeMs * 1000, actualRunTimeUs);
 
         actualRunTimeUs = uid.getProcessStateTime(BatteryStats.Uid.PROCESS_STATE_TOP_SLEEPING,
@@ -406,8 +408,7 @@
 
         actualRunTimeUs = uid.getProcessStateTime(BatteryStats.Uid.PROCESS_STATE_FOREGROUND,
                 elapsedTimeUs, STATS_SINCE_CHARGED);
-        expectedRunTimeMs = stateRuntimeMap.get(ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND)
-                + stateRuntimeMap.get(ActivityManager.PROCESS_STATE_BOUND_FOREGROUND_SERVICE);
+        expectedRunTimeMs = stateRuntimeMap.get(ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND);
         assertEquals(expectedRunTimeMs * 1000, actualRunTimeUs);
 
         actualRunTimeUs = uid.getProcessStateTime(BatteryStats.Uid.PROCESS_STATE_BACKGROUND,
@@ -415,7 +416,8 @@
         expectedRunTimeMs = stateRuntimeMap.get(ActivityManager.PROCESS_STATE_TRANSIENT_BACKGROUND)
                 + stateRuntimeMap.get(ActivityManager.PROCESS_STATE_BACKUP)
                 + stateRuntimeMap.get(ActivityManager.PROCESS_STATE_SERVICE)
-                + stateRuntimeMap.get(ActivityManager.PROCESS_STATE_RECEIVER);
+                + stateRuntimeMap.get(ActivityManager.PROCESS_STATE_RECEIVER)
+                + stateRuntimeMap.get(ActivityManager.PROCESS_STATE_BOUND_TOP);
         assertEquals(expectedRunTimeMs * 1000, actualRunTimeUs);
 
         actualRunTimeUs = uid.getProcessStateTime(BatteryStats.Uid.PROCESS_STATE_CACHED,
diff --git a/core/tests/coretests/src/com/android/internal/os/BatteryUsageStatsProviderTest.java b/core/tests/coretests/src/com/android/internal/os/BatteryUsageStatsProviderTest.java
index 354b937..2742861 100644
--- a/core/tests/coretests/src/com/android/internal/os/BatteryUsageStatsProviderTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/BatteryUsageStatsProviderTest.java
@@ -78,9 +78,9 @@
                 batteryUsageStats.getUidBatteryConsumers();
         final UidBatteryConsumer uidBatteryConsumer = uidBatteryConsumers.get(0);
         assertThat(uidBatteryConsumer.getTimeInStateMs(UidBatteryConsumer.STATE_FOREGROUND))
-                .isEqualTo(60 * MINUTE_IN_MS);
+                .isEqualTo(20 * MINUTE_IN_MS);
         assertThat(uidBatteryConsumer.getTimeInStateMs(UidBatteryConsumer.STATE_BACKGROUND))
-                .isEqualTo(10 * MINUTE_IN_MS);
+                .isEqualTo(40 * MINUTE_IN_MS);
         assertThat(uidBatteryConsumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_AUDIO))
                 .isWithin(PRECISION).of(2.0);
         assertThat(
@@ -121,22 +121,44 @@
     private BatteryStatsImpl prepareBatteryStats() {
         BatteryStatsImpl batteryStats = mStatsRule.getBatteryStats();
 
-        batteryStats.noteActivityResumedLocked(APP_UID,
-                10 * MINUTE_IN_MS, 10 * MINUTE_IN_MS);
-        batteryStats.noteUidProcessStateLocked(APP_UID, ActivityManager.PROCESS_STATE_TOP,
-                10 * MINUTE_IN_MS, 10 * MINUTE_IN_MS);
-        batteryStats.noteActivityPausedLocked(APP_UID,
-                30 * MINUTE_IN_MS, 30 * MINUTE_IN_MS);
-        batteryStats.noteUidProcessStateLocked(APP_UID, ActivityManager.PROCESS_STATE_SERVICE,
-                30 * MINUTE_IN_MS, 30 * MINUTE_IN_MS);
-        batteryStats.noteUidProcessStateLocked(APP_UID,
-                ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE,
-                40 * MINUTE_IN_MS, 40 * MINUTE_IN_MS);
-        batteryStats.noteUidProcessStateLocked(APP_UID,
-                ActivityManager.PROCESS_STATE_BOUND_FOREGROUND_SERVICE,
-                50 * MINUTE_IN_MS, 50 * MINUTE_IN_MS);
-        batteryStats.noteUidProcessStateLocked(APP_UID, ActivityManager.PROCESS_STATE_CACHED_EMPTY,
-                80 * MINUTE_IN_MS, 80 * MINUTE_IN_MS);
+        mStatsRule.setTime(10 * MINUTE_IN_MS, 10 * MINUTE_IN_MS);
+        synchronized (batteryStats) {
+            batteryStats.noteActivityResumedLocked(APP_UID);
+        }
+
+        mStatsRule.setTime(10 * MINUTE_IN_MS, 10 * MINUTE_IN_MS);
+        synchronized (batteryStats) {
+            batteryStats.noteUidProcessStateLocked(APP_UID, ActivityManager.PROCESS_STATE_TOP);
+        }
+        mStatsRule.setTime(30 * MINUTE_IN_MS, 30 * MINUTE_IN_MS);
+        synchronized (batteryStats) {
+            batteryStats.noteActivityPausedLocked(APP_UID);
+        }
+        mStatsRule.setTime(30 * MINUTE_IN_MS, 30 * MINUTE_IN_MS);
+        synchronized (batteryStats) {
+            batteryStats.noteUidProcessStateLocked(APP_UID,
+                    ActivityManager.PROCESS_STATE_SERVICE);
+        }
+        mStatsRule.setTime(40 * MINUTE_IN_MS, 40 * MINUTE_IN_MS);
+        synchronized (batteryStats) {
+            batteryStats.noteUidProcessStateLocked(APP_UID,
+                    ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE);
+        }
+        mStatsRule.setTime(50 * MINUTE_IN_MS, 50 * MINUTE_IN_MS);
+        synchronized (batteryStats) {
+            batteryStats.noteUidProcessStateLocked(APP_UID,
+                    ActivityManager.PROCESS_STATE_BOUND_FOREGROUND_SERVICE);
+        }
+        mStatsRule.setTime(60 * MINUTE_IN_MS, 60 * MINUTE_IN_MS);
+        synchronized (batteryStats) {
+            batteryStats.noteUidProcessStateLocked(APP_UID,
+                    ActivityManager.PROCESS_STATE_BOUND_TOP);
+        }
+        mStatsRule.setTime(70 * MINUTE_IN_MS, 70 * MINUTE_IN_MS);
+        synchronized (batteryStats) {
+            batteryStats.noteUidProcessStateLocked(APP_UID,
+                    ActivityManager.PROCESS_STATE_CACHED_EMPTY);
+        }
 
         batteryStats.noteFlashlightOnLocked(APP_UID, 1000, 1000);
         batteryStats.noteFlashlightOffLocked(APP_UID, 5000, 5000);
diff --git a/errorprone/java/com/google/errorprone/bugpatterns/android/RequiresPermissionChecker.java b/errorprone/java/com/google/errorprone/bugpatterns/android/RequiresPermissionChecker.java
index fa173072..d39d4b4 100644
--- a/errorprone/java/com/google/errorprone/bugpatterns/android/RequiresPermissionChecker.java
+++ b/errorprone/java/com/google/errorprone/bugpatterns/android/RequiresPermissionChecker.java
@@ -412,11 +412,11 @@
 
     private static ParsedRequiresPermission parseRequiresPermissionRecursively(
             MethodInvocationTree tree, VisitorState state) {
-        if (ENFORCE_VIA_CONTEXT.matches(tree, state)) {
+        if (ENFORCE_VIA_CONTEXT.matches(tree, state) && tree.getArguments().size() > 0) {
             final ParsedRequiresPermission res = new ParsedRequiresPermission();
             res.allOf.add(String.valueOf(ASTHelpers.constValue(tree.getArguments().get(0))));
             return res;
-        } else if (ENFORCE_VIA_CHECKER.matches(tree, state)) {
+        } else if (ENFORCE_VIA_CHECKER.matches(tree, state) && tree.getArguments().size() > 1) {
             final ParsedRequiresPermission res = new ParsedRequiresPermission();
             res.allOf.add(String.valueOf(ASTHelpers.constValue(tree.getArguments().get(1))));
             return res;
diff --git a/errorprone/tests/java/com/google/errorprone/bugpatterns/android/RequiresPermissionCheckerTest.java b/errorprone/tests/java/com/google/errorprone/bugpatterns/android/RequiresPermissionCheckerTest.java
index 388988e..38831b1 100644
--- a/errorprone/tests/java/com/google/errorprone/bugpatterns/android/RequiresPermissionCheckerTest.java
+++ b/errorprone/tests/java/com/google/errorprone/bugpatterns/android/RequiresPermissionCheckerTest.java
@@ -415,4 +415,27 @@
                         "}")
                 .doTest();
     }
+
+    @Test
+    public void testInvalidFunctions() {
+        compilationHelper
+                .addSourceFile("/android/annotation/RequiresPermission.java")
+                .addSourceFile("/android/annotation/SuppressLint.java")
+                .addSourceFile("/android/content/Context.java")
+                .addSourceLines("Example.java",
+                        "import android.annotation.RequiresPermission;",
+                        "import android.annotation.SuppressLint;",
+                        "import android.content.Context;",
+                        "class Foo extends Context {",
+                        "  private static final String RED = \"red\";",
+                        "  public void checkPermission() {",
+                        "  }",
+                        "  @RequiresPermission(RED)",
+                        "  // BUG: Diagnostic contains:",
+                        "  public void exampleScoped(Context context) {",
+                        "    checkPermission();",
+                        "  }",
+                        "}")
+                .doTest();
+    }
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeController.java
index d850416..bc81710 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeController.java
@@ -22,6 +22,7 @@
 import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
 import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
 import static android.view.WindowManager.TRANSIT_CHANGE;
+import static android.view.WindowManager.TRANSIT_NONE;
 import static android.view.WindowManager.TRANSIT_OPEN;
 import static android.view.WindowManager.TRANSIT_TO_FRONT;
 
@@ -256,10 +257,13 @@
         WindowContainerTransaction wct = new WindowContainerTransaction();
         bringDesktopAppsToFront(wct);
 
-        if (Transitions.ENABLE_SHELL_TRANSITIONS) {
-            mTransitions.startTransition(TRANSIT_TO_FRONT, wct, null /* handler */);
-        } else {
-            mShellTaskOrganizer.applyTransaction(wct);
+        if (!wct.isEmpty()) {
+            if (Transitions.ENABLE_SHELL_TRANSITIONS) {
+                // TODO(b/268662477): add animation for the transition
+                mTransitions.startTransition(TRANSIT_NONE, wct, null /* handler */);
+            } else {
+                mShellTaskOrganizer.applyTransaction(wct);
+            }
         }
     }
 
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
index 2303325..fce0138 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
@@ -27,6 +27,7 @@
 import android.os.IBinder
 import android.view.SurfaceControl
 import android.view.WindowManager.TRANSIT_CHANGE
+import android.view.WindowManager.TRANSIT_NONE
 import android.view.WindowManager.TRANSIT_OPEN
 import android.view.WindowManager.TRANSIT_TO_FRONT
 import android.window.TransitionInfo
@@ -89,7 +90,8 @@
         // Execute transaction if there are pending operations
         if (!wct.isEmpty) {
             if (Transitions.ENABLE_SHELL_TRANSITIONS) {
-                transitions.startTransition(TRANSIT_TO_FRONT, wct, null /* handler */)
+                // TODO(b/268662477): add animation for the transition
+                transitions.startTransition(TRANSIT_NONE, wct, null /* handler */)
             } else {
                 shellTaskOrganizer.applyTransaction(wct)
             }
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeControllerTest.java
index 7997a7e..43f8f7b 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeControllerTest.java
@@ -22,6 +22,7 @@
 import static android.app.WindowConfiguration.WINDOW_CONFIG_BOUNDS;
 import static android.view.WindowManager.TRANSIT_CHANGE;
 import static android.view.WindowManager.TRANSIT_CLOSE;
+import static android.view.WindowManager.TRANSIT_NONE;
 import static android.view.WindowManager.TRANSIT_OPEN;
 import static android.view.WindowManager.TRANSIT_TO_FRONT;
 import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_REORDER;
@@ -446,7 +447,7 @@
         final ArgumentCaptor<WindowContainerTransaction> arg = ArgumentCaptor.forClass(
                 WindowContainerTransaction.class);
         if (Transitions.ENABLE_SHELL_TRANSITIONS) {
-            verify(mTransitions).startTransition(eq(TRANSIT_TO_FRONT), arg.capture(), any());
+            verify(mTransitions).startTransition(eq(TRANSIT_NONE), arg.capture(), any());
         } else {
             verify(mShellTaskOrganizer).applyTransaction(arg.capture());
         }
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt
index f16beee..95e78a8 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt
@@ -26,6 +26,8 @@
 import android.os.Binder
 import android.testing.AndroidTestingRunner
 import android.view.WindowManager
+import android.view.WindowManager.TRANSIT_CHANGE
+import android.view.WindowManager.TRANSIT_NONE
 import android.view.WindowManager.TRANSIT_OPEN
 import android.view.WindowManager.TRANSIT_TO_FRONT
 import android.window.TransitionRequestInfo
@@ -55,6 +57,7 @@
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.mockito.ArgumentCaptor
+import org.mockito.ArgumentMatchers.eq
 import org.mockito.ArgumentMatchers.isNull
 import org.mockito.Mock
 import org.mockito.Mockito
@@ -141,7 +144,7 @@
 
         controller.showDesktopApps()
 
-        val wct = getLatestWct()
+        val wct = getLatestWct(expectTransition = TRANSIT_NONE)
         assertThat(wct.hierarchyOps).hasSize(3)
         // Expect order to be from bottom: home, task1, task2
         wct.assertReorderAt(index = 0, homeTask)
@@ -159,7 +162,7 @@
 
         controller.showDesktopApps()
 
-        val wct = getLatestWct()
+        val wct = getLatestWct(expectTransition = TRANSIT_NONE)
         assertThat(wct.hierarchyOps).hasSize(3)
         // Expect order to be from bottom: home, task1, task2
         wct.assertReorderAt(index = 0, homeTask)
@@ -177,7 +180,7 @@
 
         controller.showDesktopApps()
 
-        val wct = getLatestWct()
+        val wct = getLatestWct(expectTransition = TRANSIT_NONE)
         assertThat(wct.hierarchyOps).hasSize(3)
         // Expect order to be from bottom: home, task1, task2
         wct.assertReorderAt(index = 0, homeTask)
@@ -191,7 +194,7 @@
 
         controller.showDesktopApps()
 
-        val wct = getLatestWct()
+        val wct = getLatestWct(expectTransition = TRANSIT_NONE)
         assertThat(wct.hierarchyOps).hasSize(1)
         wct.assertReorderAt(index = 0, homeTask)
     }
@@ -221,7 +224,7 @@
     fun moveToDesktop() {
         val task = setUpFullscreenTask()
         controller.moveToDesktop(task)
-        val wct = getLatestWct()
+        val wct = getLatestWct(expectTransition = TRANSIT_CHANGE)
         assertThat(wct.changes[task.token.asBinder()]?.windowingMode)
             .isEqualTo(WINDOWING_MODE_FREEFORM)
     }
@@ -241,7 +244,7 @@
 
         controller.moveToDesktop(fullscreenTask)
 
-        with(getLatestWct()) {
+        with(getLatestWct(expectTransition = TRANSIT_CHANGE)) {
             assertThat(hierarchyOps).hasSize(3)
             assertReorderSequence(homeTask, freeformTask, fullscreenTask)
             assertThat(changes[fullscreenTask.token.asBinder()]?.windowingMode)
@@ -253,7 +256,7 @@
     fun moveToFullscreen() {
         val task = setUpFreeformTask()
         controller.moveToFullscreen(task)
-        val wct = getLatestWct()
+        val wct = getLatestWct(expectTransition = TRANSIT_CHANGE)
         assertThat(wct.changes[task.token.asBinder()]?.windowingMode)
             .isEqualTo(WINDOWING_MODE_FULLSCREEN)
     }
@@ -415,10 +418,12 @@
         desktopModeTaskRepository.updateVisibleFreeformTasks(task.taskId, visible = false)
     }
 
-    private fun getLatestWct(): WindowContainerTransaction {
+    private fun getLatestWct(
+        @WindowManager.TransitionType expectTransition: Int = TRANSIT_OPEN
+    ): WindowContainerTransaction {
         val arg = ArgumentCaptor.forClass(WindowContainerTransaction::class.java)
         if (ENABLE_SHELL_TRANSITIONS) {
-            verify(transitions).startTransition(anyInt(), arg.capture(), isNull())
+            verify(transitions).startTransition(eq(expectTransition), arg.capture(), isNull())
         } else {
             verify(shellTaskOrganizer).applyTransaction(arg.capture())
         }
diff --git a/libs/hwui/renderthread/DrawFrameTask.cpp b/libs/hwui/renderthread/DrawFrameTask.cpp
index 59c914f..4ceb13e 100644
--- a/libs/hwui/renderthread/DrawFrameTask.cpp
+++ b/libs/hwui/renderthread/DrawFrameTask.cpp
@@ -278,6 +278,11 @@
     mSignal.signal();
 }
 
+void DrawFrameTask::createHintSession(pid_t uiThreadId, pid_t renderThreadId) {
+    if (mHintSessionWrapper) return;
+    mHintSessionWrapper.emplace(uiThreadId, renderThreadId);
+}
+
 DrawFrameTask::HintSessionWrapper::HintSessionWrapper(int32_t uiThreadId, int32_t renderThreadId) {
     if (!Properties::useHintManager) return;
     if (uiThreadId < 0 || renderThreadId < 0) return;
diff --git a/libs/hwui/renderthread/DrawFrameTask.h b/libs/hwui/renderthread/DrawFrameTask.h
index d6fc292..b135a21 100644
--- a/libs/hwui/renderthread/DrawFrameTask.h
+++ b/libs/hwui/renderthread/DrawFrameTask.h
@@ -90,6 +90,8 @@
 
     void forceDrawNextFrame() { mForceDrawFrame = true; }
 
+    void createHintSession(pid_t uiThreadId, pid_t renderThreadId);
+
 private:
     class HintSessionWrapper {
     public:
diff --git a/libs/hwui/renderthread/RenderProxy.cpp b/libs/hwui/renderthread/RenderProxy.cpp
index a44b498..d9b650c 100644
--- a/libs/hwui/renderthread/RenderProxy.cpp
+++ b/libs/hwui/renderthread/RenderProxy.cpp
@@ -38,11 +38,18 @@
 RenderProxy::RenderProxy(bool translucent, RenderNode* rootRenderNode,
                          IContextFactory* contextFactory)
         : mRenderThread(RenderThread::getInstance()), mContext(nullptr) {
-    mContext = mRenderThread.queue().runSync([&]() -> CanvasContext* {
-        return CanvasContext::create(mRenderThread, translucent, rootRenderNode, contextFactory);
+    pid_t uiThreadId = pthread_gettid_np(pthread_self());
+    pid_t renderThreadId = getRenderThreadTid();
+    mContext = mRenderThread.queue().runSync([=, this]() -> CanvasContext* {
+        CanvasContext* context =
+                CanvasContext::create(mRenderThread, translucent, rootRenderNode, contextFactory);
+        if (context != nullptr) {
+            mRenderThread.queue().post(
+                    [=] { mDrawFrameTask.createHintSession(uiThreadId, renderThreadId); });
+        }
+        return context;
     });
-    mDrawFrameTask.setContext(&mRenderThread, mContext, rootRenderNode,
-                              pthread_gettid_np(pthread_self()), getRenderThreadTid());
+    mDrawFrameTask.setContext(&mRenderThread, mContext, rootRenderNode, uiThreadId, renderThreadId);
 }
 
 RenderProxy::~RenderProxy() {
diff --git a/packages/SystemUI/Android.bp b/packages/SystemUI/Android.bp
index 2529157..ed62c5f 100644
--- a/packages/SystemUI/Android.bp
+++ b/packages/SystemUI/Android.bp
@@ -204,6 +204,45 @@
     path: "tests/utils/src",
 }
 
+filegroup {
+    name: "SystemUI-tests-robolectric-pilots",
+    srcs: [
+        // data
+        "tests/src/com/android/systemui/keyguard/data/quickaffordance/CameraQuickAffordanceConfigTest.kt",
+        "tests/src/com/android/systemui/keyguard/data/quickaffordance/DoNotDisturbQuickAffordanceConfigTest.kt",
+        "tests/src/com/android/systemui/keyguard/data/quickaffordance/FakeKeyguardQuickAffordanceConfig.kt",
+        "tests/src/com/android/systemui/keyguard/data/quickaffordance/FlashlightQuickAffordanceConfigTest.kt",
+        "tests/src/com/android/systemui/keyguard/data/quickaffordance/HomeControlsKeyguardQuickAffordanceConfigTest.kt",
+        "tests/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceLegacySettingSyncerTest.kt",
+        "tests/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceLocalUserSelectionManagerTest.kt",
+        "tests/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceRemoteUserSelectionManagerTest.kt",
+        "tests/src/com/android/systemui/keyguard/data/quickaffordance/QrCodeScannerKeyguardQuickAffordanceConfigTest.kt",
+        "tests/src/com/android/systemui/keyguard/data/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfigTest.kt",
+        "tests/src/com/android/systemui/keyguard/data/repository/KeyguardQuickAffordanceRepositoryTest.kt",
+        "tests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt",
+        // domain
+        "tests/src/com/android/systemui/keyguard/domain/interactor/AlternateBouncerInteractorTest.kt",
+        "tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorTest.kt",
+        "tests/src/com/android/systemui/keyguard/domain/interactor/AlternateBouncerInteractorTest.kt",
+        "tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorTest.kt",
+        "tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorTest.kt",
+        "tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorTest.kt",
+        "tests/src/com/android/systemui/keyguard/domain/interactor/LightRevealScrimInteractorTest.kt",
+        "tests/src/com/android/systemui/keyguard/domain/quickaffordance/FakeKeyguardQuickAffordanceRegistry.kt",
+        "tests/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerInteractorWithCoroutinesTest.kt",
+        "tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorTest.kt",
+        "tests/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerInteractorWithCoroutinesTest.kt",
+        // ui
+        "tests/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToLockscreenTransitionViewModelTest.kt",
+        "tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBouncerViewModelTest.kt",
+        "tests/src/com/android/systemui/keyguard/ui/viewmodel/GoneToDreamingTransitionViewModelTest.kt",
+        "tests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToDreamingTransitionViewModelTest.kt",
+        "tests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToOccludedTransitionViewModelTest.kt",
+        "tests/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToLockscreenTransitionViewModelTest.kt",
+    ],
+    path: "tests/src",
+}
+
 java_library {
     name: "SystemUI-tests-concurrency",
     srcs: [
@@ -315,8 +354,16 @@
     defaults: [
         "platform_app_defaults",
         "SystemUI_app_defaults",
+        "SystemUI_compose_defaults",
     ],
     manifest: "tests/AndroidManifest-base.xml",
+
+    srcs: [
+        "src/**/*.kt",
+        "src/**/*.java",
+        "src/**/I*.aidl",
+        ":ReleaseJavaFiles",
+    ],
     static_libs: [
         "SystemUI-tests-base",
     ],
@@ -330,6 +377,9 @@
     certificate: "platform",
     privileged: true,
     resource_dirs: [],
+    kotlincflags: ["-Xjvm-default=all"],
+
+    plugins: ["dagger2-compiler"],
 }
 
 android_robolectric_test {
@@ -337,6 +387,13 @@
     srcs: [
         "tests/robolectric/src/**/*.kt",
         "tests/robolectric/src/**/*.java",
+        ":SystemUI-tests-utils",
+        ":SystemUI-tests-robolectric-pilots",
+    ],
+    static_libs: [
+        "androidx.test.uiautomator_uiautomator",
+        "androidx.test.ext.junit",
+        "inline-mockito-robolectric-prebuilt",
     ],
     libs: [
         "android.test.runner",
@@ -344,7 +401,9 @@
         "android.test.mock",
         "truth-prebuilt",
     ],
-    kotlincflags: ["-Xjvm-default=enable"],
+
+    upstream: true,
+
     instrumentation_for: "SystemUIRobo-stub",
     java_resource_dirs: ["tests/robolectric/config"],
 }
diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/ClockRegistry.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/ClockRegistry.kt
index a523cf1..ed6e619 100644
--- a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/ClockRegistry.kt
+++ b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/ClockRegistry.kt
@@ -13,11 +13,13 @@
  */
 package com.android.systemui.shared.clocks
 
+import android.app.ActivityManager
+import android.app.UserSwitchObserver
 import android.content.Context
 import android.database.ContentObserver
 import android.graphics.drawable.Drawable
 import android.net.Uri
-import android.os.Handler
+import android.os.UserHandle
 import android.provider.Settings
 import android.util.Log
 import androidx.annotation.OpenForTesting
@@ -29,17 +31,23 @@
 import com.android.systemui.plugins.ClockSettings
 import com.android.systemui.plugins.PluginListener
 import com.android.systemui.plugins.PluginManager
+import com.android.systemui.util.Assert
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.launch
 
-private val TAG = ClockRegistry::class.simpleName
+private val TAG = ClockRegistry::class.simpleName!!
 private const val DEBUG = true
 
 /** ClockRegistry aggregates providers and plugins */
 open class ClockRegistry(
     val context: Context,
     val pluginManager: PluginManager,
-    val handler: Handler,
+    val scope: CoroutineScope,
+    val mainDispatcher: CoroutineDispatcher,
+    val bgDispatcher: CoroutineDispatcher,
     val isEnabled: Boolean,
-    userHandle: Int,
+    val handleAllUsers: Boolean,
     defaultClockProvider: ClockProvider,
     val fallbackClockId: ClockId = DEFAULT_CLOCK_ID,
 ) {
@@ -50,66 +58,132 @@
 
     private val availableClocks = mutableMapOf<ClockId, ClockInfo>()
     private val clockChangeListeners = mutableListOf<ClockChangeListener>()
-    private val settingObserver = object : ContentObserver(handler) {
-        override fun onChange(selfChange: Boolean, uris: Collection<Uri>, flags: Int, userId: Int) =
-            clockChangeListeners.forEach { it.onClockChanged() }
-    }
+    private val settingObserver =
+        object : ContentObserver(null) {
+            override fun onChange(
+                selfChange: Boolean,
+                uris: Collection<Uri>,
+                flags: Int,
+                userId: Int
+            ) {
+                scope.launch(bgDispatcher) { querySettings() }
+            }
+        }
 
-    private val pluginListener = object : PluginListener<ClockProviderPlugin> {
-        override fun onPluginConnected(plugin: ClockProviderPlugin, context: Context) =
-            connectClocks(plugin)
+    private val pluginListener =
+        object : PluginListener<ClockProviderPlugin> {
+            override fun onPluginConnected(plugin: ClockProviderPlugin, context: Context) =
+                connectClocks(plugin)
 
-        override fun onPluginDisconnected(plugin: ClockProviderPlugin) =
-            disconnectClocks(plugin)
-    }
+            override fun onPluginDisconnected(plugin: ClockProviderPlugin) =
+                disconnectClocks(plugin)
+        }
 
-    open var settings: ClockSettings?
-        get() {
+    private val userSwitchObserver =
+        object : UserSwitchObserver() {
+            override fun onUserSwitchComplete(newUserId: Int) {
+                scope.launch(bgDispatcher) { querySettings() }
+            }
+        }
+
+    // TODO(b/267372164): Migrate to flows
+    var settings: ClockSettings? = null
+        get() = field
+        protected set(value) {
+            if (field != value) {
+                field = value
+                scope.launch(mainDispatcher) { onClockChanged() }
+            }
+        }
+
+    var isRegistered: Boolean = false
+        private set
+
+    @OpenForTesting
+    open fun querySettings() {
+        assertNotMainThread()
+        val result =
             try {
-                val json = Settings.Secure.getString(
-                    context.contentResolver,
-                    Settings.Secure.LOCK_SCREEN_CUSTOM_CLOCK_FACE
-                )
-                if (json == null || json.isEmpty()) {
-                    return null
-                }
-                return ClockSettings.deserialize(json)
+                val json =
+                    if (handleAllUsers) {
+                        Settings.Secure.getStringForUser(
+                            context.contentResolver,
+                            Settings.Secure.LOCK_SCREEN_CUSTOM_CLOCK_FACE,
+                            ActivityManager.getCurrentUser()
+                        )
+                    } else {
+                        Settings.Secure.getString(
+                            context.contentResolver,
+                            Settings.Secure.LOCK_SCREEN_CUSTOM_CLOCK_FACE
+                        )
+                    }
+
+                ClockSettings.deserialize(json)
             } catch (ex: Exception) {
                 Log.e(TAG, "Failed to parse clock settings", ex)
-                return null
+                null
             }
-        }
-        protected set(value) {
-            try {
-                val json = if (value != null) {
-                    value._applied_timestamp = System.currentTimeMillis()
-                    ClockSettings.serialize(value)
-                } else {
-                    ""
-                }
+        settings = result
+    }
 
+    @OpenForTesting
+    open fun applySettings(value: ClockSettings?) {
+        assertNotMainThread()
+
+        try {
+            value?._applied_timestamp = System.currentTimeMillis()
+            val json = ClockSettings.serialize(value)
+
+            if (handleAllUsers) {
+                Settings.Secure.putStringForUser(
+                    context.contentResolver,
+                    Settings.Secure.LOCK_SCREEN_CUSTOM_CLOCK_FACE,
+                    json,
+                    ActivityManager.getCurrentUser()
+                )
+            } else {
                 Settings.Secure.putString(
                     context.contentResolver,
-                    Settings.Secure.LOCK_SCREEN_CUSTOM_CLOCK_FACE, json
+                    Settings.Secure.LOCK_SCREEN_CUSTOM_CLOCK_FACE,
+                    json
                 )
-            } catch (ex: Exception) {
-                Log.e(TAG, "Failed to set clock settings", ex)
             }
+        } catch (ex: Exception) {
+            Log.e(TAG, "Failed to set clock settings", ex)
         }
+        settings = value
+    }
 
-    private fun mutateSetting(mutator: (ClockSettings) -> Unit) {
-        val settings = this.settings ?: ClockSettings()
-        mutator(settings)
-        this.settings = settings
+    @OpenForTesting
+    protected open fun assertMainThread() {
+        Assert.isMainThread()
+    }
+
+    @OpenForTesting
+    protected open fun assertNotMainThread() {
+        Assert.isNotMainThread()
+    }
+
+    private fun onClockChanged() {
+        assertMainThread()
+        clockChangeListeners.forEach { it.onClockChanged() }
+    }
+
+    private fun mutateSetting(mutator: (ClockSettings) -> ClockSettings) {
+        scope.launch(bgDispatcher) { applySettings(mutator(settings ?: ClockSettings())) }
     }
 
     var currentClockId: ClockId
         get() = settings?.clockId ?: fallbackClockId
-        set(value) { mutateSetting { it.clockId = value } }
+        set(value) {
+            mutateSetting { it.copy(clockId = value) }
+        }
 
     var seedColor: Int?
         get() = settings?.seedColor
-        set(value) { mutateSetting { it.seedColor = value } }
+        set(value) {
+            mutateSetting { it.copy(seedColor = value) }
+        }
 
     init {
         connectClocks(defaultClockProvider)
@@ -118,19 +192,51 @@
                 "$defaultClockProvider did not register clock at $DEFAULT_CLOCK_ID"
             )
         }
+    }
 
-        if (isEnabled) {
-            pluginManager.addPluginListener(
-                pluginListener,
-                ClockProviderPlugin::class.java,
-                /*allowMultiple=*/ true
-            )
+    fun registerListeners() {
+        if (!isEnabled || isRegistered) {
+            return
+        }
+
+        isRegistered = true
+
+        pluginManager.addPluginListener(
+            pluginListener,
+            ClockProviderPlugin::class.java,
+            /*allowMultiple=*/ true
+        )
+
+        scope.launch(bgDispatcher) { querySettings() }
+        if (handleAllUsers) {
             context.contentResolver.registerContentObserver(
                 Settings.Secure.getUriFor(Settings.Secure.LOCK_SCREEN_CUSTOM_CLOCK_FACE),
                 /*notifyForDescendants=*/ false,
                 settingObserver,
-                userHandle
+                UserHandle.USER_ALL
             )
+
+            ActivityManager.getService().registerUserSwitchObserver(userSwitchObserver, TAG)
+        } else {
+            context.contentResolver.registerContentObserver(
+                Settings.Secure.getUriFor(Settings.Secure.LOCK_SCREEN_CUSTOM_CLOCK_FACE),
+                /*notifyForDescendants=*/ false,
+                settingObserver
+            )
+        }
+    }
+
+    fun unregisterListeners() {
+        if (!isRegistered) {
+            return
+        }
+
+        isRegistered = false
+
+        pluginManager.removePluginListener(pluginListener)
+        context.contentResolver.unregisterContentObserver(settingObserver)
+        if (handleAllUsers) {
+            ActivityManager.getService().unregisterUserSwitchObserver(userSwitchObserver)
         }
     }
 
@@ -157,7 +263,7 @@
                 if (DEBUG) {
                     Log.i(TAG, "Current clock ($currentId) was connected")
                 }
-                clockChangeListeners.forEach { it.onClockChanged() }
+                onClockChanged()
             }
         }
     }
@@ -172,13 +278,12 @@
 
             if (currentId == clock.clockId) {
                 Log.w(TAG, "Current clock ($currentId) was disconnected")
-                clockChangeListeners.forEach { it.onClockChanged() }
+                onClockChanged()
             }
         }
     }
 
-    @OpenForTesting
-    open fun getClocks(): List<ClockMetadata> {
+    fun getClocks(): List<ClockMetadata> {
         if (!isEnabled) {
             return listOf(availableClocks[DEFAULT_CLOCK_ID]!!.metadata)
         }
@@ -213,16 +318,16 @@
         return createClock(DEFAULT_CLOCK_ID)!!
     }
 
-    private fun createClock(clockId: ClockId): ClockController? {
-        val settings = this.settings ?: ClockSettings()
-        if (clockId != settings.clockId) {
-            settings.clockId = clockId
+    private fun createClock(targetClockId: ClockId): ClockController? {
+        var settings = this.settings ?: ClockSettings()
+        if (targetClockId != settings.clockId) {
+            settings = settings.copy(clockId = targetClockId)
         }
-        return availableClocks[clockId]?.provider?.createClock(settings)
+        return availableClocks[targetClockId]?.provider?.createClock(settings)
     }
 
     private data class ClockInfo(
         val metadata: ClockMetadata,
-        val provider: ClockProvider
+        val provider: ClockProvider,
     )
 }
diff --git a/packages/SystemUI/src/com/android/systemui/util/Assert.java b/packages/SystemUI/customization/src/com/android/systemui/util/Assert.java
similarity index 100%
rename from packages/SystemUI/src/com/android/systemui/util/Assert.java
rename to packages/SystemUI/customization/src/com/android/systemui/util/Assert.java
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 590015d..1c2f38b 100644
--- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/ClockProviderPlugin.kt
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/ClockProviderPlugin.kt
@@ -45,7 +45,7 @@
     /** Initializes and returns the target clock design */
     @Deprecated("Use overload with ClockSettings")
     fun createClock(id: ClockId): ClockController {
-        return createClock(ClockSettings(id, null, null))
+        return createClock(ClockSettings(id, null))
     }
 
     /** Initializes and returns the target clock design */
@@ -186,16 +186,21 @@
 /** Structure for keeping clock-specific settings */
 @Keep
 data class ClockSettings(
-    var clockId: ClockId? = null,
-    var seedColor: Int? = null,
-    var _applied_timestamp: Long? = null,
+    val clockId: ClockId? = null,
+    val seedColor: Int? = null,
 ) {
+    var _applied_timestamp: Long? = null
+
     companion object {
         private val KEY_CLOCK_ID = "clockId"
         private val KEY_SEED_COLOR = "seedColor"
         private val KEY_TIMESTAMP = "_applied_timestamp"
 
-        fun serialize(setting: ClockSettings): String {
+        fun serialize(setting: ClockSettings?): String {
+            if (setting == null) {
+                return ""
+            }
+
             return JSONObject()
                 .put(KEY_CLOCK_ID, setting.clockId)
                 .put(KEY_SEED_COLOR, setting.seedColor)
@@ -203,13 +208,21 @@
                 .toString()
         }
 
-        fun deserialize(jsonStr: String): ClockSettings {
+        fun deserialize(jsonStr: String?): ClockSettings? {
+            if (jsonStr.isNullOrEmpty()) {
+                return null
+            }
+
             val json = JSONObject(jsonStr)
-            return ClockSettings(
-                json.getString(KEY_CLOCK_ID),
-                if (!json.isNull(KEY_SEED_COLOR)) json.getInt(KEY_SEED_COLOR) else null,
-                if (!json.isNull(KEY_TIMESTAMP)) json.getLong(KEY_TIMESTAMP) else null
-            )
+            val result =
+                ClockSettings(
+                    json.getString(KEY_CLOCK_ID),
+                    if (!json.isNull(KEY_SEED_COLOR)) json.getInt(KEY_SEED_COLOR) else null
+                )
+            if (!json.isNull(KEY_TIMESTAMP)) {
+                result._applied_timestamp = json.getLong(KEY_TIMESTAMP)
+            }
+            return result
         }
     }
 }
diff --git a/packages/SystemUI/res/xml/media_session_collapsed.xml b/packages/SystemUI/res/xml/media_session_collapsed.xml
index d9c81af..5129fc0 100644
--- a/packages/SystemUI/res/xml/media_session_collapsed.xml
+++ b/packages/SystemUI/res/xml/media_session_collapsed.xml
@@ -73,11 +73,12 @@
         android:layout_height="wrap_content"
         android:layout_marginEnd="@dimen/qs_media_info_spacing"
         android:layout_marginBottom="@dimen/qs_media_padding"
-        android:layout_marginTop="0dp"
+        android:layout_marginTop="@dimen/qs_media_icon_offset"
         app:layout_constraintStart_toStartOf="@id/header_title"
         app:layout_constraintEnd_toStartOf="@id/header_artist"
         app:layout_constraintTop_toTopOf="@id/header_artist"
-        app:layout_constraintBottom_toTopOf="@id/media_action_barrier_top"
+        app:layout_constraintBottom_toBottomOf="@id/header_artist"
+        app:layout_constraintVertical_bias="0"
         app:layout_constraintHorizontal_bias="0"
         app:layout_constraintHorizontal_chainStyle="packed" />
 
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java
index 4acbb0a..0326b6d 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java
@@ -209,7 +209,6 @@
 
         if (!animate) {
             out.setAlpha(0f);
-            out.setVisibility(INVISIBLE);
             in.setAlpha(1f);
             in.setVisibility(VISIBLE);
             mStatusArea.setTranslationY(statusAreaYTranslation);
@@ -225,10 +224,7 @@
                         direction * -mClockSwitchYAmount));
         mClockOutAnim.addListener(new AnimatorListenerAdapter() {
             public void onAnimationEnd(Animator animation) {
-                if (mClockOutAnim == animation) {
-                    out.setVisibility(INVISIBLE);
-                    mClockOutAnim = null;
-                }
+                mClockOutAnim = null;
             }
         });
 
@@ -242,9 +238,7 @@
         mClockInAnim.setStartDelay(CLOCK_OUT_MILLIS / 2);
         mClockInAnim.addListener(new AnimatorListenerAdapter() {
             public void onAnimationEnd(Animator animation) {
-                if (mClockInAnim == animation) {
-                    mClockInAnim = null;
-                }
+                mClockInAnim = null;
             }
         });
 
@@ -257,9 +251,7 @@
         mStatusAreaAnim.setInterpolator(Interpolators.FAST_OUT_SLOW_IN);
         mStatusAreaAnim.addListener(new AnimatorListenerAdapter() {
             public void onAnimationEnd(Animator animation) {
-                if (mStatusAreaAnim == animation) {
-                    mStatusAreaAnim = null;
-                }
+                mStatusAreaAnim = null;
             }
         });
         mStatusAreaAnim.start();
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java
index 879a95c..baaeb2a 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java
@@ -404,6 +404,10 @@
             int clockHeight = clock.getLargeClock().getView().getHeight();
             return frameHeight / 2 + clockHeight / 2 + mKeyguardLargeClockTopMargin / -2;
         } else {
+            // This is only called if we've never shown the large clock as the frame is inflated
+            // with 'gone', but then the visibility is never set when it is animated away by
+            // KeyguardClockSwitch, instead it is removed from the view hierarchy.
+            // TODO(b/261755021): Cleanup Large Frame Visibility
             int clockHeight = clock.getSmallClock().getView().getHeight();
             return clockHeight + statusBarHeaderHeight + mKeyguardSmallClockTopMargin;
         }
@@ -421,11 +425,15 @@
         if (mLargeClockFrame.getVisibility() == View.VISIBLE) {
             return clock.getLargeClock().getView().getHeight();
         } else {
+            // Is not called except in certain edge cases, see comment in getClockBottom
+            // TODO(b/261755021): Cleanup Large Frame Visibility
             return clock.getSmallClock().getView().getHeight();
         }
     }
 
     boolean isClockTopAligned() {
+        // Returns false except certain edge cases, see comment in getClockBottom
+        // TODO(b/261755021): Cleanup Large Frame Visibility
         return mLargeClockFrame.getVisibility() != View.VISIBLE;
     }
 
diff --git a/packages/SystemUI/src/com/android/keyguard/dagger/ClockRegistryModule.java b/packages/SystemUI/src/com/android/keyguard/dagger/ClockRegistryModule.java
index 676979c..b1a83fb 100644
--- a/packages/SystemUI/src/com/android/keyguard/dagger/ClockRegistryModule.java
+++ b/packages/SystemUI/src/com/android/keyguard/dagger/ClockRegistryModule.java
@@ -18,13 +18,12 @@
 
 import android.content.Context;
 import android.content.res.Resources;
-import android.os.Handler;
-import android.os.UserHandle;
 import android.view.LayoutInflater;
 
 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;
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.flags.FeatureFlags;
 import com.android.systemui.flags.Flags;
@@ -34,6 +33,8 @@
 
 import dagger.Module;
 import dagger.Provides;
+import kotlinx.coroutines.CoroutineDispatcher;
+import kotlinx.coroutines.CoroutineScope;
 
 /** Dagger Module for clocks. */
 @Module
@@ -44,17 +45,23 @@
     public static ClockRegistry getClockRegistry(
             @Application Context context,
             PluginManager pluginManager,
-            @Main Handler handler,
+            @Application CoroutineScope scope,
+            @Main CoroutineDispatcher mainDispatcher,
+            @Background CoroutineDispatcher bgDispatcher,
             FeatureFlags featureFlags,
             @Main Resources resources,
             LayoutInflater layoutInflater) {
-        return new ClockRegistry(
+        ClockRegistry registry = new ClockRegistry(
                 context,
                 pluginManager,
-                handler,
+                scope,
+                mainDispatcher,
+                bgDispatcher,
                 featureFlags.isEnabled(Flags.LOCKSCREEN_CUSTOM_CLOCKS),
-                UserHandle.USER_ALL,
+                /* handleAllUsers= */ true,
                 new DefaultClockProvider(context, layoutInflater, resources),
                 context.getString(R.string.lockscreen_clock_id_fallback));
+        registry.registerListeners();
+        return registry;
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFingerprintIconController.kt b/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFingerprintIconController.kt
index 1f6f6d9..3ea3cd1 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFingerprintIconController.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFingerprintIconController.kt
@@ -18,6 +18,7 @@
 
 import android.annotation.RawRes
 import android.content.Context
+import android.content.Context.FINGERPRINT_SERVICE
 import android.content.res.Configuration
 import android.hardware.fingerprint.FingerprintManager
 import android.view.DisplayInfo
@@ -66,16 +67,11 @@
                 R.dimen.biometric_dialog_fingerprint_icon_width),
                 context.resources.getDimensionPixelSize(
                         R.dimen.biometric_dialog_fingerprint_icon_height))
-        var sideFps = false
-        (context.getSystemService(Context.FINGERPRINT_SERVICE)
-                as FingerprintManager?)?.let { fpm ->
-            for (prop in fpm.sensorPropertiesInternal) {
-                if (prop.isAnySidefpsType) {
-                    sideFps = true
-                }
-            }
-        }
-        isSideFps = sideFps
+        isSideFps =
+            (context.getSystemService(FINGERPRINT_SERVICE) as FingerprintManager?)?.let { fpm ->
+                fpm.sensorPropertiesInternal.any { it.isAnySidefpsType }
+            } ?: false
+        preloadAssets(context)
         val displayInfo = DisplayInfo()
         context.display?.getDisplayInfo(displayInfo)
         if (isSideFps && getRotationFromDefault(displayInfo.rotation) == Surface.ROTATION_180) {
@@ -329,6 +325,40 @@
         else -> null
     }
 
+    private fun preloadAssets(context: Context) {
+        if (isSideFps) {
+            cacheLottieAssetsInContext(
+                context,
+                R.raw.biometricprompt_fingerprint_to_error_landscape,
+                R.raw.biometricprompt_folded_base_bottomright,
+                R.raw.biometricprompt_folded_base_default,
+                R.raw.biometricprompt_folded_base_topleft,
+                R.raw.biometricprompt_landscape_base,
+                R.raw.biometricprompt_portrait_base_bottomright,
+                R.raw.biometricprompt_portrait_base_topleft,
+                R.raw.biometricprompt_symbol_error_to_fingerprint_landscape,
+                R.raw.biometricprompt_symbol_error_to_fingerprint_portrait_bottomright,
+                R.raw.biometricprompt_symbol_error_to_fingerprint_portrait_topleft,
+                R.raw.biometricprompt_symbol_error_to_success_landscape,
+                R.raw.biometricprompt_symbol_error_to_success_portrait_bottomright,
+                R.raw.biometricprompt_symbol_error_to_success_portrait_topleft,
+                R.raw.biometricprompt_symbol_fingerprint_to_error_portrait_bottomright,
+                R.raw.biometricprompt_symbol_fingerprint_to_error_portrait_topleft,
+                R.raw.biometricprompt_symbol_fingerprint_to_success_landscape,
+                R.raw.biometricprompt_symbol_fingerprint_to_success_portrait_bottomright,
+                R.raw.biometricprompt_symbol_fingerprint_to_success_portrait_topleft
+            )
+        } else {
+            cacheLottieAssetsInContext(
+                context,
+                R.raw.fingerprint_dialogue_error_to_fingerprint_lottie,
+                R.raw.fingerprint_dialogue_error_to_success_lottie,
+                R.raw.fingerprint_dialogue_fingerprint_to_error_lottie,
+                R.raw.fingerprint_dialogue_fingerprint_to_success_lottie
+            )
+        }
+    }
+
     override fun onFoldUpdated(isFolded: Boolean) {
         isDeviceFolded = isFolded
     }
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricIconController.kt b/packages/SystemUI/src/com/android/systemui/biometrics/AuthIconController.kt
similarity index 86%
rename from packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricIconController.kt
rename to packages/SystemUI/src/com/android/systemui/biometrics/AuthIconController.kt
index b3b6fa2..d6ad4da 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricIconController.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthIconController.kt
@@ -24,14 +24,15 @@
 import android.graphics.drawable.Drawable
 import android.util.Log
 import com.airbnb.lottie.LottieAnimationView
+import com.airbnb.lottie.LottieCompositionFactory
 import com.android.systemui.biometrics.AuthBiometricView.BiometricState
 
 private const val TAG = "AuthIconController"
 
 /** Controller for animating the BiometricPrompt icon/affordance. */
 abstract class AuthIconController(
-        protected val context: Context,
-        protected val iconView: LottieAnimationView
+    protected val context: Context,
+    protected val iconView: LottieAnimationView
 ) : Animatable2.AnimationCallback() {
 
     /** If this controller should ignore events and pause. */
@@ -94,4 +95,12 @@
     open fun handleAnimationEnd(drawable: Drawable) {}
 
     open fun onConfigurationChanged(newConfig: Configuration) {}
+
+    // TODO(b/251476085): Migrate this to an extension at the appropriate level?
+    /** Load the given [rawResources] immediately so they are cached for use in the [context]. */
+    protected fun cacheLottieAssetsInContext(context: Context, vararg rawResources: Int) {
+        for (res in rawResources) {
+            LottieCompositionFactory.fromRawRes(context, res)
+        }
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemPropertiesFlagsModule.kt b/packages/SystemUI/src/com/android/systemui/dagger/SystemPropertiesFlagsModule.kt
new file mode 100644
index 0000000..c6f833b
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemPropertiesFlagsModule.kt
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.dagger
+
+import com.android.internal.config.sysui.SystemUiSystemPropertiesFlags
+import dagger.Module
+import dagger.Provides
+
+/** A module which provides access to the default [SystemUiSystemPropertiesFlags.FlagResolver] */
+@Module
+object SystemPropertiesFlagsModule {
+    /** provide the default FlagResolver. */
+    @Provides
+    fun provideFlagResolver(): SystemUiSystemPropertiesFlags.FlagResolver =
+        SystemUiSystemPropertiesFlags.getResolver()
+}
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
index 6274a26..c3ee9be 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
@@ -139,6 +139,7 @@
             DemoModeModule.class,
             FalsingModule.class,
             FlagsModule.class,
+            SystemPropertiesFlagsModule.class,
             FooterActionsModule.class,
             LogModule.class,
             MediaProjectionModule.class,
diff --git a/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsDebug.java b/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsDebug.java
index 4bac697..58fe094 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsDebug.java
+++ b/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsDebug.java
@@ -40,7 +40,6 @@
 import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.util.settings.GlobalSettings;
-import com.android.systemui.util.settings.SecureSettings;
 
 import org.jetbrains.annotations.NotNull;
 
@@ -74,7 +73,6 @@
     private final FlagManager mFlagManager;
     private final Context mContext;
     private final GlobalSettings mGlobalSettings;
-    private final SecureSettings mSecureSettings;
     private final Resources mResources;
     private final SystemPropertiesHelper mSystemProperties;
     private final ServerFlagReader mServerFlagReader;
@@ -87,8 +85,9 @@
     private final ServerFlagReader.ChangeListener mOnPropertiesChanged =
             new ServerFlagReader.ChangeListener() {
                 @Override
-                public void onChange() {
-                    mRestarter.restartSystemUI();
+                public void onChange(Flag<?> flag) {
+                    mRestarter.restartSystemUI(
+                            "Server flag change: " + flag.getNamespace() + "." + flag.getName());
                 }
             };
 
@@ -97,7 +96,6 @@
             FlagManager flagManager,
             Context context,
             GlobalSettings globalSettings,
-            SecureSettings secureSettings,
             SystemPropertiesHelper systemProperties,
             @Main Resources resources,
             ServerFlagReader serverFlagReader,
@@ -106,7 +104,6 @@
         mFlagManager = flagManager;
         mContext = context;
         mGlobalSettings = globalSettings;
-        mSecureSettings = secureSettings;
         mResources = resources;
         mSystemProperties = systemProperties;
         mServerFlagReader = serverFlagReader;
@@ -119,7 +116,8 @@
         IntentFilter filter = new IntentFilter();
         filter.addAction(ACTION_SET_FLAG);
         filter.addAction(ACTION_GET_FLAGS);
-        mFlagManager.setOnSettingsChangedAction(this::restartSystemUI);
+        mFlagManager.setOnSettingsChangedAction(
+                suppressRestart -> restartSystemUI(suppressRestart, "Settings changed"));
         mFlagManager.setClearCacheAction(this::removeFromCache);
         mContext.registerReceiver(mReceiver, filter, null, null,
                 Context.RECEIVER_EXPORTED_UNAUDITED);
@@ -233,6 +231,10 @@
         Boolean result = readBooleanFlagOverride(flag.getName());
         if (result == null) {
             result = readBooleanFlagOverride(flag.getId());
+            if (result != null) {
+                // Move overrides from id to name
+                setFlagValueInternal(flag.getName(), result, BooleanFlagSerializer.INSTANCE);
+            }
         }
         boolean hasServerOverride = mServerFlagReader.hasOverride(
                 flag.getNamespace(), flag.getName());
@@ -305,25 +307,38 @@
         requireNonNull(value, "Cannot set a null value");
         T currentValue = readFlagValueInternal(name, serializer);
         if (Objects.equals(currentValue, value)) {
-            Log.i(TAG, "Flag id " + name + " is already " + value);
+            Log.i(TAG, "Flag \"" + name + "\" is already " + value);
             return;
         }
+        setFlagValueInternal(name, value, serializer);
+        Log.i(TAG, "Set flag \"" + name + "\" to " + value);
+        removeFromCache(name);
+        mFlagManager.dispatchListenersAndMaybeRestart(
+                name,
+                suppressRestart -> restartSystemUI(
+                        suppressRestart, "Flag \"" + name + "\" changed to " + value));
+    }
+
+    private <T> void setFlagValueInternal(
+            String name, @NonNull T value, FlagSerializer<T> serializer) {
         final String data = serializer.toSettingsData(value);
         if (data == null) {
-            Log.w(TAG, "Failed to set id " + name + " to " + value);
+            Log.w(TAG, "Failed to set flag " + name + " to " + value);
             return;
         }
         mGlobalSettings.putStringForUser(mFlagManager.nameToSettingsKey(name), data,
                 UserHandle.USER_CURRENT);
-        Log.i(TAG, "Set id " + name + " to " + value);
-        removeFromCache(name);
-        mFlagManager.dispatchListenersAndMaybeRestart(name, this::restartSystemUI);
     }
 
     <T> void eraseFlag(Flag<T> flag) {
         if (flag instanceof SysPropFlag) {
-            mSystemProperties.erase(((SysPropFlag<T>) flag).getName());
-            dispatchListenersAndMaybeRestart(flag.getName(), this::restartAndroid);
+            mSystemProperties.erase(flag.getName());
+            dispatchListenersAndMaybeRestart(
+                    flag.getName(),
+                    suppressRestart -> restartSystemUI(
+                            suppressRestart,
+                            "SysProp Flag \"" + flag.getNamespace() + "."
+                                    + flag.getName() + "\" reset to default."));
         } else {
             eraseFlag(flag.getName());
         }
@@ -333,7 +348,10 @@
     private void eraseFlag(String name) {
         eraseInternal(name);
         removeFromCache(name);
-        dispatchListenersAndMaybeRestart(name, this::restartSystemUI);
+        dispatchListenersAndMaybeRestart(
+                name,
+                suppressRestart -> restartSystemUI(
+                        suppressRestart, "Flag \"" + name + "\" reset to default"));
     }
 
     private void dispatchListenersAndMaybeRestart(String name, Consumer<Boolean> restartAction) {
@@ -367,20 +385,20 @@
         mFlagManager.removeListener(listener);
     }
 
-    private void restartSystemUI(boolean requestSuppress) {
+    private void restartSystemUI(boolean requestSuppress, String reason) {
         if (requestSuppress) {
             Log.i(TAG, "SystemUI Restart Suppressed");
             return;
         }
-        mRestarter.restartSystemUI();
+        mRestarter.restartSystemUI(reason);
     }
 
-    private void restartAndroid(boolean requestSuppress) {
+    private void restartAndroid(boolean requestSuppress, String reason) {
         if (requestSuppress) {
             Log.i(TAG, "Android Restart Suppressed");
             return;
         }
-        mRestarter.restartAndroid();
+        mRestarter.restartAndroid(reason);
     }
 
     void setBooleanFlagInternal(Flag<?> flag, boolean value) {
@@ -391,8 +409,11 @@
         } else if (flag instanceof SysPropBooleanFlag) {
             // Store SysProp flags in SystemProperties where they can read by outside parties.
             mSystemProperties.setBoolean(((SysPropBooleanFlag) flag).getName(), value);
-            dispatchListenersAndMaybeRestart(flag.getName(),
-                    FeatureFlagsDebug.this::restartAndroid);
+            dispatchListenersAndMaybeRestart(
+                    flag.getName(),
+                    suppressRestart -> restartSystemUI(
+                            suppressRestart,
+                            "Flag \"" + flag.getName() + "\" changed to " + value));
         } else {
             throw new IllegalArgumentException("Unknown flag type");
         }
diff --git a/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsDebugRestarter.kt b/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsDebugRestarter.kt
index 069e612..a6956a4 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsDebugRestarter.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsDebugRestarter.kt
@@ -29,6 +29,7 @@
 ) : Restarter {
 
     private var androidRestartRequested = false
+    private var pendingReason = ""
 
     val observer =
         object : WakefulnessLifecycle.Observer {
@@ -38,18 +39,20 @@
             }
         }
 
-    override fun restartSystemUI() {
+    override fun restartSystemUI(reason: String) {
         Log.d(FeatureFlagsDebug.TAG, "SystemUI Restart requested. Restarting on next screen off.")
-        scheduleRestart()
+        Log.i(FeatureFlagsDebug.TAG, reason)
+        scheduleRestart(reason)
     }
 
-    override fun restartAndroid() {
+    override fun restartAndroid(reason: String) {
         Log.d(FeatureFlagsDebug.TAG, "Android Restart requested. Restarting on next screen off.")
         androidRestartRequested = true
-        scheduleRestart()
+        scheduleRestart(reason)
     }
 
-    fun scheduleRestart() {
+    fun scheduleRestart(reason: String) {
+        pendingReason = reason
         if (wakefulnessLifecycle.wakefulness == WakefulnessLifecycle.WAKEFULNESS_ASLEEP) {
             restartNow()
         } else {
@@ -59,9 +62,9 @@
 
     private fun restartNow() {
         if (androidRestartRequested) {
-            systemExitRestarter.restartAndroid()
+            systemExitRestarter.restartAndroid(pendingReason)
         } else {
-            systemExitRestarter.restartSystemUI()
+            systemExitRestarter.restartSystemUI(pendingReason)
         }
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsRelease.java b/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsRelease.java
index 7e14237..9859ff6 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsRelease.java
+++ b/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsRelease.java
@@ -57,8 +57,9 @@
     private final ServerFlagReader.ChangeListener mOnPropertiesChanged =
             new ServerFlagReader.ChangeListener() {
                 @Override
-                public void onChange() {
-                    mRestarter.restartSystemUI();
+                public void onChange(Flag<?> flag) {
+                    mRestarter.restartSystemUI(
+                            "Server flag change: " + flag.getNamespace() + "." + flag.getName());
                 }
             };
 
diff --git a/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsReleaseRestarter.kt b/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsReleaseRestarter.kt
index 7ff3876..c08266c 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsReleaseRestarter.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsReleaseRestarter.kt
@@ -36,41 +36,43 @@
 ) : Restarter {
     var listenersAdded = false
     var pendingRestart: Runnable? = null
+    private var pendingReason = ""
     var androidRestartRequested = false
 
     val observer =
         object : WakefulnessLifecycle.Observer {
             override fun onFinishedGoingToSleep() {
-                scheduleRestart()
+                scheduleRestart(pendingReason)
             }
         }
 
     val batteryCallback =
         object : BatteryController.BatteryStateChangeCallback {
             override fun onBatteryLevelChanged(level: Int, pluggedIn: Boolean, charging: Boolean) {
-                scheduleRestart()
+                scheduleRestart(pendingReason)
             }
         }
 
-    override fun restartSystemUI() {
+    override fun restartSystemUI(reason: String) {
         Log.d(
             FeatureFlagsDebug.TAG,
             "SystemUI Restart requested. Restarting when plugged in and idle."
         )
-        scheduleRestart()
+        scheduleRestart(reason)
     }
 
-    override fun restartAndroid() {
+    override fun restartAndroid(reason: String) {
         Log.d(
             FeatureFlagsDebug.TAG,
             "Android Restart requested. Restarting when plugged in and idle."
         )
         androidRestartRequested = true
-        scheduleRestart()
+        scheduleRestart(reason)
     }
 
-    private fun scheduleRestart() {
+    private fun scheduleRestart(reason: String) {
         // Don't bother adding listeners twice.
+        pendingReason = reason
         if (!listenersAdded) {
             listenersAdded = true
             wakefulnessLifecycle.addObserver(observer)
@@ -91,9 +93,9 @@
     private fun restartNow() {
         Log.d(FeatureFlagsRelease.TAG, "Restarting due to systemui flag change")
         if (androidRestartRequested) {
-            systemExitRestarter.restartAndroid()
+            systemExitRestarter.restartAndroid(pendingReason)
         } else {
-            systemExitRestarter.restartSystemUI()
+            systemExitRestarter.restartSystemUI(pendingReason)
         }
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/flags/Restarter.kt b/packages/SystemUI/src/com/android/systemui/flags/Restarter.kt
index ce8b821..9c67795 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/Restarter.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/Restarter.kt
@@ -16,7 +16,7 @@
 package com.android.systemui.flags
 
 interface Restarter {
-    fun restartSystemUI()
+    fun restartSystemUI(reason: String)
 
-    fun restartAndroid()
+    fun restartAndroid(reason: String)
 }
diff --git a/packages/SystemUI/src/com/android/systemui/flags/ServerFlagReader.kt b/packages/SystemUI/src/com/android/systemui/flags/ServerFlagReader.kt
index a02b795..e225b10 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/ServerFlagReader.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/ServerFlagReader.kt
@@ -17,8 +17,10 @@
 package com.android.systemui.flags
 
 import android.provider.DeviceConfig
+import android.util.Log
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.dagger.qualifiers.TestHarness
 import com.android.systemui.util.DeviceConfigProxy
 import dagger.Module
 import dagger.Provides
@@ -35,21 +37,27 @@
     fun listenForChanges(values: Collection<Flag<*>>, listener: ChangeListener)
 
     interface ChangeListener {
-        fun onChange()
+        fun onChange(flag: Flag<*>)
     }
 }
 
 class ServerFlagReaderImpl @Inject constructor(
     private val namespace: String,
     private val deviceConfig: DeviceConfigProxy,
-    @Background private val executor: Executor
+    @Background private val executor: Executor,
+    @TestHarness private val isTestHarness: Boolean
 ) : ServerFlagReader {
 
+    private val TAG = "ServerFlagReader"
+
     private val listeners =
         mutableListOf<Pair<ServerFlagReader.ChangeListener, Collection<Flag<*>>>>()
 
     private val onPropertiesChangedListener = object : DeviceConfig.OnPropertiesChangedListener {
         override fun onPropertiesChanged(properties: DeviceConfig.Properties) {
+            if (isTestHarness) {
+                Log.w(TAG, "Ignore server flag changes in Test Harness mode.")
+            }
             if (properties.namespace != namespace) {
                 return
             }
@@ -59,7 +67,7 @@
                 propLoop@ for (propName in properties.keyset) {
                     for (flag in flags) {
                         if (propName == getServerOverrideName(flag.id) || propName == flag.name) {
-                            listener.onChange()
+                            listener.onChange(flag)
                             break@propLoop
                         }
                     }
@@ -111,10 +119,11 @@
         @SysUISingleton
         fun bindsReader(
             deviceConfig: DeviceConfigProxy,
-            @Background executor: Executor
+            @Background executor: Executor,
+            @TestHarness isTestHarness: Boolean
         ): ServerFlagReader {
             return ServerFlagReaderImpl(
-                SYSUI_NAMESPACE, deviceConfig, executor
+                SYSUI_NAMESPACE, deviceConfig, executor, isTestHarness
             )
         }
     }
@@ -139,7 +148,7 @@
         for ((listener, flags) in listeners) {
             flagLoop@ for (flag in flags) {
                 if (name == flag.name) {
-                    listener.onChange()
+                    listener.onChange(flag)
                     break@flagLoop
                 }
             }
diff --git a/packages/SystemUI/src/com/android/systemui/flags/SystemExitRestarter.kt b/packages/SystemUI/src/com/android/systemui/flags/SystemExitRestarter.kt
index 89daa64..46e28a7 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/SystemExitRestarter.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/SystemExitRestarter.kt
@@ -16,6 +16,7 @@
 
 package com.android.systemui.flags
 
+import android.util.Log
 import com.android.internal.statusbar.IStatusBarService
 import javax.inject.Inject
 
@@ -24,11 +25,13 @@
 constructor(
     private val barService: IStatusBarService,
 ) : Restarter {
-    override fun restartAndroid() {
+    override fun restartAndroid(reason: String) {
+        Log.d(FeatureFlagsDebug.TAG, "Restarting Android: " + reason)
         barService.restart()
     }
 
-    override fun restartSystemUI() {
+    override fun restartSystemUI(reason: String) {
+        Log.d(FeatureFlagsDebug.TAG, "Restarting SystemUI: " + reason)
         System.exit(0)
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaViewController.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaViewController.kt
index 1e6002c..b9b0459 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaViewController.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaViewController.kt
@@ -311,16 +311,15 @@
         }
 
         // media player
-        val controlsTop =
-            calculateWidgetGroupAlphaForSquishiness(
-                controlIds,
-                squishedViewState.measureHeight.toFloat(),
-                squishedViewState,
-                squishFraction
-            )
+        calculateWidgetGroupAlphaForSquishiness(
+            controlIds,
+            squishedViewState.measureHeight.toFloat(),
+            squishedViewState,
+            squishFraction
+        )
         calculateWidgetGroupAlphaForSquishiness(
             detailIds,
-            controlsTop,
+            squishedViewState.measureHeight.toFloat(),
             squishedViewState,
             squishFraction
         )
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/util/MediaDataUtils.java b/packages/SystemUI/src/com/android/systemui/media/controls/util/MediaDataUtils.java
index 85282a1..e95106e 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/util/MediaDataUtils.java
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/util/MediaDataUtils.java
@@ -16,6 +16,7 @@
 
 package com.android.systemui.media.controls.util;
 
+import android.annotation.Nullable;
 import android.content.Context;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageManager;
@@ -61,8 +62,9 @@
      * @param extras
      * @return the progress value between 0-1 inclusive if prsent, otherwise null
      */
-    public static Double getDescriptionProgress(Bundle extras) {
-        if (!extras.containsKey(MediaConstants.DESCRIPTION_EXTRAS_KEY_COMPLETION_STATUS)) {
+    public static Double getDescriptionProgress(@Nullable Bundle extras) {
+        if (extras == null
+                || !extras.containsKey(MediaConstants.DESCRIPTION_EXTRAS_KEY_COMPLETION_STATUS)) {
             return null;
         }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java
index 56c34a0..8f1e0a1 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java
@@ -23,6 +23,7 @@
 import android.content.res.Resources;
 import android.graphics.Rect;
 import android.util.AttributeSet;
+import android.util.IndentingPrintWriter;
 import android.util.MathUtils;
 import android.view.View;
 import android.view.ViewGroup;
@@ -52,6 +53,9 @@
 import com.android.systemui.statusbar.notification.stack.StackScrollAlgorithm;
 import com.android.systemui.statusbar.notification.stack.ViewState;
 import com.android.systemui.statusbar.phone.NotificationIconContainer;
+import com.android.systemui.util.DumpUtilsKt;
+
+import java.io.PrintWriter;
 
 /**
  * A notification shelf view that is placed inside the notification scroller. It manages the
@@ -86,7 +90,6 @@
     private boolean mInteractive;
     private boolean mAnimationsEnabled = true;
     private boolean mShowNotificationShelf;
-    private float mFirstElementRoundness;
     private Rect mClipRect = new Rect();
     private int mIndexOfFirstViewInShelf = -1;
     private float mCornerAnimationDistance;
@@ -263,8 +266,7 @@
         final float actualWidth = mAmbientState.isOnKeyguard()
                 ? MathUtils.lerp(shortestWidth, getWidth(), fractionToShade)
                 : getWidth();
-        ActivatableNotificationView anv = (ActivatableNotificationView) this;
-        anv.setBackgroundWidth((int) actualWidth);
+        setBackgroundWidth((int) actualWidth);
         if (mShelfIcons != null) {
             mShelfIcons.setActualLayoutWidth((int) actualWidth);
         }
@@ -365,9 +367,7 @@
         boolean expandingAnimated = mAmbientState.isExpansionChanging()
                 && !mAmbientState.isPanelTracking();
         int baseZHeight = mAmbientState.getBaseZHeight();
-        int backgroundTop = 0;
         int clipTopAmount = 0;
-        float firstElementRoundness = 0.0f;
 
         for (int i = 0; i < mHostLayoutController.getChildCount(); i++) {
             ExpandableView child = mHostLayoutController.getChildAt(i);
@@ -420,18 +420,6 @@
                 if (notGoneIndex != 0 || !aboveShelf) {
                     expandableRow.setAboveShelf(false);
                 }
-                if (notGoneIndex == 0) {
-                    StatusBarIconView icon = expandableRow.getEntry().getIcons().getShelfIcon();
-                    NotificationIconContainer.IconState iconState = getIconState(icon);
-                    // The icon state might be null in rare cases where the notification is actually
-                    // added to the layout, but not to the shelf. An example are replied messages,
-                    // since they don't show up on AOD
-                    if (iconState != null && iconState.clampedAppearAmount == 1.0f) {
-                        // only if the first icon is fully in the shelf we want to clip to it!
-                        backgroundTop = (int) (child.getTranslationY() - getTranslationY());
-                        firstElementRoundness = expandableRow.getTopRoundness();
-                    }
-                }
 
                 previousColor = ownColorUntinted;
                 notGoneIndex++;
@@ -467,8 +455,6 @@
 
         // TODO(b/172289889) transition last icon in shelf to notification icon and vice versa.
         setVisibility(isHidden ? View.INVISIBLE : View.VISIBLE);
-        setBackgroundTop(backgroundTop);
-        setFirstElementRoundness(firstElementRoundness);
         mShelfIcons.setSpeedBumpIndex(mHostLayoutController.getSpeedBumpIndex());
         mShelfIcons.calculateIconXTranslations();
         mShelfIcons.applyIconStates();
@@ -570,12 +556,6 @@
         }
     }
 
-    private void setFirstElementRoundness(float firstElementRoundness) {
-        if (mFirstElementRoundness != firstElementRoundness) {
-            mFirstElementRoundness = firstElementRoundness;
-        }
-    }
-
     private void updateIconClipAmount(ExpandableNotificationRow row) {
         float maxTop = row.getTranslationY();
         if (getClipTopAmount() != 0) {
@@ -1011,6 +991,18 @@
         expandableView.requestRoundnessReset(LegacySourceType.OnScroll);
     }
 
+    @Override
+    public void dump(PrintWriter pwOriginal, String[] args) {
+        IndentingPrintWriter pw = DumpUtilsKt.asIndenting(pwOriginal);
+        super.dump(pw, args);
+        if (DUMP_VERBOSE) {
+            DumpUtilsKt.withIncreasedIndent(pw, () -> {
+                pw.println("mActualWidth: " + mActualWidth);
+                pw.println("mStatusBarHeight: " + mStatusBarHeight);
+            });
+        }
+    }
+
     public class ShelfState extends ExpandableViewState {
         private boolean hasItemsInStableShelf;
         private ExpandableView firstViewInShelf;
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 4856759..fc89be2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotifPipelineFlags.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotifPipelineFlags.kt
@@ -17,13 +17,16 @@
 package com.android.systemui.statusbar.notification
 
 import android.content.Context
+import com.android.internal.config.sysui.SystemUiSystemPropertiesFlags.FlagResolver
+import com.android.internal.config.sysui.SystemUiSystemPropertiesFlags.NotificationFlags
 import com.android.systemui.flags.FeatureFlags
 import com.android.systemui.flags.Flags
 import javax.inject.Inject
 
 class NotifPipelineFlags @Inject constructor(
     val context: Context,
-    val featureFlags: FeatureFlags
+    val featureFlags: FeatureFlags,
+    val sysPropFlags: FlagResolver,
 ) {
     init {
         featureFlags.addListener(Flags.DISABLE_FSI) { event -> event.requestNoRestart() }
@@ -39,11 +42,21 @@
 
     fun disableFsi(): Boolean = featureFlags.isEnabled(Flags.DISABLE_FSI)
 
-    val shouldFilterUnseenNotifsOnKeyguard: Boolean by lazy {
-        featureFlags.isEnabled(Flags.FILTER_UNSEEN_NOTIFS_ON_KEYGUARD)
-    }
+    fun forceDemoteFsi(): Boolean =
+            sysPropFlags.isEnabled(NotificationFlags.FSI_FORCE_DEMOTE)
 
-    val isNoHunForOldWhenEnabled: Boolean by lazy {
-        featureFlags.isEnabled(Flags.NO_HUN_FOR_OLD_WHEN)
-    }
+    fun showStickyHunForDeniedFsi(): Boolean =
+            sysPropFlags.isEnabled(NotificationFlags.SHOW_STICKY_HUN_FOR_DENIED_FSI)
+
+    fun allowDismissOngoing(): Boolean =
+            sysPropFlags.isEnabled(NotificationFlags.ALLOW_DISMISS_ONGOING)
+
+    fun isOtpRedactionEnabled(): Boolean =
+            sysPropFlags.isEnabled(NotificationFlags.OTP_REDACTION)
+
+    val shouldFilterUnseenNotifsOnKeyguard: Boolean
+        get() = featureFlags.isEnabled(Flags.FILTER_UNSEEN_NOTIFS_ON_KEYGUARD)
+
+    val isNoHunForOldWhenEnabled: Boolean
+        get() = featureFlags.isEnabled(Flags.NO_HUN_FOR_OLD_WHEN)
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java
index 7addc8f..68ad49be 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java
@@ -23,6 +23,7 @@
 import android.graphics.Canvas;
 import android.graphics.Point;
 import android.util.AttributeSet;
+import android.util.IndentingPrintWriter;
 import android.util.MathUtils;
 import android.view.Choreographer;
 import android.view.MotionEvent;
@@ -43,7 +44,9 @@
 import com.android.systemui.statusbar.notification.SourceType;
 import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout;
 import com.android.systemui.statusbar.notification.stack.StackStateAnimator;
+import com.android.systemui.util.DumpUtilsKt;
 
+import java.io.PrintWriter;
 import java.util.HashSet;
 import java.util.Set;
 
@@ -651,11 +654,6 @@
         mBackgroundNormal.setRadius(topRadius, bottomRadius);
     }
 
-    @Override
-    protected void setBackgroundTop(int backgroundTop) {
-        mBackgroundNormal.setBackgroundTop(backgroundTop);
-    }
-
     protected abstract View getContentView();
 
     public int calculateBgColor() {
@@ -819,6 +817,22 @@
         mOnDetachResetRoundness.add(sourceType);
     }
 
+    @Override
+    public void dump(PrintWriter pwOriginal, String[] args) {
+        IndentingPrintWriter pw = DumpUtilsKt.asIndenting(pwOriginal);
+        super.dump(pw, args);
+        if (DUMP_VERBOSE) {
+            DumpUtilsKt.withIncreasedIndent(pw, () -> {
+                pw.println("mBackgroundNormal: " + mBackgroundNormal);
+                if (mBackgroundNormal != null) {
+                    DumpUtilsKt.withIncreasedIndent(pw, () -> {
+                        mBackgroundNormal.dump(pw, args);
+                    });
+                }
+            });
+        }
+    }
+
     public interface OnActivatedListener {
         void onActivated(ActivatableNotificationView view);
         void onActivationReset(ActivatableNotificationView view);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableOutlineView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableOutlineView.java
index 2041245..197caa2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableOutlineView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableOutlineView.java
@@ -24,12 +24,16 @@
 import android.graphics.Rect;
 import android.graphics.RectF;
 import android.util.AttributeSet;
+import android.util.IndentingPrintWriter;
 import android.view.View;
 import android.view.ViewOutlineProvider;
 
 import com.android.systemui.R;
 import com.android.systemui.statusbar.notification.RoundableState;
 import com.android.systemui.statusbar.notification.stack.NotificationChildrenContainer;
+import com.android.systemui.util.DumpUtilsKt;
+
+import java.io.PrintWriter;
 
 /**
  * Like {@link ExpandableView}, but setting an outline for the height and clipping.
@@ -43,7 +47,6 @@
     private float mOutlineAlpha = -1f;
     private boolean mAlwaysRoundBothCorners;
     private Path mTmpPath = new Path();
-    private int mBackgroundTop;
 
     /**
      * {@code false} if the children views of the {@link ExpandableOutlineView} are translated when
@@ -59,7 +62,7 @@
                 // Only when translating just the contents, does the outline need to be shifted.
                 int translation = !mDismissUsingRowTranslationX ? (int) getTranslation() : 0;
                 int left = Math.max(translation, 0);
-                int top = mClipTopAmount + mBackgroundTop;
+                int top = mClipTopAmount;
                 int right = getWidth() + Math.min(translation, 0);
                 int bottom = Math.max(getActualHeight() - mClipBottomAmount, top);
                 outline.setRect(left, top, right, bottom);
@@ -92,7 +95,7 @@
                     ? (int) getTranslation() : 0;
             int halfExtraWidth = (int) (mExtraWidthForClipping / 2.0f);
             left = Math.max(translation, 0) - halfExtraWidth;
-            top = mClipTopAmount + mBackgroundTop;
+            top = mClipTopAmount;
             right = getWidth() + halfExtraWidth + Math.min(translation, 0);
             // If the top is rounded we want the bottom to be at most at the top roundness, in order
             // to avoid the shadow changing when scrolling up.
@@ -228,13 +231,6 @@
         super.applyRoundnessAndInvalidate();
     }
 
-    protected void setBackgroundTop(int backgroundTop) {
-        if (mBackgroundTop != backgroundTop) {
-            mBackgroundTop = backgroundTop;
-            invalidateOutline();
-        }
-    }
-
     public void onDensityOrFontScaleChanged() {
         initDimens();
         applyRoundnessAndInvalidate();
@@ -350,4 +346,18 @@
     public Path getCustomClipPath(View child) {
         return null;
     }
+
+    @Override
+    public void dump(PrintWriter pwOriginal, String[] args) {
+        IndentingPrintWriter pw = DumpUtilsKt.asIndenting(pwOriginal);
+        super.dump(pw, args);
+        DumpUtilsKt.withIncreasedIndent(pw, () -> {
+            pw.println("Roundness: " + getRoundableState().debugString());
+            if (DUMP_VERBOSE) {
+                pw.println("mCustomOutline: " + mCustomOutline + " mOutlineRect: " + mOutlineRect);
+                pw.println("mOutlineAlpha: " + mOutlineAlpha);
+                pw.println("mAlwaysRoundBothCorners: " + mAlwaysRoundBothCorners);
+            }
+        });
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableView.java
index 955d7c1..25c7264 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableView.java
@@ -51,6 +51,8 @@
  */
 public abstract class ExpandableView extends FrameLayout implements Dumpable, Roundable {
     private static final String TAG = "ExpandableView";
+    /** whether the dump() for this class should include verbose details */
+    protected static final boolean DUMP_VERBOSE = false;
 
     private RoundableState mRoundableState = null;
     protected OnHeightChangedListener mOnHeightChangedListener;
@@ -825,6 +827,14 @@
                 viewState.dump(pw, args);
                 pw.println();
             }
+            if (DUMP_VERBOSE) {
+                pw.println("mClipTopAmount: " + mClipTopAmount);
+                pw.println("mClipBottomAmount " + mClipBottomAmount);
+                pw.println("mClipToActualHeight: " + mClipToActualHeight);
+                pw.println("mExtraWidthForClipping: " + mExtraWidthForClipping);
+                pw.println("mMinimumHeightForClipping: " + mMinimumHeightForClipping);
+                pw.println("getClipBounds(): " + getClipBounds());
+            }
         });
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationBackgroundView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationBackgroundView.java
index 5171569..da8d2d5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationBackgroundView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationBackgroundView.java
@@ -28,12 +28,16 @@
 import android.view.View;
 
 import com.android.internal.util.ArrayUtils;
+import com.android.systemui.Dumpable;
 import com.android.systemui.R;
 
+import java.io.PrintWriter;
+import java.util.Arrays;
+
 /**
  * A view that can be used for both the dimmed and normal background of an notification.
  */
-public class NotificationBackgroundView extends View {
+public class NotificationBackgroundView extends View implements Dumpable {
 
     private final boolean mDontModifyCorners;
     private Drawable mBackground;
@@ -42,7 +46,6 @@
     private int mTintColor;
     private final float[] mCornerRadii = new float[8];
     private boolean mBottomIsRounded;
-    private int mBackgroundTop;
     private boolean mBottomAmountClips = true;
     private int mActualHeight = -1;
     private int mActualWidth = -1;
@@ -60,8 +63,7 @@
 
     @Override
     protected void onDraw(Canvas canvas) {
-        if (mClipTopAmount + mClipBottomAmount < getActualHeight() - mBackgroundTop
-                || mExpandAnimationRunning) {
+        if (mClipTopAmount + mClipBottomAmount < getActualHeight() || mExpandAnimationRunning) {
             canvas.save();
             if (!mExpandAnimationRunning) {
                 canvas.clipRect(0, mClipTopAmount, getWidth(),
@@ -74,7 +76,7 @@
 
     private void draw(Canvas canvas, Drawable drawable) {
         if (drawable != null) {
-            int top = mBackgroundTop;
+            int top = 0;
             int bottom = getActualHeight();
             if (mBottomIsRounded
                     && mBottomAmountClips
@@ -261,11 +263,6 @@
         }
     }
 
-    public void setBackgroundTop(int backgroundTop) {
-        mBackgroundTop = backgroundTop;
-        invalidate();
-    }
-
     /** Set the current expand animation size. */
     public void setExpandAnimationSize(int width, int height) {
         mExpandAnimationHeight = height;
@@ -291,4 +288,16 @@
     public void setPressedAllowed(boolean allowed) {
         mIsPressedAllowed = allowed;
     }
+
+    @Override
+    public void dump(PrintWriter pw, String[] args) {
+        pw.println("mDontModifyCorners: " + mDontModifyCorners);
+        pw.println("mClipTopAmount: " + mClipTopAmount);
+        pw.println("mClipBottomAmount: " + mClipBottomAmount);
+        pw.println("mCornerRadii: " + Arrays.toString(mCornerRadii));
+        pw.println("mBottomIsRounded: " + mBottomIsRounded);
+        pw.println("mBottomAmountClips: " + mBottomAmountClips);
+        pw.println("mActualWidth: " + mActualWidth);
+        pw.println("mActualHeight: " + mActualHeight);
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchTest.java
index 8dc1e8f..254f953 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchTest.java
@@ -16,7 +16,6 @@
 
 package com.android.keyguard;
 
-import static android.view.View.INVISIBLE;
 import static android.view.View.VISIBLE;
 
 import static com.android.keyguard.KeyguardClockSwitch.LARGE;
@@ -190,7 +189,6 @@
         assertThat(mLargeClockFrame.getAlpha()).isEqualTo(1);
         assertThat(mLargeClockFrame.getVisibility()).isEqualTo(VISIBLE);
         assertThat(mSmallClockFrame.getAlpha()).isEqualTo(0);
-        assertThat(mSmallClockFrame.getVisibility()).isEqualTo(INVISIBLE);
     }
 
     @Test
@@ -200,7 +198,6 @@
         assertThat(mLargeClockFrame.getAlpha()).isEqualTo(1);
         assertThat(mLargeClockFrame.getVisibility()).isEqualTo(VISIBLE);
         assertThat(mSmallClockFrame.getAlpha()).isEqualTo(0);
-        assertThat(mSmallClockFrame.getVisibility()).isEqualTo(INVISIBLE);
     }
 
     @Test
@@ -215,7 +212,6 @@
         // only big clock is removed at switch
         assertThat(mLargeClockFrame.getParent()).isNull();
         assertThat(mLargeClockFrame.getAlpha()).isEqualTo(0);
-        assertThat(mLargeClockFrame.getVisibility()).isEqualTo(INVISIBLE);
     }
 
     @Test
@@ -227,7 +223,6 @@
         // only big clock is removed at switch
         assertThat(mLargeClockFrame.getParent()).isNull();
         assertThat(mLargeClockFrame.getAlpha()).isEqualTo(0);
-        assertThat(mLargeClockFrame.getVisibility()).isEqualTo(INVISIBLE);
     }
 
     @Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsDebugRestarterTest.kt b/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsDebugRestarterTest.kt
index ed16721..686782f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsDebugRestarterTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsDebugRestarterTest.kt
@@ -20,6 +20,7 @@
 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.util.mockito.any
 import org.junit.Before
 import org.junit.Test
 import org.mockito.ArgumentCaptor
@@ -48,22 +49,22 @@
     @Test
     fun testRestart_ImmediateWhenAsleep() {
         whenever(wakefulnessLifecycle.wakefulness).thenReturn(WAKEFULNESS_ASLEEP)
-        restarter.restartSystemUI()
-        verify(systemExitRestarter).restartSystemUI()
+        restarter.restartSystemUI("Restart for test")
+        verify(systemExitRestarter).restartSystemUI(any())
     }
 
     @Test
     fun testRestart_WaitsForSceenOff() {
         whenever(wakefulnessLifecycle.wakefulness).thenReturn(WAKEFULNESS_AWAKE)
 
-        restarter.restartSystemUI()
-        verify(systemExitRestarter, never()).restartSystemUI()
+        restarter.restartSystemUI("Restart for test")
+        verify(systemExitRestarter, never()).restartSystemUI(any())
 
         val captor = ArgumentCaptor.forClass(WakefulnessLifecycle.Observer::class.java)
         verify(wakefulnessLifecycle).addObserver(captor.capture())
 
         captor.value.onFinishedGoingToSleep()
 
-        verify(systemExitRestarter).restartSystemUI()
+        verify(systemExitRestarter).restartSystemUI(any())
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsDebugTest.kt b/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsDebugTest.kt
index d8bbd04..2bcd75b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsDebugTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsDebugTest.kt
@@ -28,7 +28,6 @@
 import com.android.systemui.util.mockito.nullable
 import com.android.systemui.util.mockito.withArgCaptor
 import com.android.systemui.util.settings.GlobalSettings
-import com.android.systemui.util.settings.SecureSettings
 import com.google.common.truth.Truth.assertThat
 import org.junit.Assert
 import org.junit.Before
@@ -63,8 +62,6 @@
     @Mock
     private lateinit var globalSettings: GlobalSettings
     @Mock
-    private lateinit var secureSettings: SecureSettings
-    @Mock
     private lateinit var systemProperties: SystemPropertiesHelper
     @Mock
     private lateinit var resources: Resources
@@ -92,7 +89,6 @@
             flagManager,
             mockContext,
             globalSettings,
-            secureSettings,
             systemProperties,
             resources,
             serverFlagReader,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsReleaseRestarterTest.kt b/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsReleaseRestarterTest.kt
index 7d807e2..6060afe 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsReleaseRestarterTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsReleaseRestarterTest.kt
@@ -22,6 +22,7 @@
 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.mockito.any
 import com.android.systemui.util.time.FakeSystemClock
 import com.google.common.truth.Truth.assertThat
 import org.junit.Before
@@ -63,7 +64,7 @@
         whenever(batteryController.isPluggedIn).thenReturn(true)
 
         assertThat(executor.numPending()).isEqualTo(0)
-        restarter.restartSystemUI()
+        restarter.restartSystemUI("Restart for test")
         assertThat(executor.numPending()).isEqualTo(1)
     }
 
@@ -72,11 +73,11 @@
         whenever(wakefulnessLifecycle.wakefulness).thenReturn(WAKEFULNESS_ASLEEP)
         whenever(batteryController.isPluggedIn).thenReturn(true)
 
-        restarter.restartSystemUI()
-        verify(systemExitRestarter, never()).restartSystemUI()
+        restarter.restartSystemUI("Restart for test")
+        verify(systemExitRestarter, never()).restartSystemUI("Restart for test")
         executor.advanceClockToLast()
         executor.runAllReady()
-        verify(systemExitRestarter).restartSystemUI()
+        verify(systemExitRestarter).restartSystemUI(any())
     }
 
     @Test
@@ -85,7 +86,7 @@
         whenever(batteryController.isPluggedIn).thenReturn(true)
 
         assertThat(executor.numPending()).isEqualTo(0)
-        restarter.restartSystemUI()
+        restarter.restartSystemUI("Restart for test")
         assertThat(executor.numPending()).isEqualTo(0)
     }
 
@@ -95,7 +96,7 @@
         whenever(batteryController.isPluggedIn).thenReturn(false)
 
         assertThat(executor.numPending()).isEqualTo(0)
-        restarter.restartSystemUI()
+        restarter.restartSystemUI("Restart for test")
         assertThat(executor.numPending()).isEqualTo(0)
     }
 
@@ -105,8 +106,8 @@
         whenever(batteryController.isPluggedIn).thenReturn(true)
 
         assertThat(executor.numPending()).isEqualTo(0)
-        restarter.restartSystemUI()
-        restarter.restartSystemUI()
+        restarter.restartSystemUI("Restart for test")
+        restarter.restartSystemUI("Restart for test")
         assertThat(executor.numPending()).isEqualTo(1)
     }
 
@@ -115,7 +116,7 @@
         whenever(wakefulnessLifecycle.wakefulness).thenReturn(WAKEFULNESS_AWAKE)
         whenever(batteryController.isPluggedIn).thenReturn(true)
         assertThat(executor.numPending()).isEqualTo(0)
-        restarter.restartSystemUI()
+        restarter.restartSystemUI("Restart for test")
 
         val captor = ArgumentCaptor.forClass(WakefulnessLifecycle.Observer::class.java)
         verify(wakefulnessLifecycle).addObserver(captor.capture())
@@ -131,7 +132,7 @@
         whenever(wakefulnessLifecycle.wakefulness).thenReturn(WAKEFULNESS_ASLEEP)
         whenever(batteryController.isPluggedIn).thenReturn(false)
         assertThat(executor.numPending()).isEqualTo(0)
-        restarter.restartSystemUI()
+        restarter.restartSystemUI("Restart for test")
 
         val captor =
             ArgumentCaptor.forClass(BatteryController.BatteryStateChangeCallback::class.java)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/flags/ServerFlagReaderImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/flags/ServerFlagReaderImplTest.kt
index 1633912..4ebf974 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/flags/ServerFlagReaderImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/flags/ServerFlagReaderImplTest.kt
@@ -45,7 +45,7 @@
     fun setup() {
         MockitoAnnotations.initMocks(this)
 
-        serverFlagReader = ServerFlagReaderImpl(NAMESPACE, deviceConfig, executor)
+        serverFlagReader = ServerFlagReaderImpl(NAMESPACE, deviceConfig, executor, false)
     }
 
     @Test
@@ -56,6 +56,6 @@
         deviceConfig.setProperty(NAMESPACE, "flag_override_1", "1", false)
         executor.runAllReady()
 
-        verify(changeListener).onChange()
+        verify(changeListener).onChange(flag)
     }
 }
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
index 58cdec4..db18ba6 100644
--- 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
@@ -20,6 +20,7 @@
 import android.app.StatusBarManager
 import android.content.Context
 import android.content.pm.PackageManager
+import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.camera.CameraGestureHelper
@@ -31,14 +32,13 @@
 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
 
 @OptIn(ExperimentalCoroutinesApi::class)
 @SmallTest
-@RunWith(JUnit4::class)
+@RunWith(AndroidJUnit4::class)
 class CameraQuickAffordanceConfigTest : SysuiTestCase() {
 
     @Mock private lateinit var cameraGestureHelper: CameraGestureHelper
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/DoNotDisturbQuickAffordanceConfigTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/DoNotDisturbQuickAffordanceConfigTest.kt
index 15b85de..64839e2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/DoNotDisturbQuickAffordanceConfigTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/DoNotDisturbQuickAffordanceConfigTest.kt
@@ -22,6 +22,7 @@
 import android.provider.Settings.Global.ZEN_MODE_OFF
 import android.provider.Settings.Secure.ZEN_DURATION_FOREVER
 import android.provider.Settings.Secure.ZEN_DURATION_PROMPT
+import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.settingslib.notification.EnableZenModeDialog
 import com.android.systemui.R
@@ -51,7 +52,6 @@
 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.Mock
@@ -60,7 +60,7 @@
 
 @OptIn(ExperimentalCoroutinesApi::class)
 @SmallTest
-@RunWith(JUnit4::class)
+@RunWith(AndroidJUnit4::class)
 class DoNotDisturbQuickAffordanceConfigTest : SysuiTestCase() {
 
     @Mock private lateinit var zenModeController: ZenModeController
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/FlashlightQuickAffordanceConfigTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/FlashlightQuickAffordanceConfigTest.kt
index 9fa7db1..31391ee 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/FlashlightQuickAffordanceConfigTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/FlashlightQuickAffordanceConfigTest.kt
@@ -18,6 +18,7 @@
 package com.android.systemui.keyguard.data.quickaffordance
 
 import android.content.Context
+import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.systemui.R
 import com.android.systemui.common.shared.model.Icon
@@ -35,13 +36,12 @@
 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.MockitoAnnotations
 
 @OptIn(ExperimentalCoroutinesApi::class)
 @SmallTest
-@RunWith(JUnit4::class)
+@RunWith(AndroidJUnit4::class)
 class FlashlightQuickAffordanceConfigTest : LeakCheckedTest() {
 
     @Mock private lateinit var context: Context
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/HomeControlsKeyguardQuickAffordanceConfigTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/HomeControlsKeyguardQuickAffordanceConfigTest.kt
index 659c1e5..2c1c04c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/HomeControlsKeyguardQuickAffordanceConfigTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/HomeControlsKeyguardQuickAffordanceConfigTest.kt
@@ -17,6 +17,7 @@
 
 package com.android.systemui.keyguard.data.quickaffordance
 
+import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.systemui.R
 import com.android.systemui.SysuiTestCase
@@ -34,13 +35,12 @@
 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
 
 @SmallTest
-@RunWith(JUnit4::class)
+@RunWith(AndroidJUnit4::class)
 class HomeControlsKeyguardQuickAffordanceConfigTest : SysuiTestCase() {
 
     @Mock private lateinit var component: ControlsComponent
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceLegacySettingSyncerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceLegacySettingSyncerTest.kt
index 3b0169d..3bae7f7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceLegacySettingSyncerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceLegacySettingSyncerTest.kt
@@ -20,6 +20,7 @@
 import android.content.Context
 import android.content.res.Resources
 import android.provider.Settings
+import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.systemui.R
 import com.android.systemui.SysuiTestCase
@@ -40,7 +41,6 @@
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
-import org.junit.runners.JUnit4
 import org.mockito.ArgumentMatchers.anyInt
 import org.mockito.ArgumentMatchers.anyString
 import org.mockito.Mock
@@ -48,7 +48,7 @@
 
 @OptIn(ExperimentalCoroutinesApi::class)
 @SmallTest
-@RunWith(JUnit4::class)
+@RunWith(AndroidJUnit4::class)
 class KeyguardQuickAffordanceLegacySettingSyncerTest : SysuiTestCase() {
 
     @Mock private lateinit var sharedPrefs: FakeSharedPreferences
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceLocalUserSelectionManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceLocalUserSelectionManagerTest.kt
index 3d65713..1259b47 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceLocalUserSelectionManagerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceLocalUserSelectionManagerTest.kt
@@ -20,6 +20,7 @@
 import android.content.Intent
 import android.content.SharedPreferences
 import android.content.pm.UserInfo
+import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.systemui.R
 import com.android.systemui.SysuiTestCase
@@ -40,7 +41,6 @@
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
-import org.junit.runners.JUnit4
 import org.mockito.ArgumentMatchers.anyInt
 import org.mockito.ArgumentMatchers.anyString
 import org.mockito.Mock
@@ -51,7 +51,7 @@
 
 @OptIn(ExperimentalCoroutinesApi::class)
 @SmallTest
-@RunWith(JUnit4::class)
+@RunWith(AndroidJUnit4::class)
 class KeyguardQuickAffordanceLocalUserSelectionManagerTest : SysuiTestCase() {
 
     @Mock private lateinit var userFileManager: UserFileManager
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceRemoteUserSelectionManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceRemoteUserSelectionManagerTest.kt
index b21cec9..c08ef42 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceRemoteUserSelectionManagerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceRemoteUserSelectionManagerTest.kt
@@ -19,6 +19,7 @@
 
 import android.content.pm.UserInfo
 import android.os.UserHandle
+import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.settings.FakeUserTracker
@@ -37,13 +38,12 @@
 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.MockitoAnnotations
 
 @OptIn(ExperimentalCoroutinesApi::class)
 @SmallTest
-@RunWith(JUnit4::class)
+@RunWith(AndroidJUnit4::class)
 class KeyguardQuickAffordanceRemoteUserSelectionManagerTest : SysuiTestCase() {
 
     @Mock private lateinit var userHandle: UserHandle
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/MuteQuickAffordanceCoreStartableTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/MuteQuickAffordanceCoreStartableTest.kt
index 34f3ed8..facc747 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/MuteQuickAffordanceCoreStartableTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/MuteQuickAffordanceCoreStartableTest.kt
@@ -21,6 +21,7 @@
 import android.media.AudioManager
 import androidx.lifecycle.MutableLiveData
 import androidx.lifecycle.Observer
+import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.flags.FeatureFlags
@@ -47,7 +48,6 @@
 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.Mockito.verifyZeroInteractions
@@ -55,8 +55,8 @@
 
 @OptIn(ExperimentalCoroutinesApi::class)
 @SmallTest
-@RunWith(JUnit4::class)
-class MuteQuickAffordanceCoreStartableTest : SysuiTestCase()  {
+@RunWith(AndroidJUnit4::class)
+class MuteQuickAffordanceCoreStartableTest : SysuiTestCase() {
 
     @Mock
     private lateinit var featureFlags: FeatureFlags
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/QrCodeScannerKeyguardQuickAffordanceConfigTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/QrCodeScannerKeyguardQuickAffordanceConfigTest.kt
index 9d2ddff..1adf808 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/QrCodeScannerKeyguardQuickAffordanceConfigTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/QrCodeScannerKeyguardQuickAffordanceConfigTest.kt
@@ -18,6 +18,7 @@
 package com.android.systemui.keyguard.data.quickaffordance
 
 import android.content.Intent
+import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceConfig.OnTriggeredResult
@@ -33,13 +34,12 @@
 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)
+@RunWith(AndroidJUnit4::class)
 class QrCodeScannerKeyguardQuickAffordanceConfigTest : SysuiTestCase() {
 
     @Mock private lateinit var controller: QRCodeScannerController
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfigTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfigTest.kt
index 8f56b95..752963f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfigTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfigTest.kt
@@ -20,6 +20,7 @@
 import android.graphics.drawable.Drawable
 import android.service.quickaccesswallet.GetWalletCardsResponse
 import android.service.quickaccesswallet.QuickAccessWalletClient
+import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.systemui.R
 import com.android.systemui.SysuiTestCase
@@ -41,14 +42,13 @@
 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
 
 @OptIn(ExperimentalCoroutinesApi::class)
 @SmallTest
-@RunWith(JUnit4::class)
+@RunWith(AndroidJUnit4::class)
 class QuickAccessWalletKeyguardQuickAffordanceConfigTest : SysuiTestCase() {
 
     @Mock private lateinit var walletController: QuickAccessWalletController
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/VideoCameraQuickAffordanceConfigTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/VideoCameraQuickAffordanceConfigTest.kt
index 805dcec..5bd86bd 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/VideoCameraQuickAffordanceConfigTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/VideoCameraQuickAffordanceConfigTest.kt
@@ -17,6 +17,7 @@
 
 package com.android.systemui.keyguard.data.quickaffordance
 
+import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.systemui.ActivityIntentHelper
 import com.android.systemui.SysuiTestCase
@@ -32,7 +33,6 @@
 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
@@ -40,7 +40,7 @@
 
 @OptIn(ExperimentalCoroutinesApi::class)
 @SmallTest
-@RunWith(JUnit4::class)
+@RunWith(AndroidJUnit4::class)
 class VideoCameraQuickAffordanceConfigTest : SysuiTestCase() {
 
     @Mock private lateinit var activityIntentHelper: ActivityIntentHelper
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardBouncerRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardBouncerRepositoryTest.kt
index 444a2a7..ff22f1e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardBouncerRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardBouncerRepositoryTest.kt
@@ -16,6 +16,7 @@
 
 package com.android.systemui.keyguard.data.repository
 
+import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.keyguard.ViewMediatorCallback
 import com.android.systemui.SysuiTestCase
@@ -26,13 +27,12 @@
 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)
+@RunWith(AndroidJUnit4::class)
 class KeyguardBouncerRepositoryTest : SysuiTestCase() {
 
     @Mock private lateinit var systemClock: SystemClock
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 6099f01..86e8c9a 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
@@ -19,9 +19,11 @@
 
 import android.content.pm.UserInfo
 import android.os.UserHandle
+import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.systemui.R
 import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.keyguard.data.quickaffordance.FakeKeyguardQuickAffordanceConfig
 import com.android.systemui.keyguard.data.quickaffordance.FakeKeyguardQuickAffordanceProviderClientFactory
 import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceConfig
@@ -39,23 +41,19 @@
 import com.android.systemui.util.mockito.whenever
 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.ExperimentalCoroutinesApi
-import kotlinx.coroutines.flow.launchIn
-import kotlinx.coroutines.flow.onEach
-import kotlinx.coroutines.runBlocking
-import kotlinx.coroutines.yield
+import kotlinx.coroutines.test.StandardTestDispatcher
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runTest
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
-import org.junit.runners.JUnit4
 import org.mockito.ArgumentMatchers.anyInt
 import org.mockito.ArgumentMatchers.anyString
 
 @OptIn(ExperimentalCoroutinesApi::class)
 @SmallTest
-@RunWith(JUnit4::class)
+@RunWith(AndroidJUnit4::class)
 class KeyguardQuickAffordanceRepositoryTest : SysuiTestCase() {
 
     private lateinit var underTest: KeyguardQuickAffordanceRepository
@@ -65,12 +63,14 @@
     private lateinit var userTracker: FakeUserTracker
     private lateinit var client1: FakeCustomizationProviderClient
     private lateinit var client2: FakeCustomizationProviderClient
+    private lateinit var testScope: TestScope
 
     @Before
     fun setUp() {
         config1 = FakeKeyguardQuickAffordanceConfig(FakeCustomizationProviderClient.AFFORDANCE_1)
         config2 = FakeKeyguardQuickAffordanceConfig(FakeCustomizationProviderClient.AFFORDANCE_2)
-        val scope = CoroutineScope(IMMEDIATE)
+        val testDispatcher = StandardTestDispatcher()
+        testScope = TestScope(testDispatcher)
         userTracker = FakeUserTracker()
         val localUserSelectionManager =
             KeyguardQuickAffordanceLocalUserSelectionManager(
@@ -93,7 +93,7 @@
         client2 = FakeCustomizationProviderClient()
         val remoteUserSelectionManager =
             KeyguardQuickAffordanceRemoteUserSelectionManager(
-                scope = scope,
+                scope = testScope.backgroundScope,
                 userTracker = userTracker,
                 clientFactory =
                     FakeKeyguardQuickAffordanceProviderClientFactory(
@@ -116,14 +116,14 @@
         underTest =
             KeyguardQuickAffordanceRepository(
                 appContext = context,
-                scope = scope,
+                scope = testScope.backgroundScope,
                 localUserSelectionManager = localUserSelectionManager,
                 remoteUserSelectionManager = remoteUserSelectionManager,
                 userTracker = userTracker,
                 legacySettingSyncer =
                     KeyguardQuickAffordanceLegacySettingSyncer(
-                        scope = scope,
-                        backgroundDispatcher = IMMEDIATE,
+                        scope = testScope.backgroundScope,
+                        backgroundDispatcher = testDispatcher,
                         secureSettings = FakeSettings(),
                         selectionsManager = localUserSelectionManager,
                     ),
@@ -135,15 +135,14 @@
 
     @Test
     fun setSelections() =
-        runBlocking(IMMEDIATE) {
-            var configsBySlotId: Map<String, List<KeyguardQuickAffordanceConfig>>? = null
-            val job = underTest.selections.onEach { configsBySlotId = it }.launchIn(this)
+        testScope.runTest {
+            val configsBySlotId = collectLastValue(underTest.selections)
             val slotId1 = "slot1"
             val slotId2 = "slot2"
 
             underTest.setSelections(slotId1, listOf(config1.key))
             assertSelections(
-                configsBySlotId,
+                configsBySlotId(),
                 mapOf(
                     slotId1 to listOf(config1),
                 ),
@@ -151,7 +150,7 @@
 
             underTest.setSelections(slotId2, listOf(config2.key))
             assertSelections(
-                configsBySlotId,
+                configsBySlotId(),
                 mapOf(
                     slotId1 to listOf(config1),
                     slotId2 to listOf(config2),
@@ -161,19 +160,17 @@
             underTest.setSelections(slotId1, emptyList())
             underTest.setSelections(slotId2, listOf(config1.key))
             assertSelections(
-                configsBySlotId,
+                configsBySlotId(),
                 mapOf(
                     slotId1 to emptyList(),
                     slotId2 to listOf(config1),
                 ),
             )
-
-            job.cancel()
         }
 
     @Test
     fun getAffordancePickerRepresentations() =
-        runBlocking(IMMEDIATE) {
+        testScope.runTest {
             assertThat(underTest.getAffordancePickerRepresentations())
                 .isEqualTo(
                     listOf(
@@ -226,7 +223,7 @@
 
     @Test
     fun `selections for secondary user`() =
-        runBlocking(IMMEDIATE) {
+        testScope.runTest {
             userTracker.set(
                 userInfos =
                     listOf(
@@ -252,12 +249,10 @@
                 slotId = KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START,
                 affordanceId = FakeCustomizationProviderClient.AFFORDANCE_2,
             )
-            val observed = mutableListOf<Map<String, List<KeyguardQuickAffordanceConfig>>>()
-            val job = underTest.selections.onEach { observed.add(it) }.launchIn(this)
-            yield()
+            val observed = collectLastValue(underTest.selections)
 
             assertSelections(
-                observed = observed.last(),
+                observed = observed(),
                 expected =
                     mapOf(
                         KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START to
@@ -266,8 +261,6 @@
                             ),
                     )
             )
-
-            job.cancel()
         }
 
     private fun assertSelections(
@@ -283,7 +276,6 @@
     }
 
     companion object {
-        private val IMMEDIATE = Dispatchers.Main.immediate
         private const val SECONDARY_USER_1 = UserHandle.MIN_SECONDARY_USER_ID + 1
         private const val SECONDARY_USER_2 = UserHandle.MIN_SECONDARY_USER_ID + 2
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt
index f997d18..8bb6a85 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt
@@ -18,6 +18,7 @@
 
 import android.graphics.Point
 import android.hardware.biometrics.BiometricSourceType
+import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.keyguard.KeyguardUpdateMonitor
 import com.android.keyguard.KeyguardUpdateMonitorCallback
@@ -54,13 +55,12 @@
 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)
+@RunWith(AndroidJUnit4::class)
 class KeyguardRepositoryImplTest : SysuiTestCase() {
 
     @Mock private lateinit var statusBarStateController: StatusBarStateController
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepositoryTest.kt
index 32cec09..ae227b4 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepositoryTest.kt
@@ -20,6 +20,7 @@
 import android.util.Log
 import android.util.Log.TerribleFailure
 import android.util.Log.TerribleFailureHandler
+import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.animation.Interpolators
@@ -43,10 +44,9 @@
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
-import org.junit.runners.JUnit4
 
 @SmallTest
-@RunWith(JUnit4::class)
+@RunWith(AndroidJUnit4::class)
 class KeyguardTransitionRepositoryTest : SysuiTestCase() {
 
     private lateinit var underTest: KeyguardTransitionRepository
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/TrustRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/TrustRepositoryTest.kt
index 4b06905..a181137 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/TrustRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/TrustRepositoryTest.kt
@@ -18,6 +18,7 @@
 
 import android.app.trust.TrustManager
 import android.content.pm.UserInfo
+import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.keyguard.logging.TrustRepositoryLogger
 import com.android.systemui.SysuiTestCase
@@ -33,7 +34,6 @@
 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.Mock
@@ -43,7 +43,7 @@
 
 @OptIn(ExperimentalCoroutinesApi::class)
 @SmallTest
-@RunWith(JUnit4::class)
+@RunWith(AndroidJUnit4::class)
 class TrustRepositoryTest : SysuiTestCase() {
     @Mock private lateinit var trustManager: TrustManager
     @Captor private lateinit var listenerCaptor: ArgumentCaptor<TrustManager.TrustListener>
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/AlternateBouncerInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/AlternateBouncerInteractorTest.kt
index 8caf60f..7ded354 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/AlternateBouncerInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/AlternateBouncerInteractorTest.kt
@@ -16,6 +16,7 @@
 
 package com.android.systemui.keyguard.domain.interactor
 
+import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.keyguard.KeyguardUpdateMonitor
 import com.android.keyguard.ViewMediatorCallback
@@ -36,14 +37,13 @@
 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.mock
 import org.mockito.MockitoAnnotations
 
 @OptIn(ExperimentalCoroutinesApi::class)
 @SmallTest
-@RunWith(JUnit4::class)
+@RunWith(AndroidJUnit4::class)
 class AlternateBouncerInteractorTest : SysuiTestCase() {
     private lateinit var underTest: AlternateBouncerInteractor
     private lateinit var bouncerRepository: KeyguardBouncerRepository
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorTest.kt
index d938243..7d4861b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorTest.kt
@@ -19,6 +19,7 @@
 
 import android.app.StatusBarManager
 import android.content.Context
+import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.coroutines.collectLastValue
@@ -36,12 +37,12 @@
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
-import org.junit.runners.JUnit4
 import org.mockito.Mockito.mock
+import org.mockito.Mockito.verify
 import org.mockito.MockitoAnnotations
 
 @SmallTest
-@RunWith(JUnit4::class)
+@RunWith(AndroidJUnit4::class)
 class KeyguardInteractorTest : SysuiTestCase() {
     private lateinit var commandQueue: FakeCommandQueue
     private lateinit var featureFlags: FakeFeatureFlags
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardLongPressInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardLongPressInteractorTest.kt
index 9d60b16..51988ef 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardLongPressInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardLongPressInteractorTest.kt
@@ -18,6 +18,7 @@
 package com.android.systemui.keyguard.domain.interactor
 
 import android.content.Intent
+import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.internal.logging.UiEventLogger
 import com.android.systemui.SysuiTestCase
@@ -39,7 +40,6 @@
 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.Mock
 import org.mockito.Mockito.never
@@ -48,7 +48,7 @@
 
 @OptIn(ExperimentalCoroutinesApi::class)
 @SmallTest
-@RunWith(JUnit4::class)
+@RunWith(AndroidJUnit4::class)
 class KeyguardLongPressInteractorTest : SysuiTestCase() {
 
     @Mock private lateinit var activityStarter: ActivityStarter
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 8cff0ae..ec70857 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
@@ -18,6 +18,7 @@
 package com.android.systemui.keyguard.domain.interactor
 
 import android.os.UserHandle
+import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.internal.widget.LockPatternUtils
 import com.android.systemui.R
@@ -60,7 +61,6 @@
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
-import org.junit.runners.JUnit4
 import org.mockito.ArgumentMatchers.anyInt
 import org.mockito.ArgumentMatchers.anyString
 import org.mockito.Mock
@@ -68,7 +68,7 @@
 
 @OptIn(ExperimentalCoroutinesApi::class)
 @SmallTest
-@RunWith(JUnit4::class)
+@RunWith(AndroidJUnit4::class)
 class KeyguardQuickAffordanceInteractorTest : SysuiTestCase() {
 
     @Mock private lateinit var lockPatternUtils: LockPatternUtils
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorTest.kt
index 6333b24..3d13d80 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorTest.kt
@@ -17,6 +17,7 @@
 
 package com.android.systemui.keyguard.domain.interactor
 
+import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
@@ -29,17 +30,17 @@
 import com.android.systemui.keyguard.shared.model.TransitionState.STARTED
 import com.android.systemui.keyguard.shared.model.TransitionStep
 import com.google.common.truth.Truth.assertThat
-import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.test.UnconfinedTestDispatcher
 import kotlinx.coroutines.flow.launchIn
 import kotlinx.coroutines.flow.onEach
-import kotlinx.coroutines.runBlocking
+import kotlinx.coroutines.test.runTest
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
-import org.junit.runners.JUnit4
 
 @SmallTest
-@RunWith(JUnit4::class)
+@RunWith(AndroidJUnit4::class)
+@kotlinx.coroutines.ExperimentalCoroutinesApi
 class KeyguardTransitionInteractorTest : SysuiTestCase() {
 
     private lateinit var underTest: KeyguardTransitionInteractor
@@ -53,7 +54,7 @@
 
     @Test
     fun `transition collectors receives only appropriate events`() =
-        runBlocking(IMMEDIATE) {
+        runTest(UnconfinedTestDispatcher()) {
             var lockscreenToAodSteps = mutableListOf<TransitionStep>()
             val job1 =
                 underTest.lockscreenToAodTransition
@@ -87,10 +88,9 @@
 
     @Test
     fun dozeAmountTransitionTest() =
-        runBlocking(IMMEDIATE) {
+        runTest(UnconfinedTestDispatcher()) {
             var dozeAmountSteps = mutableListOf<TransitionStep>()
-            val job =
-                underTest.dozeAmountTransition.onEach { dozeAmountSteps.add(it) }.launchIn(this)
+            val job = underTest.dozeAmountTransition.onEach { dozeAmountSteps.add(it) }.launchIn(this)
 
             val steps = mutableListOf<TransitionStep>()
 
@@ -119,10 +119,9 @@
 
     @Test
     fun keyguardStateTests() =
-        runBlocking(IMMEDIATE) {
+        runTest(UnconfinedTestDispatcher()) {
             var finishedSteps = mutableListOf<KeyguardState>()
-            val job =
-                underTest.finishedKeyguardState.onEach { finishedSteps.add(it) }.launchIn(this)
+            val job = underTest.finishedKeyguardState.onEach { finishedSteps.add(it) }.launchIn(this)
 
             val steps = mutableListOf<TransitionStep>()
 
@@ -143,12 +142,10 @@
 
     @Test
     fun finishedKeyguardTransitionStepTests() =
-        runBlocking(IMMEDIATE) {
+        runTest(UnconfinedTestDispatcher()) {
             var finishedSteps = mutableListOf<TransitionStep>()
             val job =
-                underTest.finishedKeyguardTransitionStep
-                    .onEach { finishedSteps.add(it) }
-                    .launchIn(this)
+                underTest.finishedKeyguardTransitionStep.onEach { finishedSteps.add(it) }.launchIn(this)
 
             val steps = mutableListOf<TransitionStep>()
 
@@ -169,12 +166,10 @@
 
     @Test
     fun startedKeyguardTransitionStepTests() =
-        runBlocking(IMMEDIATE) {
+        runTest(UnconfinedTestDispatcher()) {
             var startedSteps = mutableListOf<TransitionStep>()
             val job =
-                underTest.startedKeyguardTransitionStep
-                    .onEach { startedSteps.add(it) }
-                    .launchIn(this)
+                underTest.startedKeyguardTransitionStep.onEach { startedSteps.add(it) }.launchIn(this)
 
             val steps = mutableListOf<TransitionStep>()
 
@@ -192,8 +187,4 @@
 
             job.cancel()
         }
-
-    companion object {
-        private val IMMEDIATE = Dispatchers.Main.immediate
-    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/LightRevealScrimInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/LightRevealScrimInteractorTest.kt
index 3166214..6236616 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/LightRevealScrimInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/LightRevealScrimInteractorTest.kt
@@ -16,6 +16,7 @@
 
 package com.android.systemui.keyguard.domain.interactor
 
+import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
@@ -33,11 +34,10 @@
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
-import org.junit.runners.JUnit4
 import org.mockito.MockitoAnnotations
 
 @SmallTest
-@RunWith(JUnit4::class)
+@RunWith(AndroidJUnit4::class)
 class LightRevealScrimInteractorTest : SysuiTestCase() {
     private val fakeKeyguardTransitionRepository = FakeKeyguardTransitionRepository()
     private val fakeLightRevealScrimRepository = FakeLightRevealScrimRepository()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerCallbackInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerCallbackInteractorTest.kt
index fbfeca9..f86ac79 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerCallbackInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerCallbackInteractorTest.kt
@@ -17,18 +17,18 @@
 package com.android.systemui.keyguard.domain.interactor
 
 import android.view.View
+import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
 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)
+@RunWith(AndroidJUnit4::class)
 class PrimaryBouncerCallbackInteractorTest : SysuiTestCase() {
     private val mPrimaryBouncerCallbackInteractor = PrimaryBouncerCallbackInteractor()
     @Mock
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerInteractorWithCoroutinesTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerInteractorWithCoroutinesTest.kt
index ea7bc91..75b74b0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerInteractorWithCoroutinesTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerInteractorWithCoroutinesTest.kt
@@ -17,6 +17,7 @@
 package com.android.systemui.keyguard.domain.interactor
 
 import android.os.Looper
+import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.keyguard.KeyguardSecurityModel
 import com.android.keyguard.KeyguardUpdateMonitor
@@ -35,12 +36,11 @@
 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.MockitoAnnotations
 
 @SmallTest
-@RunWith(JUnit4::class)
+@RunWith(AndroidJUnit4::class)
 class PrimaryBouncerInteractorWithCoroutinesTest : SysuiTestCase() {
     private lateinit var repository: FakeKeyguardBouncerRepository
     @Mock private lateinit var bouncerView: BouncerView
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToLockscreenTransitionViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToLockscreenTransitionViewModelTest.kt
index 06e397d..706154e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToLockscreenTransitionViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToLockscreenTransitionViewModelTest.kt
@@ -16,6 +16,7 @@
 
 package com.android.systemui.keyguard.ui.viewmodel
 
+import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
@@ -33,10 +34,9 @@
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
-import org.junit.runners.JUnit4
 
 @SmallTest
-@RunWith(JUnit4::class)
+@RunWith(AndroidJUnit4::class)
 class DreamingToLockscreenTransitionViewModelTest : SysuiTestCase() {
     private lateinit var underTest: DreamingToLockscreenTransitionViewModel
     private lateinit var repository: FakeKeyguardTransitionRepository
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/GoneToDreamingTransitionViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/GoneToDreamingTransitionViewModelTest.kt
index 14c3b50..b15ce10 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/GoneToDreamingTransitionViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/GoneToDreamingTransitionViewModelTest.kt
@@ -16,6 +16,7 @@
 
 package com.android.systemui.keyguard.ui.viewmodel
 
+import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
@@ -32,10 +33,9 @@
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
-import org.junit.runners.JUnit4
 
 @SmallTest
-@RunWith(JUnit4::class)
+@RunWith(AndroidJUnit4::class)
 class GoneToDreamingTransitionViewModelTest : SysuiTestCase() {
     private lateinit var underTest: GoneToDreamingTransitionViewModel
     private lateinit var repository: FakeKeyguardTransitionRepository
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBouncerViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBouncerViewModelTest.kt
index 3727134..586af62 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBouncerViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBouncerViewModelTest.kt
@@ -16,28 +16,29 @@
 
 package com.android.systemui.keyguard.ui.viewmodel
 
+import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.keyguard.data.BouncerView
 import com.android.systemui.keyguard.domain.interactor.PrimaryBouncerInteractor
 import com.android.systemui.keyguard.shared.model.BouncerShowMessageModel
 import com.google.common.truth.Truth.assertThat
-import kotlinx.coroutines.Dispatchers
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.launchIn
 import kotlinx.coroutines.flow.onEach
-import kotlinx.coroutines.runBlocking
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
-import org.junit.runners.JUnit4
 import org.mockito.Mock
 import org.mockito.Mockito
 import org.mockito.MockitoAnnotations
 
 @SmallTest
-@RunWith(JUnit4::class)
+@RunWith(AndroidJUnit4::class)
+@kotlinx.coroutines.ExperimentalCoroutinesApi
 class KeyguardBouncerViewModelTest : SysuiTestCase() {
     lateinit var underTest: KeyguardBouncerViewModel
     @Mock lateinit var bouncerView: BouncerView
@@ -51,7 +52,7 @@
 
     @Test
     fun setMessage() =
-        runBlocking(Dispatchers.Main.immediate) {
+        runTest {
             val flow = MutableStateFlow<BouncerShowMessageModel?>(null)
             var message: BouncerShowMessageModel? = null
             Mockito.`when`(bouncerInteractor.showMessage)
@@ -62,6 +63,8 @@
             flow.value = BouncerShowMessageModel(message = "abc", colorStateList = null)
 
             val job = underTest.bouncerShowMessage.onEach { message = it }.launchIn(this)
+            // Run the tasks that are pending at this point of virtual time.
+            runCurrent()
             assertThat(message?.message).isEqualTo("abc")
             job.cancel()
         }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToDreamingTransitionViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToDreamingTransitionViewModelTest.kt
index ed31dc3..d94c108 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToDreamingTransitionViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToDreamingTransitionViewModelTest.kt
@@ -16,6 +16,7 @@
 
 package com.android.systemui.keyguard.ui.viewmodel
 
+import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
@@ -32,10 +33,9 @@
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
-import org.junit.runners.JUnit4
 
 @SmallTest
-@RunWith(JUnit4::class)
+@RunWith(AndroidJUnit4::class)
 class LockscreenToDreamingTransitionViewModelTest : SysuiTestCase() {
     private lateinit var underTest: LockscreenToDreamingTransitionViewModel
     private lateinit var repository: FakeKeyguardTransitionRepository
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToOccludedTransitionViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToOccludedTransitionViewModelTest.kt
index 458b315..12ec24d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToOccludedTransitionViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToOccludedTransitionViewModelTest.kt
@@ -16,6 +16,7 @@
 
 package com.android.systemui.keyguard.ui.viewmodel
 
+import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
@@ -32,10 +33,9 @@
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
-import org.junit.runners.JUnit4
 
 @SmallTest
-@RunWith(JUnit4::class)
+@RunWith(AndroidJUnit4::class)
 class LockscreenToOccludedTransitionViewModelTest : SysuiTestCase() {
     private lateinit var underTest: LockscreenToOccludedTransitionViewModel
     private lateinit var repository: FakeKeyguardTransitionRepository
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToLockscreenTransitionViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToLockscreenTransitionViewModelTest.kt
index a36214e..0c4e845 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToLockscreenTransitionViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToLockscreenTransitionViewModelTest.kt
@@ -16,6 +16,7 @@
 
 package com.android.systemui.keyguard.ui.viewmodel
 
+import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
@@ -32,10 +33,9 @@
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
-import org.junit.runners.JUnit4
 
 @SmallTest
-@RunWith(JUnit4::class)
+@RunWith(AndroidJUnit4::class)
 class OccludedToLockscreenTransitionViewModelTest : SysuiTestCase() {
     private lateinit var underTest: OccludedToLockscreenTransitionViewModel
     private lateinit var repository: FakeKeyguardTransitionRepository
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 6f1b42b..9f12329 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
@@ -827,6 +827,24 @@
     }
 
     @Test
+    fun testAddResumptionControls_hasNoExtras() {
+        whenever(mediaFlags.isResumeProgressEnabled()).thenReturn(true)
+
+        // WHEN resumption controls are added that do not have any extras
+        val desc =
+            MediaDescription.Builder().run {
+                setTitle(SESSION_TITLE)
+                build()
+            }
+        addResumeControlAndLoad(desc)
+
+        // Resume progress is null
+        val data = mediaDataCaptor.value
+        assertThat(data.resumption).isTrue()
+        assertThat(data.resumeProgress).isEqualTo(null)
+    }
+
+    @Test
     fun testResumptionDisabled_dismissesResumeControls() {
         // WHEN there are resume controls and resumption is switched off
         val desc =
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaViewControllerTest.kt
index 2f7eac2..af91cdb 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaViewControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaViewControllerTest.kt
@@ -33,6 +33,7 @@
 import org.junit.runner.RunWith
 import org.mockito.ArgumentMatchers.floatThat
 import org.mockito.Mock
+import org.mockito.Mockito.times
 import org.mockito.Mockito.verify
 import org.mockito.Mockito.`when` as whenever
 import org.mockito.MockitoAnnotations
@@ -139,14 +140,12 @@
         whenever(controlWidgetState.y).thenReturn(150F)
         whenever(controlWidgetState.height).thenReturn(20)
         // in current beizer, when the progress reach 0.38, the result will be 0.5
-        mediaViewController.squishViewState(mockViewState, 119F / 200F)
-        verify(detailWidgetState).alpha = floatThat { kotlin.math.abs(it - 0.5F) < delta }
-        mediaViewController.squishViewState(mockViewState, 150F / 200F)
-        verify(detailWidgetState).alpha = floatThat { kotlin.math.abs(it - 1.0F) < delta }
         mediaViewController.squishViewState(mockViewState, 181.4F / 200F)
         verify(controlWidgetState).alpha = floatThat { kotlin.math.abs(it - 0.5F) < delta }
+        verify(detailWidgetState).alpha = floatThat { kotlin.math.abs(it - 1.0F) < delta }
         mediaViewController.squishViewState(mockViewState, 200F / 200F)
         verify(controlWidgetState).alpha = floatThat { kotlin.math.abs(it - 1.0F) < delta }
+        verify(detailWidgetState, times(2)).alpha = floatThat { kotlin.math.abs(it - 1.0F) < delta }
     }
 
     @Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shared/clocks/ClockRegistryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shared/clocks/ClockRegistryTest.kt
index 78bebb9..d01edcc 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shared/clocks/ClockRegistryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shared/clocks/ClockRegistryTest.kt
@@ -18,8 +18,6 @@
 import android.content.ContentResolver
 import android.content.Context
 import android.graphics.drawable.Drawable
-import android.os.Handler
-import android.os.UserHandle
 import android.testing.AndroidTestingRunner
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
@@ -34,6 +32,9 @@
 import com.android.systemui.util.mockito.eq
 import junit.framework.Assert.assertEquals
 import junit.framework.Assert.fail
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.test.StandardTestDispatcher
+import kotlinx.coroutines.test.TestScope
 import org.json.JSONException
 import org.junit.Before
 import org.junit.Rule
@@ -49,19 +50,19 @@
 class ClockRegistryTest : SysuiTestCase() {
 
     @JvmField @Rule val mockito = MockitoJUnit.rule()
+    private lateinit var dispatcher: CoroutineDispatcher
+    private lateinit var scope: TestScope
+
     @Mock private lateinit var mockContext: Context
     @Mock private lateinit var mockPluginManager: PluginManager
     @Mock private lateinit var mockClock: ClockController
     @Mock private lateinit var mockDefaultClock: ClockController
     @Mock private lateinit var mockThumbnail: Drawable
-    @Mock private lateinit var mockHandler: Handler
     @Mock private lateinit var mockContentResolver: ContentResolver
     private lateinit var fakeDefaultProvider: FakeClockPlugin
     private lateinit var pluginListener: PluginListener<ClockProviderPlugin>
     private lateinit var registry: ClockRegistry
 
-    private var settingValue: ClockSettings? = null
-
     companion object {
         private fun failFactory(): ClockController {
             fail("Unexpected call to createClock")
@@ -99,6 +100,9 @@
 
     @Before
     fun setUp() {
+        dispatcher = StandardTestDispatcher()
+        scope = TestScope(dispatcher)
+
         fakeDefaultProvider = FakeClockPlugin()
             .addClock(DEFAULT_CLOCK_ID, DEFAULT_CLOCK_NAME, { mockDefaultClock }, { mockThumbnail })
         whenever(mockContext.contentResolver).thenReturn(mockContentResolver)
@@ -107,15 +111,22 @@
         registry = object : ClockRegistry(
             mockContext,
             mockPluginManager,
-            mockHandler,
+            scope = scope.backgroundScope,
+            mainDispatcher = dispatcher,
+            bgDispatcher = dispatcher,
             isEnabled = true,
-            userHandle = UserHandle.USER_ALL,
-            defaultClockProvider = fakeDefaultProvider
+            handleAllUsers = true,
+            defaultClockProvider = fakeDefaultProvider,
         ) {
-            override var settings: ClockSettings?
-                get() = settingValue
-                set(value) { settingValue = value }
+            override fun querySettings() { }
+            override fun applySettings(value: ClockSettings?) {
+                settings = value
+            }
+            // Unit Test does not validate threading
+            override fun assertMainThread() {}
+            override fun assertNotMainThread() {}
         }
+        registry.registerListeners()
 
         verify(mockPluginManager)
             .addPluginListener(captor.capture(), eq(ClockProviderPlugin::class.java), eq(true))
@@ -187,16 +198,16 @@
             .addClock("clock_1", "clock 1")
             .addClock("clock_2", "clock 2")
 
-        settingValue = ClockSettings("clock_3", null, null)
         val plugin2 = FakeClockPlugin()
             .addClock("clock_3", "clock 3", { mockClock })
             .addClock("clock_4", "clock 4")
 
+        registry.applySettings(ClockSettings("clock_3", null))
         pluginListener.onPluginConnected(plugin1, mockContext)
         pluginListener.onPluginConnected(plugin2, mockContext)
 
         val clock = registry.createCurrentClock()
-        assertEquals(clock, mockClock)
+        assertEquals(mockClock, clock)
     }
 
     @Test
@@ -205,11 +216,11 @@
             .addClock("clock_1", "clock 1")
             .addClock("clock_2", "clock 2")
 
-        settingValue = ClockSettings("clock_3", null, null)
         val plugin2 = FakeClockPlugin()
             .addClock("clock_3", "clock 3")
             .addClock("clock_4", "clock 4")
 
+        registry.applySettings(ClockSettings("clock_3", null))
         pluginListener.onPluginConnected(plugin1, mockContext)
         pluginListener.onPluginConnected(plugin2, mockContext)
         pluginListener.onPluginDisconnected(plugin2)
@@ -224,11 +235,11 @@
             .addClock("clock_1", "clock 1")
             .addClock("clock_2", "clock 2")
 
-        settingValue = ClockSettings("clock_3", null, null)
         val plugin2 = FakeClockPlugin()
             .addClock("clock_3", "clock 3", { mockClock })
             .addClock("clock_4", "clock 4")
 
+        registry.applySettings(ClockSettings("clock_3", null))
         pluginListener.onPluginConnected(plugin1, mockContext)
         pluginListener.onPluginConnected(plugin2, mockContext)
 
@@ -244,7 +255,7 @@
 
     @Test
     fun jsonDeserialization_gotExpectedObject() {
-        val expected = ClockSettings("ID", null, 500)
+        val expected = ClockSettings("ID", null).apply { _applied_timestamp = 500 }
         val actual = ClockSettings.deserialize("""{
             "clockId":"ID",
             "_applied_timestamp":500
@@ -254,14 +265,14 @@
 
     @Test
     fun jsonDeserialization_noTimestamp_gotExpectedObject() {
-        val expected = ClockSettings("ID", null, null)
+        val expected = ClockSettings("ID", null)
         val actual = ClockSettings.deserialize("{\"clockId\":\"ID\"}")
         assertEquals(expected, actual)
     }
 
     @Test
     fun jsonDeserialization_nullTimestamp_gotExpectedObject() {
-        val expected = ClockSettings("ID", null, null)
+        val expected = ClockSettings("ID", null)
         val actual = ClockSettings.deserialize("""{
             "clockId":"ID",
             "_applied_timestamp":null
@@ -271,7 +282,7 @@
 
     @Test(expected = JSONException::class)
     fun jsonDeserialization_noId_threwException() {
-        val expected = ClockSettings("ID", null, 500)
+        val expected = ClockSettings(null, null).apply { _applied_timestamp = 500 }
         val actual = ClockSettings.deserialize("{\"_applied_timestamp\":500}")
         assertEquals(expected, actual)
     }
@@ -279,14 +290,15 @@
     @Test
     fun jsonSerialization_gotExpectedString() {
         val expected = "{\"clockId\":\"ID\",\"_applied_timestamp\":500}"
-        val actual = ClockSettings.serialize(ClockSettings("ID", null, 500))
+        val actual = ClockSettings.serialize(ClockSettings("ID", null)
+            .apply { _applied_timestamp = 500 })
         assertEquals(expected, actual)
     }
 
     @Test
     fun jsonSerialization_noTimestamp_gotExpectedString() {
         val expected = "{\"clockId\":\"ID\"}"
-        val actual = ClockSettings.serialize(ClockSettings("ID", null, null))
+        val actual = ClockSettings.serialize(ClockSettings("ID", null))
         assertEquals(expected, actual)
     }
 }
diff --git a/services/core/java/com/android/server/dreams/DreamController.java b/services/core/java/com/android/server/dreams/DreamController.java
index 61f7928..f74356d 100644
--- a/services/core/java/com/android/server/dreams/DreamController.java
+++ b/services/core/java/com/android/server/dreams/DreamController.java
@@ -270,7 +270,7 @@
         try {
             service.asBinder().linkToDeath(mCurrentDream, 0);
             service.attach(mCurrentDream.mToken, mCurrentDream.mCanDoze,
-                    mCurrentDream.mDreamingStartedCallback);
+                    mCurrentDream.mIsPreviewMode, mCurrentDream.mDreamingStartedCallback);
         } catch (RemoteException ex) {
             Slog.e(TAG, "The dream service died unexpectedly.", ex);
             stopDream(true /*immediate*/, "attach failed");
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
index faa219e..cbbee5d 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
@@ -4831,6 +4831,13 @@
             Slog.w(TAG, "Ignoring setInputMethod of uid " + Binder.getCallingUid()
                     + " token: " + token);
             return;
+        } else {
+            // Called with current IME's token.
+            if (mMethodMap.get(id) != null
+                    && mSettings.getEnabledInputMethodListWithFilterLocked(
+                            (info) -> info.getId().equals(id)).isEmpty()) {
+                throw new IllegalStateException("Requested IME is not enabled: " + id);
+            }
         }
 
         final long ident = Binder.clearCallingIdentity();
diff --git a/services/tests/servicestests/src/com/android/server/dreams/DreamControllerTest.java b/services/tests/servicestests/src/com/android/server/dreams/DreamControllerTest.java
index 303a370..1ef1197 100644
--- a/services/tests/servicestests/src/com/android/server/dreams/DreamControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/dreams/DreamControllerTest.java
@@ -99,7 +99,24 @@
         mLooper.dispatchAll();
 
         // Verify that dream service is called to attach.
-        verify(mIDreamService).attach(eq(mToken), eq(false) /*doze*/, any());
+        verify(mIDreamService).attach(eq(mToken), eq(false) /*doze*/,
+                eq(false) /*preview*/, any());
+    }
+
+    @Test
+    public void startDream_attachOnServiceConnectedInPreviewMode() throws RemoteException {
+        // Call dream controller to start dreaming.
+        mDreamController.startDream(mToken, mDreamName, true /*isPreview*/, false /*doze*/,
+                0 /*userId*/, null /*wakeLock*/, mOverlayName, "test" /*reason*/);
+
+        // Mock service connected.
+        final ServiceConnection serviceConnection = captureServiceConnection();
+        serviceConnection.onServiceConnected(mDreamName, mIBinder);
+        mLooper.dispatchAll();
+
+        // Verify that dream service is called to attach.
+        verify(mIDreamService).attach(eq(mToken), eq(false) /*doze*/,
+                eq(true) /*preview*/, any());
     }
 
     @Test
@@ -129,7 +146,7 @@
 
         // Mock second dream started.
         verify(newDreamService).attach(eq(newToken), eq(false) /*doze*/,
-                mRemoteCallbackCaptor.capture());
+                eq(false) /*preview*/, mRemoteCallbackCaptor.capture());
         mRemoteCallbackCaptor.getValue().sendResult(null /*data*/);
         mLooper.dispatchAll();
 
diff --git a/tests/testables/src/android/testing/TestableSettingsProvider.java b/tests/testables/src/android/testing/TestableSettingsProvider.java
index fd92c65..c6f18fd 100644
--- a/tests/testables/src/android/testing/TestableSettingsProvider.java
+++ b/tests/testables/src/android/testing/TestableSettingsProvider.java
@@ -49,14 +49,15 @@
     }
 
     void clearValuesAndCheck(Context context) {
-        int userId = UserHandle.myUserId();
-        mValues.put(key("global", MY_UNIQUE_KEY, userId), MY_UNIQUE_KEY);
-        mValues.put(key("secure", MY_UNIQUE_KEY, userId), MY_UNIQUE_KEY);
-        mValues.put(key("system", MY_UNIQUE_KEY, userId), MY_UNIQUE_KEY);
-
+        // Ensure we swapped over to use TestableSettingsProvider
         Settings.Global.clearProviderForTest();
         Settings.Secure.clearProviderForTest();
         Settings.System.clearProviderForTest();
+
+        // putString will eventually invoking the mocked call() method and update mValues
+        Settings.Global.putString(context.getContentResolver(), MY_UNIQUE_KEY, MY_UNIQUE_KEY);
+        Settings.Secure.putString(context.getContentResolver(), MY_UNIQUE_KEY, MY_UNIQUE_KEY);
+        Settings.System.putString(context.getContentResolver(), MY_UNIQUE_KEY, MY_UNIQUE_KEY);
         // Verify that if any test is using TestableContext, they all have the correct settings
         // provider.
         assertEquals("Incorrect settings provider, test using incorrect Context?", MY_UNIQUE_KEY,