Merge "multiPointerDraggable set an order for PointerEventScopes" into main
diff --git a/core/api/current.txt b/core/api/current.txt
index f2c59dac..889b627 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -6844,6 +6844,9 @@
     method public android.app.Notification.MessagingStyle.Message setData(String, android.net.Uri);
   }
 
+  @FlaggedApi("android.app.api_rich_ongoing") public abstract static class Notification.RichOngoingStyle extends android.app.Notification.Style {
+  }
+
   public abstract static class Notification.Style {
     ctor @Deprecated public Notification.Style();
     method public android.app.Notification build();
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java
index db979a5..ef09dc4 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -10979,6 +10979,18 @@
     }
 
     /**
+     * An object that can apply a rich ongoing notification style to a {@link Notification.Builder}
+     * object.
+     */
+    @FlaggedApi(Flags.FLAG_API_RICH_ONGOING)
+    public abstract static class RichOngoingStyle extends Notification.Style {
+        /**
+         * @hide
+         */
+        public RichOngoingStyle() {}
+    }
+
+    /**
      * Notification style for custom views that are decorated by the system
      *
      * <p>Instead of providing a notification that is completely custom, a developer can set this
diff --git a/core/java/android/hardware/input/VirtualRotaryEncoderScrollEvent.java b/core/java/android/hardware/input/VirtualRotaryEncoderScrollEvent.java
index 3be911abe7..8c98abd 100644
--- a/core/java/android/hardware/input/VirtualRotaryEncoderScrollEvent.java
+++ b/core/java/android/hardware/input/VirtualRotaryEncoderScrollEvent.java
@@ -107,7 +107,8 @@
         }
 
         /**
-         * Sets the scroll amount, normalized from -1.0 to 1.0, inclusive.
+         * Sets the scroll amount, normalized from -1.0 to 1.0, inclusive. By default, the scroll
+         * amount is 0, which results in no scroll.
          * <p>
          * Positive values indicate scrolling forward (e.g. down in a vertical list); negative
          * values, backward.
diff --git a/core/java/android/hardware/input/input_framework.aconfig b/core/java/android/hardware/input/input_framework.aconfig
index 16d9ef2..0cd2800 100644
--- a/core/java/android/hardware/input/input_framework.aconfig
+++ b/core/java/android/hardware/input/input_framework.aconfig
@@ -68,3 +68,10 @@
     description: "Controls if the mouse keys accessibility feature for physical keyboard is available to the user"
     bug: "341799888"
 }
+
+flag {
+    namespace: "input_native"
+    name: "touchpad_visualizer"
+    description: "Enables a developer overlay that displays raw touchpad input data and gesture recognition status in real-time."
+    bug: "286551975"
+}
diff --git a/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java
index 984bf65..3aa42c6 100644
--- a/core/java/android/os/UserManager.java
+++ b/core/java/android/os/UserManager.java
@@ -5668,6 +5668,33 @@
         }
     }
 
+    private static final String CACHE_KEY_QUIET_MODE_ENABLED_PROPERTY =
+        PropertyInvalidatedCache.createPropertyName(
+            PropertyInvalidatedCache.MODULE_SYSTEM, "quiet_mode_enabled");
+
+    private final PropertyInvalidatedCache<Integer, Boolean> mQuietModeEnabledCache =
+            new PropertyInvalidatedCache<Integer, Boolean>(
+                32, CACHE_KEY_QUIET_MODE_ENABLED_PROPERTY) {
+                @Override
+                public Boolean recompute(Integer query) {
+                    try {
+                        return mService.isQuietModeEnabled(query);
+                    } catch (RemoteException re) {
+                        throw re.rethrowFromSystemServer();
+                    }
+                }
+                @Override
+                public boolean bypass(Integer query) {
+                    return query < 0;
+                }
+            };
+
+
+    /** @hide */
+    public static final void invalidateQuietModeEnabledCache() {
+        PropertyInvalidatedCache.invalidateCache(CACHE_KEY_QUIET_MODE_ENABLED_PROPERTY);
+    }
+
     /**
      * Returns whether the given profile is in quiet mode or not.
      *
@@ -5675,6 +5702,13 @@
      * @return true if the profile is in quiet mode, false otherwise.
      */
     public boolean isQuietModeEnabled(UserHandle userHandle) {
+        if (android.multiuser.Flags.cacheQuietModeState()){
+            final int userId = userHandle.getIdentifier();
+            if (userId < 0) {
+                return false;
+            }
+            return mQuietModeEnabledCache.query(userId);
+        }
         try {
             return mService.isQuietModeEnabled(userHandle.getIdentifier());
         } catch (RemoteException re) {
diff --git a/core/java/android/window/flags/windowing_frontend.aconfig b/core/java/android/window/flags/windowing_frontend.aconfig
index 0322e4e..8fd525c 100644
--- a/core/java/android/window/flags/windowing_frontend.aconfig
+++ b/core/java/android/window/flags/windowing_frontend.aconfig
@@ -216,3 +216,12 @@
     purpose: PURPOSE_BUGFIX
   }
 }
+flag {
+  name: "custom_animations_behind_translucent"
+  namespace: "windowing_frontend"
+  description: "A change can use its own layer parameters to animate behind a translucent activity"
+  bug: "327332488"
+  metadata {
+    purpose: PURPOSE_BUGFIX
+  }
+}
diff --git a/core/java/com/android/internal/protolog/PerfettoProtoLogImpl.java b/core/java/com/android/internal/protolog/PerfettoProtoLogImpl.java
index 6abba8b..50fb8d5 100644
--- a/core/java/com/android/internal/protolog/PerfettoProtoLogImpl.java
+++ b/core/java/com/android/internal/protolog/PerfettoProtoLogImpl.java
@@ -104,8 +104,8 @@
     private final Runnable mCacheUpdater;
 
     private final int[] mDefaultLogLevelCounts = new int[LogLevel.values().length];
-    private final Map<IProtoLogGroup, int[]> mLogLevelCounts = new ArrayMap<>();
-    private final Map<IProtoLogGroup, Integer> mCollectStackTraceGroupCounts = new ArrayMap<>();
+    private final Map<String, int[]> mLogLevelCounts = new ArrayMap<>();
+    private final Map<String, Integer> mCollectStackTraceGroupCounts = new ArrayMap<>();
 
     private final Lock mBackgroundServiceLock = new ReentrantLock();
     private ExecutorService mBackgroundLoggingService = Executors.newSingleThreadExecutor();
@@ -226,7 +226,7 @@
 
     @Override
     public boolean isEnabled(IProtoLogGroup group, LogLevel level) {
-        final int[] groupLevelCount = mLogLevelCounts.get(group);
+        final int[] groupLevelCount = mLogLevelCounts.get(group.name());
         return (groupLevelCount == null && mDefaultLogLevelCounts[level.ordinal()] > 0)
                 || (groupLevelCount != null && groupLevelCount[level.ordinal()] > 0)
                 || group.isLogToLogcat();
@@ -279,7 +279,7 @@
         if (isProtoEnabled()) {
             long tsNanos = SystemClock.elapsedRealtimeNanos();
             final String stacktrace;
-            if (mCollectStackTraceGroupCounts.getOrDefault(group, 0) > 0) {
+            if (mCollectStackTraceGroupCounts.getOrDefault(group.name(), 0) > 0) {
                 stacktrace = collectStackTrace();
             } else {
                 stacktrace = null;
@@ -739,15 +739,8 @@
         final Set<String> overriddenGroupTags = config.getGroupTagsWithOverriddenConfigs();
 
         for (String overriddenGroupTag : overriddenGroupTags) {
-            IProtoLogGroup group = mLogGroups.get(overriddenGroupTag);
-
-            if (group == null) {
-                throw new IllegalArgumentException("Trying to set config for \""
-                        + overriddenGroupTag + "\" that isn't registered");
-            }
-
-            mLogLevelCounts.putIfAbsent(group, new int[LogLevel.values().length]);
-            final int[] logLevelsCountsForGroup = mLogLevelCounts.get(group);
+            mLogLevelCounts.putIfAbsent(overriddenGroupTag, new int[LogLevel.values().length]);
+            final int[] logLevelsCountsForGroup = mLogLevelCounts.get(overriddenGroupTag);
 
             final LogLevel logFromLevel = config.getConfigFor(overriddenGroupTag).logFrom;
             for (int i = logFromLevel.ordinal(); i < LogLevel.values().length; i++) {
@@ -755,13 +748,13 @@
             }
 
             if (config.getConfigFor(overriddenGroupTag).collectStackTrace) {
-                mCollectStackTraceGroupCounts.put(group,
-                        mCollectStackTraceGroupCounts.getOrDefault(group, 0) + 1);
+                mCollectStackTraceGroupCounts.put(overriddenGroupTag,
+                        mCollectStackTraceGroupCounts.getOrDefault(overriddenGroupTag, 0) + 1);
             }
 
             if (config.getConfigFor(overriddenGroupTag).collectStackTrace) {
-                mCollectStackTraceGroupCounts.put(group,
-                        mCollectStackTraceGroupCounts.getOrDefault(group, 0) + 1);
+                mCollectStackTraceGroupCounts.put(overriddenGroupTag,
+                        mCollectStackTraceGroupCounts.getOrDefault(overriddenGroupTag, 0) + 1);
             }
         }
 
@@ -781,24 +774,22 @@
         final Set<String> overriddenGroupTags = config.getGroupTagsWithOverriddenConfigs();
 
         for (String overriddenGroupTag : overriddenGroupTags) {
-            IProtoLogGroup group = mLogGroups.get(overriddenGroupTag);
-
-            final int[] logLevelsCountsForGroup = mLogLevelCounts.get(group);
+            final int[] logLevelsCountsForGroup = mLogLevelCounts.get(overriddenGroupTag);
 
             final LogLevel logFromLevel = config.getConfigFor(overriddenGroupTag).logFrom;
-            for (int i = defaultLogFrom.ordinal(); i < LogLevel.values().length; i++) {
+            for (int i = logFromLevel.ordinal(); i < LogLevel.values().length; i++) {
                 logLevelsCountsForGroup[i]--;
             }
             if (Arrays.stream(logLevelsCountsForGroup).allMatch(it -> it == 0)) {
-                mLogLevelCounts.remove(group);
+                mLogLevelCounts.remove(overriddenGroupTag);
             }
 
             if (config.getConfigFor(overriddenGroupTag).collectStackTrace) {
-                mCollectStackTraceGroupCounts.put(group,
-                        mCollectStackTraceGroupCounts.get(group) - 1);
+                mCollectStackTraceGroupCounts.put(overriddenGroupTag,
+                        mCollectStackTraceGroupCounts.get(overriddenGroupTag) - 1);
 
-                if (mCollectStackTraceGroupCounts.get(group) == 0) {
-                    mCollectStackTraceGroupCounts.remove(group);
+                if (mCollectStackTraceGroupCounts.get(overriddenGroupTag) == 0) {
+                    mCollectStackTraceGroupCounts.remove(overriddenGroupTag);
                 }
             }
         }
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 18157d6..dc3e2d0 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
@@ -159,18 +159,6 @@
             }
         }
 
-    private val transitionAreaHeight
-        get() =
-            context.resources.getDimensionPixelSize(
-                com.android.wm.shell.R.dimen.desktop_mode_fullscreen_from_desktop_height
-            )
-
-    private val transitionAreaWidth
-        get() =
-            context.resources.getDimensionPixelSize(
-                com.android.wm.shell.R.dimen.desktop_mode_transition_area_width
-            )
-
     /** Task id of the task currently being dragged from fullscreen/split. */
     val draggingTaskId
         get() = dragToDesktopTransitionHandler.draggingTaskId
@@ -776,12 +764,15 @@
             newTaskIdInFront ?: "null"
         )
 
-        if (Flags.enableDesktopWindowingWallpaperActivity()) {
-            // Add translucent wallpaper activity to show the wallpaper underneath
-            addWallpaperActivity(wct)
-        } else {
-            // Move home to front
-            moveHomeTask(wct, toTop = true)
+        // Currently, we only handle the desktop on the default display really.
+        if (displayId == DEFAULT_DISPLAY) {
+            if (Flags.enableDesktopWindowingWallpaperActivity()) {
+                // Add translucent wallpaper activity to show the wallpaper underneath
+                addWallpaperActivity(wct)
+            } else {
+                // Move home to front
+                moveHomeTask(wct, toTop = true)
+            }
         }
 
         val nonMinimizedTasksOrderedFrontToBack =
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 6cabbf9..4d1b6ba 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
@@ -311,6 +311,23 @@
   }
 
   @Test
+  @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
+  fun showDesktopApps_onSecondaryDisplay_desktopWallpaperEnabled_shouldNotShowWallpaper() {
+    val task1 = setUpFreeformTask(SECOND_DISPLAY)
+    val task2 = setUpFreeformTask(SECOND_DISPLAY)
+    markTaskHidden(task1)
+    markTaskHidden(task2)
+
+    controller.showDesktopApps(SECOND_DISPLAY, RemoteTransition(TestRemoteTransition()))
+
+    val wct = getLatestWct(type = TRANSIT_TO_FRONT, handlerClass = OneShotRemoteHandler::class.java)
+    assertThat(wct.hierarchyOps).hasSize(2)
+    // Expect order to be from bottom: task1, task2 (no wallpaper intent)
+    wct.assertReorderAt(index = 0, task1)
+    wct.assertReorderAt(index = 1, task2)
+  }
+
+  @Test
   @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
   fun showDesktopApps_appsAlreadyVisible_bringsToFront_desktopWallpaperDisabled() {
     val homeTask = setUpHomeTask()
@@ -330,6 +347,22 @@
   }
 
   @Test
+  @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
+  fun showDesktopApps_onSecondaryDisplay_desktopWallpaperDisabled_shouldNotMoveLauncher() {
+    val task1 = setUpFreeformTask(SECOND_DISPLAY)
+    val task2 = setUpFreeformTask(SECOND_DISPLAY)
+    markTaskHidden(task1)
+    markTaskHidden(task2)
+
+    controller.showDesktopApps(SECOND_DISPLAY, RemoteTransition(TestRemoteTransition()))
+
+    val wct = getLatestWct(type = TRANSIT_TO_FRONT, handlerClass = OneShotRemoteHandler::class.java)
+    assertThat(wct.hierarchyOps).hasSize(2)
+    wct.assertReorderAt(index = 0, task1)
+    wct.assertReorderAt(index = 1, task2)
+  }
+
+  @Test
   @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
   fun showDesktopApps_appsAlreadyVisible_bringsToFront_desktopWallpaperEnabled() {
     val task1 = setUpFreeformTask()
diff --git a/nfc/java/android/nfc/NfcAdapter.java b/nfc/java/android/nfc/NfcAdapter.java
index 9ce1c82..395f81d 100644
--- a/nfc/java/android/nfc/NfcAdapter.java
+++ b/nfc/java/android/nfc/NfcAdapter.java
@@ -963,22 +963,9 @@
             throw new UnsupportedOperationException("You need a context on NfcAdapter to use the "
                     + " NFC extras APIs");
         }
-        try {
-            return sService.getNfcDtaInterface(mContext.getPackageName());
-        } catch (RemoteException e) {
-            attemptDeadServiceRecovery(e);
-            // Try one more time
-            if (sService == null) {
-                Log.e(TAG, "Failed to recover NFC Service.");
-                return null;
-            }
-            try {
-                return sService.getNfcDtaInterface(mContext.getPackageName());
-            } catch (RemoteException ee) {
-                Log.e(TAG, "Failed to recover NFC Service.");
-            }
-            return null;
-        }
+        return callServiceReturn(() ->  sService.getNfcDtaInterface(mContext.getPackageName()),
+                null);
+
     }
 
     /**
@@ -1095,22 +1082,8 @@
     @SystemApi
     @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE)
     public @AdapterState int getAdapterState() {
-        try {
-            return sService.getState();
-        } catch (RemoteException e) {
-            attemptDeadServiceRecovery(e);
-            // Try one more time
-            if (sService == null) {
-                Log.e(TAG, "Failed to recover NFC Service.");
-                return NfcAdapter.STATE_OFF;
-            }
-            try {
-                return sService.getState();
-            } catch (RemoteException ee) {
-                Log.e(TAG, "Failed to recover NFC Service.");
-            }
-            return NfcAdapter.STATE_OFF;
-        }
+        return callServiceReturn(() ->  sService.getState(), NfcAdapter.STATE_OFF);
+
     }
 
     /**
@@ -1134,22 +1107,8 @@
     @FlaggedApi(Flags.FLAG_NFC_STATE_CHANGE)
     @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS)
     public boolean enable() {
-        try {
-            return sService.enable(mContext.getPackageName());
-        } catch (RemoteException e) {
-            attemptDeadServiceRecovery(e);
-            // Try one more time
-            if (sService == null) {
-                Log.e(TAG, "Failed to recover NFC Service.");
-                return false;
-            }
-            try {
-                return sService.enable(mContext.getPackageName());
-            } catch (RemoteException ee) {
-                Log.e(TAG, "Failed to recover NFC Service.");
-            }
-            return false;
-        }
+        return callServiceReturn(() ->  sService.enable(mContext.getPackageName()), false);
+
     }
 
     /**
@@ -1175,22 +1134,9 @@
     @FlaggedApi(Flags.FLAG_NFC_STATE_CHANGE)
     @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS)
     public boolean disable() {
-        try {
-            return sService.disable(true, mContext.getPackageName());
-        } catch (RemoteException e) {
-            attemptDeadServiceRecovery(e);
-            // Try one more time
-            if (sService == null) {
-                Log.e(TAG, "Failed to recover NFC Service.");
-                return false;
-            }
-            try {
-                return sService.disable(true, mContext.getPackageName());
-            } catch (RemoteException ee) {
-                Log.e(TAG, "Failed to recover NFC Service.");
-            }
-            return false;
-        }
+        return callServiceReturn(() ->  sService.disable(true, mContext.getPackageName()),
+                false);
+
     }
 
     /**
@@ -1200,22 +1146,9 @@
     @SystemApi
     @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS)
     public boolean disable(boolean persist) {
-        try {
-            return sService.disable(persist, mContext.getPackageName());
-        } catch (RemoteException e) {
-            attemptDeadServiceRecovery(e);
-            // Try one more time
-            if (sService == null) {
-                Log.e(TAG, "Failed to recover NFC Service.");
-                return false;
-            }
-            try {
-                return sService.disable(persist, mContext.getPackageName());
-            } catch (RemoteException ee) {
-                Log.e(TAG, "Failed to recover NFC Service.");
-            }
-            return false;
-        }
+        return callServiceReturn(() ->  sService.disable(persist, mContext.getPackageName()),
+                false);
+
     }
 
     /**
@@ -1241,12 +1174,7 @@
      */
     @FlaggedApi(Flags.FLAG_NFC_OBSERVE_MODE)
     public boolean isObserveModeSupported() {
-        try {
-            return sService.isObserveModeSupported();
-        } catch (RemoteException e) {
-            attemptDeadServiceRecovery(e);
-            return false;
-        }
+        return callServiceReturn(() ->  sService.isObserveModeSupported(), false);
     }
 
     /**
@@ -1257,12 +1185,7 @@
 
     @FlaggedApi(Flags.FLAG_NFC_OBSERVE_MODE)
     public boolean isObserveModeEnabled() {
-        try {
-            return sService.isObserveModeEnabled();
-        } catch (RemoteException e) {
-            attemptDeadServiceRecovery(e);
-            return false;
-        }
+        return callServiceReturn(() ->  sService.isObserveModeEnabled(), false);
     }
 
     /**
@@ -1286,12 +1209,8 @@
             throw new UnsupportedOperationException("You need a context on NfcAdapter to use the "
                     + " observe mode APIs");
         }
-        try {
-            return sService.setObserveMode(enabled, mContext.getPackageName());
-        } catch (RemoteException e) {
-            attemptDeadServiceRecovery(e);
-            return false;
-        }
+        return callServiceReturn(() ->  sService.setObserveMode(enabled, mContext.getPackageName()),
+                false);
     }
 
     /**
@@ -2057,22 +1976,8 @@
         if (!sHasNfcFeature && !sHasCeFeature) {
             throw new UnsupportedOperationException();
         }
-        try {
-            return sService.setNfcSecure(enable);
-        } catch (RemoteException e) {
-            attemptDeadServiceRecovery(e);
-            // Try one more time
-            if (sService == null) {
-                Log.e(TAG, "Failed to recover NFC Service.");
-                return false;
-            }
-            try {
-                return sService.setNfcSecure(enable);
-            } catch (RemoteException ee) {
-                Log.e(TAG, "Failed to recover NFC Service.");
-            }
-            return false;
-        }
+        return callServiceReturn(() ->  sService.setNfcSecure(enable), false);
+
     }
 
     /**
@@ -2088,22 +1993,8 @@
         if (!sHasNfcFeature && !sHasCeFeature) {
             throw new UnsupportedOperationException();
         }
-        try {
-            return sService.deviceSupportsNfcSecure();
-        } catch (RemoteException e) {
-            attemptDeadServiceRecovery(e);
-            // Try one more time
-            if (sService == null) {
-                Log.e(TAG, "Failed to recover NFC Service.");
-                return false;
-            }
-            try {
-                return sService.deviceSupportsNfcSecure();
-            } catch (RemoteException ee) {
-                Log.e(TAG, "Failed to recover NFC Service.");
-            }
-            return false;
-        }
+        return callServiceReturn(() ->  sService.deviceSupportsNfcSecure(), false);
+
     }
 
     /**
@@ -2121,22 +2012,8 @@
         if (!sHasNfcFeature && !sHasCeFeature) {
             throw new UnsupportedOperationException();
         }
-        try {
-            return sService.getNfcAntennaInfo();
-        } catch (RemoteException e) {
-            attemptDeadServiceRecovery(e);
-            // Try one more time
-            if (sService == null) {
-                Log.e(TAG, "Failed to recover NFC Service.");
-                return null;
-            }
-            try {
-                return sService.getNfcAntennaInfo();
-            } catch (RemoteException ee) {
-                Log.e(TAG, "Failed to recover NFC Service.");
-            }
-            return null;
-        }
+        return callServiceReturn(() ->  sService.getNfcAntennaInfo(), null);
+
     }
 
     /**
@@ -2154,22 +2031,8 @@
         if (!sHasNfcFeature && !sHasCeFeature) {
             throw new UnsupportedOperationException();
         }
-        try {
-            return sService.isNfcSecureEnabled();
-        } catch (RemoteException e) {
-            attemptDeadServiceRecovery(e);
-            // Try one more time
-            if (sService == null) {
-                Log.e(TAG, "Failed to recover NFC Service.");
-                return false;
-            }
-            try {
-                return sService.isNfcSecureEnabled();
-            } catch (RemoteException ee) {
-                Log.e(TAG, "Failed to recover NFC Service.");
-            }
-            return false;
-        }
+        return callServiceReturn(() ->  sService.isNfcSecureEnabled(), false);
+
     }
 
     /**
@@ -2185,22 +2048,8 @@
         if (!sHasNfcFeature) {
             throw new UnsupportedOperationException();
         }
-        try {
-            return sService.enableReaderOption(enable);
-        } catch (RemoteException e) {
-            attemptDeadServiceRecovery(e);
-            // Try one more time
-            if (sService == null) {
-                Log.e(TAG, "Failed to recover NFC Service.");
-                return false;
-            }
-            try {
-                return sService.enableReaderOption(enable);
-            } catch (RemoteException ee) {
-                Log.e(TAG, "Failed to recover NFC Service.");
-            }
-            return false;
-        }
+        return callServiceReturn(() ->  sService.enableReaderOption(enable), false);
+
     }
 
     /**
@@ -2214,22 +2063,8 @@
         if (!sHasNfcFeature) {
             throw new UnsupportedOperationException();
         }
-        try {
-            return sService.isReaderOptionSupported();
-        } catch (RemoteException e) {
-            attemptDeadServiceRecovery(e);
-            // Try one more time
-            if (sService == null) {
-                Log.e(TAG, "Failed to recover NFC Service.");
-                return false;
-            }
-            try {
-                return sService.isReaderOptionSupported();
-            } catch (RemoteException ee) {
-                Log.e(TAG, "Failed to recover NFC Service.");
-            }
-            return false;
-        }
+        return callServiceReturn(() ->  sService.isReaderOptionSupported(), false);
+
     }
 
     /**
@@ -2245,22 +2080,8 @@
         if (!sHasNfcFeature) {
             throw new UnsupportedOperationException();
         }
-        try {
-            return sService.isReaderOptionEnabled();
-        } catch (RemoteException e) {
-            attemptDeadServiceRecovery(e);
-            // Try one more time
-            if (sService == null) {
-                Log.e(TAG, "Failed to recover NFC Service.");
-                return false;
-            }
-            try {
-                return sService.isReaderOptionEnabled();
-            } catch (RemoteException ee) {
-                Log.e(TAG, "Failed to recover NFC Service.");
-            }
-            return false;
-        }
+        return callServiceReturn(() ->  sService.isReaderOptionEnabled(), false);
+
     }
 
     /**
@@ -2388,11 +2209,9 @@
         synchronized (mLock) {
             mTagRemovedListener = iListener;
         }
-        try {
-            return sService.ignore(tag.getServiceHandle(), debounceMs, iListener);
-        } catch (RemoteException e) {
-            return false;
-        }
+        final ITagRemovedCallback.Stub passedListener = iListener;
+        return callServiceReturn(() ->
+                sService.ignore(tag.getServiceHandle(), debounceMs, passedListener), false);
     }
 
     /**
@@ -2509,22 +2328,9 @@
             throw new UnsupportedOperationException("You need a context on NfcAdapter to use the "
                     + " NFC extras APIs");
         }
-        try {
-            return sService.getNfcAdapterExtrasInterface(mContext.getPackageName());
-        } catch (RemoteException e) {
-            attemptDeadServiceRecovery(e);
-            // Try one more time
-            if (sService == null) {
-                Log.e(TAG, "Failed to recover NFC Service.");
-                return null;
-            }
-            try {
-                return sService.getNfcAdapterExtrasInterface(mContext.getPackageName());
-            } catch (RemoteException ee) {
-                Log.e(TAG, "Failed to recover NFC Service.");
-            }
-            return null;
-        }
+        return callServiceReturn(() ->
+                sService.getNfcAdapterExtrasInterface(mContext.getPackageName()), null);
+
     }
 
     void enforceResumed(Activity activity) {
@@ -2569,22 +2375,8 @@
         if (!sHasNfcFeature && !sHasCeFeature) {
             throw new UnsupportedOperationException();
         }
-        try {
-            return sService.setControllerAlwaysOn(value);
-        } catch (RemoteException e) {
-            attemptDeadServiceRecovery(e);
-            // Try one more time
-            if (sService == null) {
-                Log.e(TAG, "Failed to recover NFC Service.");
-                return false;
-            }
-            try {
-                return sService.setControllerAlwaysOn(value);
-            } catch (RemoteException ee) {
-                Log.e(TAG, "Failed to recover NFC Service.");
-            }
-            return false;
-        }
+        return callServiceReturn(() ->  sService.setControllerAlwaysOn(value), false);
+
     }
 
     /**
@@ -2600,22 +2392,8 @@
     @SystemApi
     @RequiresPermission(android.Manifest.permission.NFC_SET_CONTROLLER_ALWAYS_ON)
     public boolean isControllerAlwaysOn() {
-        try {
-            return sService.isControllerAlwaysOn();
-        } catch (RemoteException e) {
-            attemptDeadServiceRecovery(e);
-            // Try one more time
-            if (sService == null) {
-                Log.e(TAG, "Failed to recover NFC Service.");
-                return false;
-            }
-            try {
-                return sService.isControllerAlwaysOn();
-            } catch (RemoteException ee) {
-                Log.e(TAG, "Failed to recover NFC Service.");
-            }
-            return false;
-        }
+        return callServiceReturn(() ->  sService.isControllerAlwaysOn(), false);
+
     }
 
     /**
@@ -2634,22 +2412,8 @@
         if (!sHasNfcFeature && !sHasCeFeature) {
             throw new UnsupportedOperationException();
         }
-        try {
-            return sService.isControllerAlwaysOnSupported();
-        } catch (RemoteException e) {
-            attemptDeadServiceRecovery(e);
-            // Try one more time
-            if (sService == null) {
-                Log.e(TAG, "Failed to recover NFC Service.");
-                return false;
-            }
-            try {
-                return sService.isControllerAlwaysOnSupported();
-            } catch (RemoteException ee) {
-                Log.e(TAG, "Failed to recover NFC Service.");
-            }
-            return false;
-        }
+        return callServiceReturn(() ->  sService.isControllerAlwaysOnSupported(), false);
+
     }
 
     /**
@@ -2719,21 +2483,9 @@
             Log.e(TAG, "TagIntentAppPreference is not supported");
             throw new UnsupportedOperationException();
         }
-        try {
-            return sService.setTagIntentAppPreferenceForUser(userId, pkg, allow);
-        } catch (RemoteException e) {
-            attemptDeadServiceRecovery(e);
-            // Try one more time
-            if (sService == null) {
-                Log.e(TAG, "Failed to recover NFC Service.");
-            }
-            try {
-                return sService.setTagIntentAppPreferenceForUser(userId, pkg, allow);
-            } catch (RemoteException ee) {
-                Log.e(TAG, "Failed to recover NFC Service.");
-            }
-            return TAG_INTENT_APP_PREF_RESULT_UNAVAILABLE;
-        }
+        return callServiceReturn(() ->
+                sService.setTagIntentAppPreferenceForUser(userId, pkg, allow),
+                        TAG_INTENT_APP_PREF_RESULT_UNAVAILABLE);
     }
 
 
@@ -2808,22 +2560,8 @@
         if (!sHasNfcFeature) {
             throw new UnsupportedOperationException();
         }
-        try {
-            return sService.isTagIntentAppPreferenceSupported();
-        } catch (RemoteException e) {
-            attemptDeadServiceRecovery(e);
-            // Try one more time
-            if (sService == null) {
-                Log.e(TAG, "Failed to recover NFC Service.");
-                return false;
-            }
-            try {
-                return sService.isTagIntentAppPreferenceSupported();
-            } catch (RemoteException ee) {
-                Log.e(TAG, "Failed to recover NFC Service.");
-            }
-            return false;
-        }
+        return callServiceReturn(() ->  sService.isTagIntentAppPreferenceSupported(), false);
+
     }
 
    /**
@@ -2836,26 +2574,10 @@
     @TestApi
     @FlaggedApi(Flags.FLAG_NFC_READ_POLLING_LOOP)
     public void notifyPollingLoop(@NonNull PollingFrame pollingFrame) {
-        try {
-            if (sService == null) {
-                attemptDeadServiceRecovery(null);
-            }
-            sService.notifyPollingLoop(pollingFrame);
-        } catch (RemoteException e) {
-            attemptDeadServiceRecovery(e);
-            // Try one more time
-            if (sService == null) {
-                Log.e(TAG, "Failed to recover NFC Service.");
-                return;
-            }
-            try {
-                sService.notifyPollingLoop(pollingFrame);
-            } catch (RemoteException ee) {
-                Log.e(TAG, "Failed to recover NFC Service.");
-            }
-        }
+        callService(() ->  sService.notifyPollingLoop(pollingFrame));
     }
 
+
    /**
      * Notifies the system of new HCE data for tests.
      *
@@ -2863,11 +2585,19 @@
      */
     @FlaggedApi(Flags.FLAG_NFC_READ_POLLING_LOOP)
     public void notifyTestHceData(int technology, byte[] data) {
+        callService(() ->  sService.notifyTestHceData(technology, data));
+    }
+
+    interface ServiceCall {
+        void call() throws RemoteException;
+    }
+
+    void callService(ServiceCall call) {
         try {
             if (sService == null) {
                 attemptDeadServiceRecovery(null);
             }
-            sService.notifyTestHceData(technology, data);
+            call.call();
         } catch (RemoteException e) {
             attemptDeadServiceRecovery(e);
             // Try one more time
@@ -2876,12 +2606,36 @@
                 return;
             }
             try {
-                sService.notifyTestHceData(technology, data);
-            } catch (RemoteException e2) {
+                call.call();
+            } catch (RemoteException ee) {
                 Log.e(TAG, "Failed to recover NFC Service.");
             }
         }
     }
+    interface ServiceCallReturn<T> {
+        T call() throws RemoteException;
+    }
+    <T> T callServiceReturn(ServiceCallReturn<T> call, T defaultReturn) {
+        try {
+            if (sService == null) {
+                attemptDeadServiceRecovery(null);
+            }
+            return call.call();
+        } catch (RemoteException e) {
+            attemptDeadServiceRecovery(e);
+            // Try one more time
+            if (sService == null) {
+                Log.e(TAG, "Failed to recover NFC Service.");
+                return defaultReturn;
+            }
+            try {
+                return call.call();
+            } catch (RemoteException ee) {
+                Log.e(TAG, "Failed to recover NFC Service.");
+            }
+        }
+        return defaultReturn;
+    }
 
    /**
      * Notifies the system of a an HCE session being deactivated.
@@ -2891,24 +2645,7 @@
     @TestApi
     @FlaggedApi(Flags.FLAG_NFC_READ_POLLING_LOOP)
     public void notifyHceDeactivated() {
-        try {
-            if (sService == null) {
-                attemptDeadServiceRecovery(null);
-            }
-            sService.notifyHceDeactivated();
-        } catch (RemoteException e) {
-            attemptDeadServiceRecovery(e);
-            // Try one more time
-            if (sService == null) {
-                Log.e(TAG, "Failed to recover NFC Service.");
-                return;
-            }
-            try {
-                sService.notifyHceDeactivated();
-            } catch (RemoteException ee) {
-                Log.e(TAG, "Failed to recover NFC Service.");
-            }
-        }
+        callService(() ->  sService.notifyHceDeactivated());
     }
 
     /**
@@ -2924,22 +2661,7 @@
         if (!sHasNfcWlcFeature) {
             throw new UnsupportedOperationException();
         }
-        try {
-            return sService.setWlcEnabled(enable);
-        } catch (RemoteException e) {
-            attemptDeadServiceRecovery(e);
-            // Try one more time
-            if (sService == null) {
-                Log.e(TAG, "Failed to recover NFC Service.");
-                return false;
-            }
-            try {
-                return sService.setWlcEnabled(enable);
-            } catch (RemoteException ee) {
-                Log.e(TAG, "Failed to recover NFC Service.");
-            }
-            return false;
-        }
+        return callServiceReturn(() ->  sService.setWlcEnabled(enable), false);
     }
 
     /**
@@ -2954,22 +2676,8 @@
         if (!sHasNfcWlcFeature) {
             throw new UnsupportedOperationException();
         }
-        try {
-            return sService.isWlcEnabled();
-        } catch (RemoteException e) {
-            attemptDeadServiceRecovery(e);
-            // Try one more time
-            if (sService == null) {
-                Log.e(TAG, "Failed to recover NFC Service.");
-                return false;
-            }
-            try {
-                return sService.isWlcEnabled();
-            } catch (RemoteException ee) {
-                Log.e(TAG, "Failed to recover NFC Service.");
-            }
-            return false;
-        }
+        return callServiceReturn(() ->  sService.isWlcEnabled(), false);
+
     }
 
     /**
@@ -3048,22 +2756,8 @@
         if (!sHasNfcWlcFeature) {
             throw new UnsupportedOperationException();
         }
-        try {
-            return sService.getWlcListenerDeviceInfo();
-        } catch (RemoteException e) {
-            attemptDeadServiceRecovery(e);
-            // Try one more time
-            if (sService == null) {
-                Log.e(TAG, "Failed to recover NFC Service.");
-                return null;
-            }
-            try {
-                return sService.getWlcListenerDeviceInfo();
-            } catch (RemoteException ee) {
-                Log.e(TAG, "Failed to recover NFC Service.");
-            }
-            return null;
-        }
+        return callServiceReturn(() ->  sService.getWlcListenerDeviceInfo(), null);
+
     }
 
     /**
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/RouterInfoMediaManager.java b/packages/SettingsLib/src/com/android/settingslib/media/RouterInfoMediaManager.java
index 6571dd7..eced7b3 100644
--- a/packages/SettingsLib/src/com/android/settingslib/media/RouterInfoMediaManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/media/RouterInfoMediaManager.java
@@ -29,6 +29,7 @@
 import android.os.UserHandle;
 import android.text.TextUtils;
 
+import androidx.annotation.GuardedBy;
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 
@@ -41,7 +42,6 @@
 import java.util.List;
 import java.util.concurrent.Executor;
 import java.util.concurrent.Executors;
-import java.util.concurrent.atomic.AtomicReference;
 import java.util.function.Consumer;
 import java.util.stream.Collectors;
 
@@ -65,7 +65,9 @@
                 refreshDevices();
             };
 
-    private final AtomicReference<MediaRouter2.ScanToken> mScanToken = new AtomicReference<>();
+    @GuardedBy("this")
+    @Nullable
+    private MediaRouter2.ScanToken mScanToken;
 
     // TODO (b/321969740): Plumb target UserHandle between UMO and RouterInfoMediaManager.
     /* package */ RouterInfoMediaManager(
@@ -101,8 +103,13 @@
     @Override
     protected void startScanOnRouter() {
         if (Flags.enableScreenOffScanning()) {
-            MediaRouter2.ScanRequest request = new MediaRouter2.ScanRequest.Builder().build();
-            mScanToken.compareAndSet(null, mRouter.requestScan(request));
+            synchronized (this) {
+                if (mScanToken == null) {
+                    MediaRouter2.ScanRequest request =
+                            new MediaRouter2.ScanRequest.Builder().build();
+                    mScanToken = mRouter.requestScan(request);
+                }
+            }
         } else {
             mRouter.startScan();
         }
@@ -120,9 +127,11 @@
     @Override
     protected void stopScanOnRouter() {
         if (Flags.enableScreenOffScanning()) {
-            MediaRouter2.ScanToken token = mScanToken.getAndSet(null);
-            if (token != null) {
-                mRouter.cancelScanRequest(token);
+            synchronized (this) {
+                if (mScanToken != null) {
+                    mRouter.cancelScanRequest(mScanToken);
+                    mScanToken = null;
+                }
             }
         } else {
             mRouter.stopScan();
diff --git a/packages/SettingsLib/src/com/android/settingslib/notification/data/repository/ZenModeRepository.kt b/packages/SettingsLib/src/com/android/settingslib/notification/data/repository/ZenModeRepository.kt
index 273a63d..72c3c17 100644
--- a/packages/SettingsLib/src/com/android/settingslib/notification/data/repository/ZenModeRepository.kt
+++ b/packages/SettingsLib/src/com/android/settingslib/notification/data/repository/ZenModeRepository.kt
@@ -56,7 +56,7 @@
     val backgroundHandler: Handler?,
 ) : ZenModeRepository {
 
-    private val notificationBroadcasts =
+    private val notificationBroadcasts by lazy {
         callbackFlow {
                 val receiver =
                     object : BroadcastReceiver() {
@@ -95,8 +95,9 @@
                     )
                 }
             }
+    }
 
-    override val consolidatedNotificationPolicy: StateFlow<NotificationManager.Policy?> =
+    override val consolidatedNotificationPolicy: StateFlow<NotificationManager.Policy?> by lazy {
         if (Flags.volumePanelBroadcastFix() && android.app.Flags.modesApi())
             flowFromBroadcast(NotificationManager.ACTION_CONSOLIDATED_NOTIFICATION_POLICY_CHANGED) {
                 notificationManager.consolidatedNotificationPolicy
@@ -105,11 +106,13 @@
             flowFromBroadcast(NotificationManager.ACTION_NOTIFICATION_POLICY_CHANGED) {
                 notificationManager.consolidatedNotificationPolicy
             }
+    }
 
-    override val globalZenMode: StateFlow<Int?> =
+    override val globalZenMode: StateFlow<Int?> by lazy {
         flowFromBroadcast(NotificationManager.ACTION_INTERRUPTION_FILTER_CHANGED) {
             notificationManager.zenMode
         }
+    }
 
     private fun <T> flowFromBroadcast(intentAction: String, mapper: () -> T) =
         notificationBroadcasts
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/notification/data/repository/ZenModeRepositoryTest.kt b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/notification/data/repository/ZenModeRepositoryTest.kt
index 096c25d..06333b61 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/notification/data/repository/ZenModeRepositoryTest.kt
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/notification/data/repository/ZenModeRepositoryTest.kt
@@ -48,7 +48,6 @@
 @RunWith(RobolectricTestRunner::class)
 @SmallTest
 class ZenModeRepositoryTest {
-
     @Mock private lateinit var context: Context
 
     @Mock private lateinit var notificationManager: NotificationManager
@@ -73,7 +72,7 @@
             )
     }
 
-    @DisableFlags(android.app.Flags.FLAG_MODES_API, Flags.FLAG_VOLUME_PANEL_BROADCAST_FIX)
+    @DisableFlags(Flags.FLAG_VOLUME_PANEL_BROADCAST_FIX)
     @Test
     fun consolidatedPolicyChanges_repositoryEmits_flagsOff() {
         testScope.runTest {
@@ -88,9 +87,7 @@
             triggerIntent(NotificationManager.ACTION_NOTIFICATION_POLICY_CHANGED)
             runCurrent()
 
-            assertThat(values)
-                .containsExactlyElementsIn(listOf(null, testPolicy1, testPolicy2))
-                .inOrder()
+            assertThat(values).containsExactly(null, testPolicy1, testPolicy2).inOrder()
         }
     }
 
@@ -109,9 +106,7 @@
             triggerIntent(NotificationManager.ACTION_CONSOLIDATED_NOTIFICATION_POLICY_CHANGED)
             runCurrent()
 
-            assertThat(values)
-                .containsExactlyElementsIn(listOf(null, testPolicy1, testPolicy2))
-                .inOrder()
+            assertThat(values).containsExactly(null, testPolicy1, testPolicy2).inOrder()
         }
     }
 
@@ -128,14 +123,13 @@
             runCurrent()
 
             assertThat(values)
-                .containsExactlyElementsIn(
-                    listOf(null, Global.ZEN_MODE_OFF, Global.ZEN_MODE_ALARMS))
+                .containsExactly(null, Global.ZEN_MODE_OFF, Global.ZEN_MODE_ALARMS)
                 .inOrder()
         }
     }
 
     private fun triggerIntent(action: String) {
-        verify(context).registerReceiver(receiverCaptor.capture(), any())
+        verify(context).registerReceiver(receiverCaptor.capture(), any(), any(), any())
         receiverCaptor.value.onReceive(context, Intent(action))
     }
 
diff --git a/packages/SystemUI/aconfig/systemui.aconfig b/packages/SystemUI/aconfig/systemui.aconfig
index e2ecda3..63ce7eb 100644
--- a/packages/SystemUI/aconfig/systemui.aconfig
+++ b/packages/SystemUI/aconfig/systemui.aconfig
@@ -1143,3 +1143,13 @@
        purpose: PURPOSE_BUGFIX
    }
 }
+
+flag {
+  namespace: "systemui"
+  name: "qs_register_setting_observer_on_bg_thread"
+  description: "Registers Quick Settings content providers on background thread"
+  bug: "351766769"
+  metadata {
+    purpose: PURPOSE_BUGFIX
+  }
+}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/media/controls/ui/composable/MediaCarousel.kt b/packages/SystemUI/compose/features/src/com/android/systemui/media/controls/ui/composable/MediaCarousel.kt
index 581f3a5..d629eec 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/media/controls/ui/composable/MediaCarousel.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/media/controls/ui/composable/MediaCarousel.kt
@@ -16,8 +16,10 @@
 
 package com.android.systemui.media.controls.ui.composable
 
+import android.view.View
 import android.view.ViewGroup
 import android.widget.FrameLayout
+import androidx.compose.foundation.layout.fillMaxSize
 import androidx.compose.foundation.layout.fillMaxWidth
 import androidx.compose.foundation.layout.height
 import androidx.compose.runtime.Composable
@@ -26,7 +28,6 @@
 import androidx.compose.ui.platform.LocalDensity
 import androidx.compose.ui.res.dimensionResource
 import androidx.compose.ui.viewinterop.AndroidView
-import androidx.core.view.contains
 import com.android.compose.animation.scene.ElementKey
 import com.android.compose.animation.scene.SceneScope
 import com.android.systemui.media.controls.ui.controller.MediaCarouselController
@@ -36,7 +37,8 @@
 
 private object MediaCarousel {
     object Elements {
-        internal val Content = ElementKey("MediaCarouselContent")
+        internal val Content =
+            ElementKey(debugName = "MediaCarouselContent", scenePicker = MediaScenePicker)
     }
 }
 
@@ -61,40 +63,43 @@
     mediaHost.measurementInput = MeasurementInput(layoutWidth, layoutHeight)
     carouselController.setSceneContainerSize(layoutWidth, layoutHeight)
 
-    AndroidView(
-        modifier =
-            modifier
-                .element(MediaCarousel.Elements.Content)
-                .height(mediaHeight)
-                .fillMaxWidth()
-                .layout { measurable, constraints ->
-                    val placeable = measurable.measure(constraints)
+    MovableElement(
+        key = MediaCarousel.Elements.Content,
+        modifier = modifier.height(mediaHeight).fillMaxWidth()
+    ) {
+        content {
+            AndroidView(
+                modifier =
+                    Modifier.fillMaxSize().layout { measurable, constraints ->
+                        val placeable = measurable.measure(constraints)
 
-                    // Notify controller to size the carousel for the current space
-                    mediaHost.measurementInput = MeasurementInput(placeable.width, placeable.height)
-                    carouselController.setSceneContainerSize(placeable.width, placeable.height)
+                        // Notify controller to size the carousel for the current space
+                        mediaHost.measurementInput =
+                            MeasurementInput(placeable.width, placeable.height)
+                        carouselController.setSceneContainerSize(placeable.width, placeable.height)
 
-                    layout(placeable.width, placeable.height) { placeable.placeRelative(0, 0) }
+                        layout(placeable.width, placeable.height) { placeable.placeRelative(0, 0) }
+                    },
+                factory = { context ->
+                    FrameLayout(context).apply {
+                        layoutParams =
+                            FrameLayout.LayoutParams(
+                                FrameLayout.LayoutParams.MATCH_PARENT,
+                                FrameLayout.LayoutParams.MATCH_PARENT,
+                            )
+                    }
                 },
-        factory = { context ->
-            FrameLayout(context).apply {
-                val mediaFrame = carouselController.mediaFrame
-                (mediaFrame.parent as? ViewGroup)?.removeView(mediaFrame)
-                addView(mediaFrame)
-                layoutParams =
-                    FrameLayout.LayoutParams(
-                        FrameLayout.LayoutParams.MATCH_PARENT,
-                        FrameLayout.LayoutParams.MATCH_PARENT,
-                    )
-            }
-        },
-        update = {
-            if (it.contains(carouselController.mediaFrame)) {
-                return@AndroidView
-            }
-            val mediaFrame = carouselController.mediaFrame
-            (mediaFrame.parent as? ViewGroup)?.removeView(mediaFrame)
-            it.addView(mediaFrame)
-        },
-    )
+                update = { it.setView(carouselController.mediaFrame) },
+                onRelease = { it.removeAllViews() }
+            )
+        }
+    }
+}
+
+private fun ViewGroup.setView(view: View) {
+    if (view.parent == this) {
+        return
+    }
+    (view.parent as? ViewGroup)?.removeView(view)
+    addView(view)
 }
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/media/controls/ui/composable/MediaScenePicker.kt b/packages/SystemUI/compose/features/src/com/android/systemui/media/controls/ui/composable/MediaScenePicker.kt
new file mode 100644
index 0000000..0398133
--- /dev/null
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/media/controls/ui/composable/MediaScenePicker.kt
@@ -0,0 +1,72 @@
+/*
+ * Copyright (C) 2024 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.media.controls.ui.composable
+
+import com.android.compose.animation.scene.ElementKey
+import com.android.compose.animation.scene.ElementScenePicker
+import com.android.compose.animation.scene.SceneKey
+import com.android.compose.animation.scene.TransitionState
+import com.android.systemui.scene.shared.model.Scenes
+
+/** [ElementScenePicker] implementation for the media carousel object. */
+object MediaScenePicker : ElementScenePicker {
+
+    private val shadeLockscreenFraction = 0.65f
+    private val scenes =
+        setOf(
+            Scenes.Lockscreen,
+            Scenes.Shade,
+            Scenes.QuickSettings,
+            Scenes.QuickSettingsShade,
+            Scenes.Communal
+        )
+
+    override fun sceneDuringTransition(
+        element: ElementKey,
+        transition: TransitionState.Transition,
+        fromSceneZIndex: Float,
+        toSceneZIndex: Float
+    ): SceneKey? {
+        return when {
+            // TODO: 352052894 - update with the actual scene picking
+            transition.isTransitioning(from = Scenes.Lockscreen, to = Scenes.Shade) -> {
+                if (transition.progress < shadeLockscreenFraction) {
+                    Scenes.Lockscreen
+                } else {
+                    Scenes.Shade
+                }
+            }
+
+            // TODO: 345467290 - update with the actual scene picking
+            transition.isTransitioning(from = Scenes.Shade, to = Scenes.Lockscreen) -> {
+                if (transition.progress < 1f - shadeLockscreenFraction) {
+                    Scenes.Shade
+                } else {
+                    Scenes.Lockscreen
+                }
+            }
+
+            // TODO: 345467290 - update with the actual scene picking
+            transition.isTransitioningBetween(Scenes.QuickSettings, Scenes.Shade) -> {
+                Scenes.QuickSettings
+            }
+
+            // TODO: 340216785 - update with the actual scene picking
+            else -> pickSingleSceneIn(scenes, transition, element)
+        }
+    }
+}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/statusbar/ui/composable/StatusBar.kt b/packages/SystemUI/compose/features/src/com/android/systemui/statusbar/ui/composable/StatusBar.kt
deleted file mode 100644
index f514ab4..0000000
--- a/packages/SystemUI/compose/features/src/com/android/systemui/statusbar/ui/composable/StatusBar.kt
+++ /dev/null
@@ -1,43 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *
- */
-
-package com.android.systemui.statusbar.ui.composable
-
-import androidx.compose.foundation.layout.Arrangement
-import androidx.compose.foundation.layout.Row
-import androidx.compose.foundation.layout.defaultMinSize
-import androidx.compose.foundation.layout.fillMaxWidth
-import androidx.compose.foundation.layout.padding
-import androidx.compose.material3.Text
-import androidx.compose.runtime.Composable
-import androidx.compose.ui.Alignment
-import androidx.compose.ui.Modifier
-import androidx.compose.ui.unit.dp
-
-@Composable
-fun StatusBar(
-    modifier: Modifier = Modifier,
-) {
-    // TODO(b/272780101): implement.
-    Row(
-        modifier = modifier.fillMaxWidth().defaultMinSize(minHeight = 48.dp).padding(4.dp),
-        horizontalArrangement = Arrangement.Center,
-        verticalAlignment = Alignment.CenterVertically,
-    ) {
-        Text("Status bar")
-    }
-}
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/PunchHole.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/PunchHole.kt
deleted file mode 100644
index b346a70..0000000
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/PunchHole.kt
+++ /dev/null
@@ -1,178 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.compose.animation.scene
-
-import androidx.compose.runtime.Stable
-import androidx.compose.ui.Modifier
-import androidx.compose.ui.geometry.Offset
-import androidx.compose.ui.geometry.Size
-import androidx.compose.ui.graphics.BlendMode
-import androidx.compose.ui.graphics.Color
-import androidx.compose.ui.graphics.CompositingStrategy
-import androidx.compose.ui.graphics.Outline
-import androidx.compose.ui.graphics.RectangleShape
-import androidx.compose.ui.graphics.Shape
-import androidx.compose.ui.graphics.drawOutline
-import androidx.compose.ui.graphics.drawscope.ContentDrawScope
-import androidx.compose.ui.graphics.drawscope.DrawScope
-import androidx.compose.ui.graphics.drawscope.translate
-import androidx.compose.ui.layout.LayoutCoordinates
-import androidx.compose.ui.layout.Measurable
-import androidx.compose.ui.layout.MeasureResult
-import androidx.compose.ui.layout.MeasureScope
-import androidx.compose.ui.node.DelegatingNode
-import androidx.compose.ui.node.DrawModifierNode
-import androidx.compose.ui.node.GlobalPositionAwareModifierNode
-import androidx.compose.ui.node.LayoutModifierNode
-import androidx.compose.ui.node.ModifierNodeElement
-import androidx.compose.ui.unit.Constraints
-import androidx.compose.ui.unit.LayoutDirection
-import androidx.compose.ui.unit.toSize
-
-/**
- * Punch a hole in this node with the given [size], [offset] and [shape].
- *
- * Punching a hole in an element will "remove" any pixel drawn by that element in the hole area.
- * This can be used to make content drawn below an opaque element visible. For example, if we have
- * [this lockscreen scene](http://shortn/_VYySFnJDhN) drawn below
- * [this shade scene](http://shortn/_fpxGUk0Rg7) and punch a hole in the latter using the big clock
- * time bounds and a RoundedCornerShape(10dp), [this](http://shortn/_qt80IvORFj) would be the
- * result.
- */
-@Stable
-fun Modifier.punchHole(
-    size: () -> Size,
-    offset: () -> Offset,
-    shape: Shape = RectangleShape,
-): Modifier = this.then(PunchHoleElement(size, offset, shape))
-
-/**
- * Punch a hole in this node using the bounds of [coords] and the given [shape].
- *
- * You can use [androidx.compose.ui.layout.onGloballyPositioned] to get the last coordinates of a
- * node.
- */
-@Stable
-fun Modifier.punchHole(
-    coords: () -> LayoutCoordinates?,
-    shape: Shape = RectangleShape,
-): Modifier = this.then(PunchHoleWithBoundsElement(coords, shape))
-
-private data class PunchHoleElement(
-    private val size: () -> Size,
-    private val offset: () -> Offset,
-    private val shape: Shape,
-) : ModifierNodeElement<PunchHoleNode>() {
-    override fun create(): PunchHoleNode = PunchHoleNode(size, offset, { shape })
-
-    override fun update(node: PunchHoleNode) {
-        node.size = size
-        node.offset = offset
-        node.shape = { shape }
-    }
-}
-
-private class PunchHoleNode(
-    var size: () -> Size,
-    var offset: () -> Offset,
-    var shape: () -> Shape,
-) : Modifier.Node(), DrawModifierNode, LayoutModifierNode {
-    private var lastSize: Size = Size.Unspecified
-    private var lastLayoutDirection: LayoutDirection = LayoutDirection.Ltr
-    private var lastOutline: Outline? = null
-
-    override fun MeasureScope.measure(
-        measurable: Measurable,
-        constraints: Constraints
-    ): MeasureResult {
-        return measurable.measure(constraints).run {
-            layout(width, height) {
-                placeWithLayer(0, 0) { compositingStrategy = CompositingStrategy.Offscreen }
-            }
-        }
-    }
-
-    override fun ContentDrawScope.draw() {
-        drawContent()
-
-        val holeSize = size()
-        if (holeSize != Size.Zero) {
-            val offset = offset()
-            translate(offset.x, offset.y) { drawHole(holeSize) }
-        }
-    }
-
-    private fun DrawScope.drawHole(size: Size) {
-        if (shape == RectangleShape) {
-            drawRect(Color.Black, size = size, blendMode = BlendMode.DstOut)
-            return
-        }
-
-        val outline =
-            if (size == lastSize && layoutDirection == lastLayoutDirection) {
-                lastOutline!!
-            } else {
-                val newOutline = shape().createOutline(size, layoutDirection, this)
-                lastSize = size
-                lastLayoutDirection = layoutDirection
-                lastOutline = newOutline
-                newOutline
-            }
-
-        drawOutline(
-            outline,
-            Color.Black,
-            blendMode = BlendMode.DstOut,
-        )
-    }
-}
-
-private data class PunchHoleWithBoundsElement(
-    private val coords: () -> LayoutCoordinates?,
-    private val shape: Shape,
-) : ModifierNodeElement<PunchHoleWithBoundsNode>() {
-    override fun create(): PunchHoleWithBoundsNode = PunchHoleWithBoundsNode(coords, shape)
-
-    override fun update(node: PunchHoleWithBoundsNode) {
-        node.holeCoords = coords
-        node.shape = shape
-    }
-}
-
-private class PunchHoleWithBoundsNode(
-    var holeCoords: () -> LayoutCoordinates?,
-    var shape: Shape,
-) : DelegatingNode(), DrawModifierNode, GlobalPositionAwareModifierNode {
-    private val delegate = delegate(PunchHoleNode(::holeSize, ::holeOffset, ::shape))
-    private var lastCoords: LayoutCoordinates? = null
-
-    override fun onGloballyPositioned(coordinates: LayoutCoordinates) {
-        this.lastCoords = coordinates
-    }
-
-    override fun ContentDrawScope.draw() = with(delegate) { draw() }
-
-    private fun holeSize(): Size {
-        return holeCoords()?.size?.toSize() ?: Size.Zero
-    }
-
-    private fun holeOffset(): Offset {
-        val holeCoords = holeCoords() ?: return Offset.Zero
-        val lastCoords = lastCoords ?: error("draw() was called before onGloballyPositioned()")
-        return lastCoords.localPositionOf(holeCoords, relativeToSource = Offset.Zero)
-    }
-}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayServiceTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayServiceTest.kt
index 5a39de8..444f63a 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayServiceTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayServiceTest.kt
@@ -34,6 +34,9 @@
 import androidx.lifecycle.LifecycleRegistry
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
+import com.android.app.viewcapture.ViewCapture
+import com.android.app.viewcapture.ViewCaptureAwareWindowManager
+import com.android.app.viewcapture.ViewCaptureFactory
 import com.android.compose.animation.scene.ObservableTransitionState
 import com.android.internal.logging.UiEventLogger
 import com.android.keyguard.KeyguardUpdateMonitor
@@ -79,6 +82,7 @@
 import org.mockito.Mockito
 import org.mockito.Mockito.clearInvocations
 import org.mockito.Mockito.isNull
+import org.mockito.Mockito.spy
 import org.mockito.Mockito.verify
 import org.mockito.MockitoAnnotations
 
@@ -114,11 +118,11 @@
 
     @Mock
     lateinit var mDreamComplicationComponentFactory:
-        com.android.systemui.dreams.complication.dagger.ComplicationComponent.Factory
+            com.android.systemui.dreams.complication.dagger.ComplicationComponent.Factory
 
     @Mock
     lateinit var mDreamComplicationComponent:
-        com.android.systemui.dreams.complication.dagger.ComplicationComponent
+            com.android.systemui.dreams.complication.dagger.ComplicationComponent
 
     @Mock lateinit var mHideComplicationTouchHandler: HideComplicationTouchHandler
 
@@ -154,8 +158,12 @@
 
     @Mock lateinit var mDreamOverlayCallbackController: DreamOverlayCallbackController
 
+    @Mock lateinit var mLazyViewCapture: Lazy<ViewCapture>
+
+    private lateinit var mViewCaptureAwareWindowManager: ViewCaptureAwareWindowManager
     private lateinit var bouncerRepository: FakeKeyguardBouncerRepository
     private lateinit var communalRepository: FakeCommunalSceneRepository
+    private var viewCaptureSpy = spy(ViewCaptureFactory.getInstance(context))
 
     @Captor var mViewCaptor: ArgumentCaptor<View>? = null
     private lateinit var mService: DreamOverlayService
@@ -192,13 +200,16 @@
         whenever(mDreamOverlayContainerViewController.containerView)
             .thenReturn(mDreamOverlayContainerView)
         whenever(mScrimManager.getCurrentController()).thenReturn(mScrimController)
+        whenever(mLazyViewCapture.value).thenReturn(viewCaptureSpy)
         mWindowParams = WindowManager.LayoutParams()
+        mViewCaptureAwareWindowManager = ViewCaptureAwareWindowManager(mWindowManager,
+                mLazyViewCapture, isViewCaptureEnabled = false)
         mService =
             DreamOverlayService(
                 mContext,
                 mLifecycleOwner,
                 mMainExecutor,
-                mWindowManager,
+                mViewCaptureAwareWindowManager,
                 mComplicationComponentFactory,
                 mDreamComplicationComponentFactory,
                 mDreamOverlayComponentFactory,
@@ -246,7 +257,7 @@
         mMainExecutor.runAllReady()
         verify(mUiEventLogger).log(DreamOverlayService.DreamOverlayEvent.DREAM_OVERLAY_ENTER_START)
         verify(mUiEventLogger)
-            .log(DreamOverlayService.DreamOverlayEvent.DREAM_OVERLAY_COMPLETE_START)
+                .log(DreamOverlayService.DreamOverlayEvent.DREAM_OVERLAY_COMPLETE_START)
     }
 
     @Test
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/SettingObserverTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/SettingObserverTest.kt
new file mode 100644
index 0000000..188f2ac
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/SettingObserverTest.kt
@@ -0,0 +1,117 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.systemui.qs
+
+import android.net.Uri
+import android.os.Handler
+import android.os.Looper
+import android.platform.test.annotations.DisableFlags
+import android.platform.test.annotations.EnableFlags
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.Flags
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.util.settings.SettingsProxy
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentCaptor
+import org.mockito.Captor
+import org.mockito.Mock
+import org.mockito.MockitoAnnotations
+import org.mockito.kotlin.any
+import org.mockito.kotlin.capture
+import org.mockito.kotlin.eq
+import org.mockito.kotlin.reset
+import org.mockito.kotlin.verify
+import org.mockito.kotlin.whenever
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class SettingObserverTest : SysuiTestCase() {
+
+    private val DEFAULT_VALUE = 7
+
+    @Mock lateinit var settingsProxy: SettingsProxy
+    @Captor private lateinit var argumentCaptor: ArgumentCaptor<Runnable>
+
+    private lateinit var testSettingObserver: SettingObserver
+
+    @Before
+    fun setUp() {
+        MockitoAnnotations.initMocks(this)
+        whenever(settingsProxy.getInt(any(), any())).thenReturn(5)
+        whenever(settingsProxy.getUriFor(any())).thenReturn(Uri.parse("content://test_uri"))
+        testSettingObserver =
+            object :
+                SettingObserver(
+                    settingsProxy,
+                    Handler(Looper.getMainLooper()),
+                    "test_setting",
+                    DEFAULT_VALUE
+                ) {
+                override fun handleValueChanged(value: Int, observedChange: Boolean) {}
+            }
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_QS_REGISTER_SETTING_OBSERVER_ON_BG_THREAD)
+    fun setListening_true_settingsProxyRegistered() {
+        testSettingObserver.isListening = true
+        verify(settingsProxy)
+            .registerContentObserverAsync(
+                any<Uri>(),
+                eq(false),
+                eq(testSettingObserver),
+                capture(argumentCaptor)
+            )
+        assertThat(testSettingObserver.value).isEqualTo(5)
+
+        // Verify if the callback applies updated value after the fact
+        whenever(settingsProxy.getInt(any(), any())).thenReturn(12341234)
+        argumentCaptor.value.run()
+        assertThat(testSettingObserver.value).isEqualTo(12341234)
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_QS_REGISTER_SETTING_OBSERVER_ON_BG_THREAD)
+    fun setListening_false_settingsProxyRegistered() {
+        testSettingObserver.isListening = true
+        reset(settingsProxy)
+        testSettingObserver.isListening = false
+
+        verify(settingsProxy).unregisterContentObserverAsync(eq(testSettingObserver))
+    }
+
+    @Test
+    @DisableFlags(Flags.FLAG_QS_REGISTER_SETTING_OBSERVER_ON_BG_THREAD)
+    fun setListening_bgFlagDisabled_true_settingsProxyRegistered() {
+        testSettingObserver.isListening = true
+        verify(settingsProxy)
+            .registerContentObserverSync(any<Uri>(), eq(false), eq(testSettingObserver))
+    }
+
+    @Test
+    @DisableFlags(Flags.FLAG_QS_REGISTER_SETTING_OBSERVER_ON_BG_THREAD)
+    fun setListening_bgFlagDisabled_false_settingsProxyRegistered() {
+        testSettingObserver.isListening = true
+        reset(settingsProxy)
+        testSettingObserver.isListening = false
+
+        verify(settingsProxy).unregisterContentObserverSync(eq(testSettingObserver))
+    }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/airplate/domain/interactor/AirplaneModeTileDataInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/airplane/domain/interactor/AirplaneModeTileDataInteractorTest.kt
similarity index 94%
rename from packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/airplate/domain/interactor/AirplaneModeTileDataInteractorTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/airplane/domain/interactor/AirplaneModeTileDataInteractorTest.kt
index 89b9b7f..67e2fba 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/airplate/domain/interactor/AirplaneModeTileDataInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/airplane/domain/interactor/AirplaneModeTileDataInteractorTest.kt
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.systemui.qs.tiles.impl.airplate.domain.interactor
+package com.android.systemui.qs.tiles.impl.airplane.domain.interactor
 
 import android.os.UserHandle
 import android.platform.test.annotations.EnabledOnRavenwood
@@ -23,7 +23,6 @@
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.coroutines.collectValues
 import com.android.systemui.qs.tiles.base.interactor.DataUpdateTrigger
-import com.android.systemui.qs.tiles.impl.airplane.domain.interactor.AirplaneModeTileDataInteractor
 import com.android.systemui.qs.tiles.impl.airplane.domain.model.AirplaneModeTileModel
 import com.android.systemui.statusbar.pipeline.airplane.data.repository.FakeAirplaneModeRepository
 import com.google.common.truth.Truth.assertThat
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/airplate/domain/interactor/AirplaneModeTileUserActionInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/airplane/domain/interactor/AirplaneModeTileUserActionInteractorTest.kt
similarity index 94%
rename from packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/airplate/domain/interactor/AirplaneModeTileUserActionInteractorTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/airplane/domain/interactor/AirplaneModeTileUserActionInteractorTest.kt
index 8982d81..79fcc92 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/airplate/domain/interactor/AirplaneModeTileUserActionInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/airplane/domain/interactor/AirplaneModeTileUserActionInteractorTest.kt
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.systemui.qs.tiles.impl.airplate.domain.interactor
+package com.android.systemui.qs.tiles.impl.airplane.domain.interactor
 
 import android.platform.test.annotations.EnabledOnRavenwood
 import android.provider.Settings
@@ -26,7 +26,6 @@
 import com.android.systemui.qs.tiles.base.actions.QSTileIntentUserInputHandlerSubject.Companion.assertThat
 import com.android.systemui.qs.tiles.base.interactor.QSTileInputTestKtx.click
 import com.android.systemui.qs.tiles.base.interactor.QSTileInputTestKtx.longClick
-import com.android.systemui.qs.tiles.impl.airplane.domain.interactor.AirplaneModeTileUserActionInteractor
 import com.android.systemui.qs.tiles.impl.airplane.domain.model.AirplaneModeTileModel
 import com.android.systemui.statusbar.pipeline.airplane.data.repository.FakeAirplaneModeRepository
 import com.android.systemui.statusbar.pipeline.airplane.domain.interactor.AirplaneModeInteractor
@@ -54,7 +53,7 @@
                 connectivityRepository,
                 mobileConnectionsRepository,
             ),
-            inputHandler
+            inputHandler,
         )
 
     @Test
diff --git a/packages/SystemUI/res/xml/large_screen_shade_header.xml b/packages/SystemUI/res/xml/large_screen_shade_header.xml
index fe61c46..eb0aae9 100644
--- a/packages/SystemUI/res/xml/large_screen_shade_header.xml
+++ b/packages/SystemUI/res/xml/large_screen_shade_header.xml
@@ -33,6 +33,7 @@
             android:layout_width="wrap_content"
             android:layout_height="0dp"
             android:layout_marginStart="8dp"
+            app:layout_constraintBaseline_toBaselineOf="@id/clock"
             app:layout_constraintBottom_toBottomOf="parent"
             app:layout_constraintStart_toEndOf="@id/clock"
             app:layout_constraintTop_toTopOf="parent" />
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java b/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java
index b0fc60e..a6deca7 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java
@@ -16,6 +16,9 @@
 
 package com.android.systemui.dagger;
 
+import static com.android.systemui.Flags.enableViewCaptureTracing;
+import static com.android.systemui.util.ConvenienceExtensionsKt.toKotlinLazy;
+
 import android.annotation.Nullable;
 import android.annotation.SuppressLint;
 import android.app.ActivityManager;
@@ -111,6 +114,9 @@
 import androidx.asynclayoutinflater.view.AsyncLayoutInflater;
 import androidx.core.app.NotificationManagerCompat;
 
+import com.android.app.viewcapture.ViewCapture;
+import com.android.app.viewcapture.ViewCaptureAwareWindowManager;
+import com.android.app.viewcapture.ViewCaptureFactory;
 import com.android.internal.app.IBatteryStats;
 import com.android.internal.appwidget.IAppWidgetService;
 import com.android.internal.jank.InteractionJankMonitor;
@@ -125,6 +131,7 @@
 import com.android.systemui.user.utils.UserScopedService;
 import com.android.systemui.user.utils.UserScopedServiceImpl;
 
+import dagger.Lazy;
 import dagger.Module;
 import dagger.Provides;
 
@@ -680,6 +687,15 @@
 
     @Provides
     @Singleton
+    static ViewCaptureAwareWindowManager provideViewCaptureAwareWindowManager(
+            WindowManager windowManager, Lazy<ViewCapture> daggerLazyViewCapture) {
+        return new ViewCaptureAwareWindowManager(windowManager,
+                /* lazyViewCapture= */ toKotlinLazy(daggerLazyViewCapture),
+                /* isViewCaptureEnabled= */ enableViewCaptureTracing());
+    }
+
+    @Provides
+    @Singleton
     static PermissionManager providePermissionManager(Context context) {
         PermissionManager pm = context.getSystemService(PermissionManager.class);
         if (pm != null) {
@@ -764,4 +780,10 @@
         return IDeviceIdleController.Stub.asInterface(
                 ServiceManager.getService(Context.DEVICE_IDLE_CONTROLLER));
     }
+
+    @Provides
+    @Singleton
+    static ViewCapture provideViewCapture(Context context) {
+        return ViewCaptureFactory.getInstance(context);
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java
index c6c57479..83fa001 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java
@@ -45,6 +45,7 @@
 import androidx.lifecycle.ServiceLifecycleDispatcher;
 import androidx.lifecycle.ViewModelStore;
 
+import com.android.app.viewcapture.ViewCaptureAwareWindowManager;
 import com.android.dream.lowlight.dagger.LowLightDreamModule;
 import com.android.internal.logging.UiEvent;
 import com.android.internal.logging.UiEventLogger;
@@ -97,7 +98,7 @@
     @Nullable
     private final ComponentName mHomeControlPanelDreamComponent;
     private final UiEventLogger mUiEventLogger;
-    private final WindowManager mWindowManager;
+    private final ViewCaptureAwareWindowManager mWindowManager;
     private final String mWindowTitle;
 
     // A reference to the {@link Window} used to hold the dream overlay.
@@ -244,7 +245,7 @@
             Context context,
             DreamOverlayLifecycleOwner lifecycleOwner,
             @Main DelayableExecutor executor,
-            WindowManager windowManager,
+            ViewCaptureAwareWindowManager viewCaptureAwareWindowManager,
             ComplicationComponent.Factory complicationComponentFactory,
             com.android.systemui.dreams.complication.dagger.ComplicationComponent.Factory
                     dreamComplicationComponentFactory,
@@ -267,7 +268,7 @@
         super(executor);
         mContext = context;
         mExecutor = executor;
-        mWindowManager = windowManager;
+        mWindowManager = viewCaptureAwareWindowManager;
         mKeyguardUpdateMonitor = keyguardUpdateMonitor;
         mScrimManager = scrimManager;
         mLowLightDreamComponent = lowLightDreamComponent;
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java
index c4b70d8..9f33113 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java
@@ -81,6 +81,7 @@
 import com.android.systemui.flags.FeatureFlags;
 import com.android.systemui.keyguard.domain.interactor.KeyguardEnabledInteractor;
 import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor;
+import com.android.systemui.keyguard.domain.interactor.KeyguardWakeDirectlyToGoneInteractor;
 import com.android.systemui.keyguard.ui.binder.KeyguardSurfaceBehindParamsApplier;
 import com.android.systemui.keyguard.ui.binder.KeyguardSurfaceBehindViewBinder;
 import com.android.systemui.keyguard.ui.binder.WindowManagerLockscreenVisibilityViewBinder;
@@ -317,7 +318,7 @@
 
     private final WindowManagerOcclusionManager mWmOcclusionManager;
     private final KeyguardEnabledInteractor mKeyguardEnabledInteractor;
-
+    private final KeyguardWakeDirectlyToGoneInteractor mKeyguardWakeDirectlyToGoneInteractor;
     private final Lazy<FoldGracePeriodProvider> mFoldGracePeriodProvider = new Lazy<>() {
         @Override
         public FoldGracePeriodProvider get() {
@@ -344,7 +345,8 @@
             @Main Executor mainExecutor,
             KeyguardInteractor keyguardInteractor,
             KeyguardEnabledInteractor keyguardEnabledInteractor,
-            Lazy<KeyguardStateCallbackStartable> keyguardStateCallbackStartableLazy) {
+            Lazy<KeyguardStateCallbackStartable> keyguardStateCallbackStartableLazy,
+            KeyguardWakeDirectlyToGoneInteractor keyguardWakeDirectlyToGoneInteractor) {
         super();
         mKeyguardViewMediator = keyguardViewMediator;
         mKeyguardLifecyclesDispatcher = keyguardLifecyclesDispatcher;
@@ -372,6 +374,7 @@
 
         mWmOcclusionManager = windowManagerOcclusionManager;
         mKeyguardEnabledInteractor = keyguardEnabledInteractor;
+        mKeyguardWakeDirectlyToGoneInteractor = keyguardWakeDirectlyToGoneInteractor;
     }
 
     @Override
@@ -486,6 +489,7 @@
         public void onDreamingStarted() {
             trace("onDreamingStarted");
             checkPermission();
+            mKeyguardWakeDirectlyToGoneInteractor.onDreamingStarted();
             mKeyguardInteractor.setDreaming(true);
             mKeyguardViewMediator.onDreamingStarted();
         }
@@ -494,6 +498,7 @@
         public void onDreamingStopped() {
             trace("onDreamingStopped");
             checkPermission();
+            mKeyguardWakeDirectlyToGoneInteractor.onDreamingStopped();
             mKeyguardInteractor.setDreaming(false);
             mKeyguardViewMediator.onDreamingStopped();
         }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt
index f837d8e..ae751db 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt
@@ -127,6 +127,30 @@
      */
     val isKeyguardEnabled: StateFlow<Boolean>
 
+    /**
+     * Whether we can transition directly back to GONE from AOD/DOZING without any authentication
+     * events (such as a fingerprint wake and unlock), even though authentication would normally be
+     * required. This means that if you tap the screen or press the power button, you'll return
+     * directly to the unlocked app content without seeing the lockscreen, even if a secure
+     * authentication method (PIN/password/biometrics) is set.
+     *
+     * This is true in these cases:
+     * - The screen timed out, but the "lock after screen timeout" duration (default 5 seconds) has
+     *   not yet elapsed.
+     * - The power button was pressed, but "power button instantly locks" is not enabled, and the
+     *   "lock after screen timeout" duration has not elapsed.
+     *
+     * Note that this value specifically tells us if we can *ignore* authentication that would
+     * otherwise be required to transition from AOD/DOZING -> GONE. AOD/DOZING -> GONE is also
+     * possible if keyguard is disabled, either from an app request or because security is set to
+     * "none", but in that case, auth is not required so this boolean is not relevant.
+     *
+     * See [KeyguardWakeToGoneInteractor].
+     */
+    val canIgnoreAuthAndReturnToGone: StateFlow<Boolean>
+
+    fun setCanIgnoreAuthAndReturnToGone(canWake: Boolean)
+
     /** Is the always-on display available to be used? */
     val isAodAvailable: StateFlow<Boolean>
 
@@ -386,6 +410,13 @@
         MutableStateFlow(!lockPatternUtils.isLockScreenDisabled(userTracker.userId))
     override val isKeyguardEnabled: StateFlow<Boolean> = _isKeyguardEnabled.asStateFlow()
 
+    private val _canIgnoreAuthAndReturnToGone = MutableStateFlow(false)
+    override val canIgnoreAuthAndReturnToGone = _canIgnoreAuthAndReturnToGone.asStateFlow()
+
+    override fun setCanIgnoreAuthAndReturnToGone(canWakeToGone: Boolean) {
+        _canIgnoreAuthAndReturnToGone.value = canWakeToGone
+    }
+
     private val _isDozing = MutableStateFlow(statusBarStateController.isDozing)
     override val isDozing: StateFlow<Boolean> = _isDozing.asStateFlow()
 
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractor.kt
index 1426cba..893835a 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractor.kt
@@ -53,6 +53,7 @@
     powerInteractor: PowerInteractor,
     keyguardOcclusionInteractor: KeyguardOcclusionInteractor,
     val deviceEntryRepository: DeviceEntryRepository,
+    private val wakeToGoneInteractor: KeyguardWakeDirectlyToGoneInteractor,
 ) :
     TransitionInteractor(
         fromState = KeyguardState.AOD,
@@ -98,6 +99,7 @@
                     keyguardInteractor.primaryBouncerShowing,
                     keyguardInteractor.isKeyguardOccluded,
                     canDismissLockscreen,
+                    wakeToGoneInteractor.canWakeDirectlyToGone,
                 )
                 .collect {
                     (
@@ -107,6 +109,7 @@
                         primaryBouncerShowing,
                         isKeyguardOccludedLegacy,
                         canDismissLockscreen,
+                        canWakeDirectlyToGone,
                     ) ->
                     if (!maybeHandleInsecurePowerGesture()) {
                         val shouldTransitionToLockscreen =
@@ -131,8 +134,7 @@
 
                         val shouldTransitionToGone =
                             (!KeyguardWmStateRefactor.isEnabled && canDismissLockscreen) ||
-                                (KeyguardWmStateRefactor.isEnabled &&
-                                    !deviceEntryRepository.isLockscreenEnabled())
+                                (KeyguardWmStateRefactor.isEnabled && canWakeDirectlyToGone)
 
                         if (shouldTransitionToGone) {
                             // TODO(b/336576536): Check if adaptation for scene framework is needed
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractor.kt
index 76e88a2..aee65a8 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractor.kt
@@ -55,6 +55,7 @@
     private val communalInteractor: CommunalInteractor,
     keyguardOcclusionInteractor: KeyguardOcclusionInteractor,
     val deviceEntryRepository: DeviceEntryRepository,
+    private val wakeToGoneInteractor: KeyguardWakeDirectlyToGoneInteractor,
 ) :
     TransitionInteractor(
         fromState = KeyguardState.DOZING,
@@ -181,7 +182,7 @@
                 .sample(
                     communalInteractor.isIdleOnCommunal,
                     keyguardInteractor.biometricUnlockState,
-                    canTransitionToGoneOnWake,
+                    wakeToGoneInteractor.canWakeDirectlyToGone,
                     keyguardInteractor.primaryBouncerShowing,
                 )
                 .collect {
@@ -189,27 +190,14 @@
                         _,
                         isIdleOnCommunal,
                         biometricUnlockState,
-                        canDismissLockscreen,
+                        canWakeDirectlyToGone,
                         primaryBouncerShowing) ->
                     if (
                         !maybeStartTransitionToOccludedOrInsecureCamera() &&
                             // Handled by dismissFromDozing().
                             !isWakeAndUnlock(biometricUnlockState.mode)
                     ) {
-                        if (!KeyguardWmStateRefactor.isEnabled && canDismissLockscreen) {
-                            if (SceneContainerFlag.isEnabled) {
-                                // TODO(b/336576536): Check if adaptation for scene framework is
-                                // needed
-                            } else {
-                                startTransitionTo(
-                                    KeyguardState.GONE,
-                                    ownerReason = "waking from dozing"
-                                )
-                            }
-                        } else if (
-                            KeyguardWmStateRefactor.isEnabled &&
-                                !deviceEntryRepository.isLockscreenEnabled()
-                        ) {
+                        if (canWakeDirectlyToGone) {
                             if (SceneContainerFlag.isEnabled) {
                                 // TODO(b/336576536): Check if adaptation for scene framework is
                                 // needed
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractor.kt
index 0e76487..cfb161c 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractor.kt
@@ -23,6 +23,7 @@
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Background
 import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.deviceentry.domain.interactor.DeviceEntryInteractor
 import com.android.systemui.keyguard.KeyguardWmStateRefactor
 import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository
 import com.android.systemui.keyguard.shared.model.BiometricUnlockMode
@@ -37,11 +38,14 @@
 import kotlin.time.Duration.Companion.seconds
 import kotlinx.coroutines.CoroutineDispatcher
 import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.FlowPreview
 import kotlinx.coroutines.flow.combine
 import kotlinx.coroutines.flow.debounce
+import kotlinx.coroutines.flow.filter
 import kotlinx.coroutines.launch
 
+@OptIn(ExperimentalCoroutinesApi::class)
 @SysUISingleton
 class FromDreamingTransitionInteractor
 @Inject
@@ -56,6 +60,7 @@
     private val glanceableHubTransitions: GlanceableHubTransitions,
     powerInteractor: PowerInteractor,
     keyguardOcclusionInteractor: KeyguardOcclusionInteractor,
+    private val deviceEntryInteractor: DeviceEntryInteractor,
 ) :
     TransitionInteractor(
         fromState = KeyguardState.DREAMING,
@@ -72,7 +77,7 @@
         listenForDreamingToOccluded()
         listenForDreamingToGoneWhenDismissable()
         listenForDreamingToGoneFromBiometricUnlock()
-        listenForDreamingToLockscreen()
+        listenForDreamingToLockscreenOrGone()
         listenForDreamingToAodOrDozing()
         listenForTransitionToCamera(scope, keyguardInteractor)
         listenForDreamingToGlanceableHub()
@@ -132,17 +137,7 @@
 
     @OptIn(FlowPreview::class)
     private fun listenForDreamingToOccluded() {
-        if (KeyguardWmStateRefactor.isEnabled) {
-            scope.launch {
-                combine(
-                        keyguardInteractor.isDreaming,
-                        keyguardOcclusionInteractor.isShowWhenLockedActivityOnTop,
-                        ::Pair
-                    )
-                    .filterRelevantKeyguardStateAnd { (isDreaming, _) -> !isDreaming }
-                    .collect { maybeStartTransitionToOccludedOrInsecureCamera() }
-            }
-        } else {
+        if (!KeyguardWmStateRefactor.isEnabled) {
             scope.launch {
                 combine(
                         keyguardInteractor.isKeyguardOccluded,
@@ -168,21 +163,41 @@
         }
     }
 
-    private fun listenForDreamingToLockscreen() {
+    private fun listenForDreamingToLockscreenOrGone() {
         if (!KeyguardWmStateRefactor.isEnabled) {
             return
         }
 
         scope.launch {
-            keyguardOcclusionInteractor.isShowWhenLockedActivityOnTop
-                .filterRelevantKeyguardStateAnd { onTop -> !onTop }
-                .collect { startTransitionTo(KeyguardState.LOCKSCREEN) }
+            keyguardInteractor.isDreaming
+                .filter { !it }
+                .sample(deviceEntryInteractor.isUnlocked, ::Pair)
+                .collect { (_, dismissable) ->
+                    // TODO(b/349837588): Add check for -> OCCLUDED.
+                    if (dismissable) {
+                        startTransitionTo(
+                            KeyguardState.GONE,
+                            ownerReason = "No longer dreaming; dismissable"
+                        )
+                    } else {
+                        startTransitionTo(
+                            KeyguardState.LOCKSCREEN,
+                            ownerReason = "No longer dreaming"
+                        )
+                    }
+                }
         }
     }
 
     private fun listenForDreamingToGoneWhenDismissable() {
-        // TODO(b/336576536): Check if adaptation for scene framework is needed
-        if (SceneContainerFlag.isEnabled) return
+        if (SceneContainerFlag.isEnabled) {
+            return // TODO(b/336576536): Check if adaptation for scene framework is needed
+        }
+
+        if (KeyguardWmStateRefactor.isEnabled) {
+            return
+        }
+
         scope.launch {
             keyguardInteractor.isAbleToDream
                 .sampleCombine(
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardWakeDirectlyToGoneInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardWakeDirectlyToGoneInteractor.kt
new file mode 100644
index 0000000..f0bf402
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardWakeDirectlyToGoneInteractor.kt
@@ -0,0 +1,372 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.domain.interactor
+
+import android.annotation.SuppressLint
+import android.app.AlarmManager
+import android.app.PendingIntent
+import android.content.BroadcastReceiver
+import android.content.Context
+import android.content.Intent
+import android.content.IntentFilter
+import android.provider.Settings
+import android.provider.Settings.Secure
+import com.android.internal.widget.LockPatternUtils
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.keyguard.KeyguardViewMediator
+import com.android.systemui.keyguard.KeyguardWmStateRefactor
+import com.android.systemui.keyguard.data.repository.KeyguardRepository
+import com.android.systemui.keyguard.shared.model.BiometricUnlockMode
+import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.keyguard.shared.model.KeyguardState.Companion.deviceIsAsleepInState
+import com.android.systemui.keyguard.shared.model.KeyguardState.Companion.deviceIsAwakeInState
+import com.android.systemui.power.domain.interactor.PowerInteractor
+import com.android.systemui.power.shared.model.WakeSleepReason
+import com.android.systemui.user.domain.interactor.SelectedUserInteractor
+import com.android.systemui.util.kotlin.sample
+import com.android.systemui.util.settings.SecureSettings
+import com.android.systemui.util.settings.SystemSettings
+import com.android.systemui.util.time.SystemClock
+import javax.inject.Inject
+import kotlin.math.max
+import kotlin.math.min
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.distinctUntilChangedBy
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.launch
+
+/**
+ * Logic related to the ability to wake directly to GONE from asleep (AOD/DOZING), without going
+ * through LOCKSCREEN or a BOUNCER state.
+ *
+ * This is possible in the following scenarios:
+ * - The lockscreen is disabled, either from an app request (SUW does this), or by the security
+ *   "None" setting.
+ * - A biometric authentication event occurred while we were asleep (fingerprint auth, etc). This
+ *   specifically is referred to throughout the codebase as "wake and unlock".
+ * - The screen timed out, but the "lock after screen timeout" duration has not elapsed.
+ * - The power button was pressed, but "power button instantly locks" is disabled and the "lock
+ *   after screen timeout" duration has not elapsed.
+ *
+ * In these cases, no (further) authentication is required, and we can transition directly from
+ * AOD/DOZING -> GONE.
+ */
+@SysUISingleton
+class KeyguardWakeDirectlyToGoneInteractor
+@Inject
+constructor(
+    @Application private val scope: CoroutineScope,
+    private val context: Context,
+    private val repository: KeyguardRepository,
+    private val systemClock: SystemClock,
+    private val alarmManager: AlarmManager,
+    private val transitionInteractor: KeyguardTransitionInteractor,
+    private val powerInteractor: PowerInteractor,
+    private val secureSettings: SecureSettings,
+    private val lockPatternUtils: LockPatternUtils,
+    private val systemSettings: SystemSettings,
+    private val selectedUserInteractor: SelectedUserInteractor,
+) {
+
+    /**
+     * Whether the lockscreen was disabled as of the last wake/sleep event, according to
+     * LockPatternUtils.
+     *
+     * This will always be true if [repository.isKeyguardServiceEnabled]=false, but it can also be
+     * true when the keyguard service is enabled if the lockscreen has been disabled via adb using
+     * the `adb shell locksettings set-disabled true` command, which is often done in tests.
+     *
+     * Unlike keyguardServiceEnabled, changes to this value should *not* immediately show or hide
+     * the keyguard. If the lockscreen is disabled in this way, it will just not show on the next
+     * sleep/wake.
+     */
+    private val isLockscreenDisabled: Flow<Boolean> =
+        powerInteractor.isAwake.map { isLockscreenDisabled() }
+
+    /**
+     * Whether we can wake from AOD/DOZING directly to GONE, bypassing LOCKSCREEN/BOUNCER states.
+     *
+     * This is possible in the following cases:
+     * - Keyguard is disabled, either from an app request or from security being set to "None".
+     * - We're wake and unlocking (fingerprint auth occurred while asleep).
+     * - We're allowed to ignore auth and return to GONE, due to timeouts not elapsing.
+     */
+    val canWakeDirectlyToGone =
+        combine(
+                repository.isKeyguardEnabled,
+                isLockscreenDisabled,
+                repository.biometricUnlockState,
+                repository.canIgnoreAuthAndReturnToGone,
+            ) {
+                keyguardEnabled,
+                isLockscreenDisabled,
+                biometricUnlockState,
+                canIgnoreAuthAndReturnToGone ->
+                (!keyguardEnabled || isLockscreenDisabled) ||
+                    BiometricUnlockMode.isWakeAndUnlock(biometricUnlockState.mode) ||
+                    canIgnoreAuthAndReturnToGone
+            }
+            .distinctUntilChanged()
+
+    /**
+     * Counter that is incremented every time we wake up or stop dreaming. Upon sleeping/dreaming,
+     * we put the current value of this counter into the intent extras of the timeout alarm intent.
+     * If this value has changed by the time we receive the intent, it is discarded since it's out
+     * of date.
+     */
+    var timeoutCounter = 0
+
+    var isAwake = false
+
+    private val broadcastReceiver: BroadcastReceiver =
+        object : BroadcastReceiver() {
+            override fun onReceive(context: Context, intent: Intent) {
+                if (DELAYED_KEYGUARD_ACTION == intent.action) {
+                    val sequence = intent.getIntExtra(SEQ_EXTRA_KEY, 0)
+                    synchronized(this) {
+                        if (timeoutCounter == sequence) {
+                            // If the sequence # matches, we have not woken up or stopped dreaming
+                            // since
+                            // the alarm was set. That means this is still relevant - the lock
+                            // timeout
+                            // has elapsed, so let the repository know that we can no longer return
+                            // to
+                            // GONE without authenticating.
+                            repository.setCanIgnoreAuthAndReturnToGone(false)
+                        }
+                    }
+                }
+            }
+        }
+
+    init {
+        setOrCancelAlarmFromWakefulness()
+        listenForWakeToClearCanIgnoreAuth()
+        registerBroadcastReceiver()
+    }
+
+    fun onDreamingStarted() {
+        // If we start dreaming while awake, lock after the normal timeout.
+        if (isAwake) {
+            setResetCanIgnoreAuthAlarm()
+        }
+    }
+
+    fun onDreamingStopped() {
+        // Cancel the timeout if we stop dreaming while awake.
+        if (isAwake) {
+            cancelCanIgnoreAuthAlarm()
+        }
+    }
+
+    private fun setOrCancelAlarmFromWakefulness() {
+        scope.launch {
+            powerInteractor.detailedWakefulness
+                .distinctUntilChangedBy { it.isAwake() }
+                .sample(transitionInteractor.currentKeyguardState, ::Pair)
+                .collect { (wakefulness, currentState) ->
+                    // Save isAwake for use in onDreamingStarted/onDreamingStopped.
+                    this@KeyguardWakeDirectlyToGoneInteractor.isAwake = wakefulness.isAwake()
+
+                    // If we're sleeping from GONE, check the timeout and lock instantly settings.
+                    // These are not relevant if we're coming from non-GONE states.
+                    if (!isAwake && currentState == KeyguardState.GONE) {
+                        val lockTimeoutDuration = getCanIgnoreAuthAndReturnToGoneDuration()
+
+                        // If the screen timed out and went to sleep, and the lock timeout is > 0ms,
+                        // then we can return to GONE until that duration elapses. If the power
+                        // button was pressed but "instantly locks" is disabled, then we can also
+                        // return to GONE until the timeout duration elapses.
+                        if (
+                            (wakefulness.lastSleepReason == WakeSleepReason.TIMEOUT &&
+                                lockTimeoutDuration > 0) ||
+                                (wakefulness.lastSleepReason == WakeSleepReason.POWER_BUTTON &&
+                                    !willLockImmediately())
+                        ) {
+
+                            // Let the repository know that we can return to GONE until we notify
+                            // it otherwise.
+                            repository.setCanIgnoreAuthAndReturnToGone(true)
+                            setResetCanIgnoreAuthAlarm()
+                        }
+                    } else if (isAwake) {
+                        // If we're waking up, ignore the alarm if it goes off since it's no longer
+                        // relevant. Once a wake KeyguardTransition is started, we'll also clear the
+                        // canIgnoreAuthAndReturnToGone value in listenForWakeToClearCanIgnoreAuth.
+                        cancelCanIgnoreAuthAlarm()
+                    }
+                }
+        }
+    }
+
+    /** Clears the canIgnoreAuthAndReturnToGone value upon waking. */
+    private fun listenForWakeToClearCanIgnoreAuth() {
+        scope.launch {
+            transitionInteractor
+                .isInTransitionWhere(
+                    fromStatePredicate = { deviceIsAsleepInState(it) },
+                    toStatePredicate = { deviceIsAwakeInState(it) },
+                )
+                .collect {
+                    // This value is reset when the timeout alarm fires, but if the device is woken
+                    // back up before then, it needs to be reset here. The alarm is cancelled
+                    // immediately upon waking up, but since this value is used by keyguard
+                    // transition internals to decide whether we can transition to GONE, wait until
+                    // that decision is made before resetting it.
+                    repository.setCanIgnoreAuthAndReturnToGone(false)
+                }
+        }
+    }
+
+    /**
+     * Registers the broadcast receiver to receive the alarm intent.
+     *
+     * TODO(b/351817381): Investigate using BroadcastDispatcher vs. ignoring this lint warning.
+     */
+    @SuppressLint("WrongConstant", "RegisterReceiverViaContext")
+    private fun registerBroadcastReceiver() {
+        val delayedActionFilter = IntentFilter()
+        delayedActionFilter.addAction(KeyguardViewMediator.DELAYED_KEYGUARD_ACTION)
+        // TODO(b/346803756): Listen for DELAYED_LOCK_PROFILE_ACTION.
+        delayedActionFilter.priority = IntentFilter.SYSTEM_HIGH_PRIORITY
+        context.registerReceiver(
+            broadcastReceiver,
+            delayedActionFilter,
+            SYSTEMUI_PERMISSION,
+            null /* scheduler */,
+            Context.RECEIVER_EXPORTED_UNAUDITED
+        )
+    }
+
+    /** Set an alarm for */
+    private fun setResetCanIgnoreAuthAlarm() {
+        if (!KeyguardWmStateRefactor.isEnabled) {
+            return
+        }
+
+        val intent =
+            Intent(DELAYED_KEYGUARD_ACTION).apply {
+                setPackage(context.packageName)
+                putExtra(SEQ_EXTRA_KEY, timeoutCounter)
+                addFlags(Intent.FLAG_RECEIVER_FOREGROUND)
+            }
+
+        val sender =
+            PendingIntent.getBroadcast(
+                context,
+                0,
+                intent,
+                PendingIntent.FLAG_CANCEL_CURRENT or PendingIntent.FLAG_IMMUTABLE
+            )
+
+        val time = systemClock.elapsedRealtime() + getCanIgnoreAuthAndReturnToGoneDuration()
+        alarmManager.setExactAndAllowWhileIdle(AlarmManager.ELAPSED_REALTIME_WAKEUP, time, sender)
+
+        // TODO(b/346803756): Migrate support for child profiles.
+    }
+
+    /**
+     * Cancel the timeout by incrementing the counter so that we ignore the intent when it's
+     * received.
+     */
+    private fun cancelCanIgnoreAuthAlarm() {
+        timeoutCounter++
+    }
+
+    /**
+     * Whether pressing the power button locks the device immediately; vs. waiting for a specified
+     * timeout first.
+     */
+    private fun willLockImmediately(
+        userId: Int = selectedUserInteractor.getSelectedUserId()
+    ): Boolean {
+        return lockPatternUtils.getPowerButtonInstantlyLocks(userId) ||
+            !lockPatternUtils.isSecure(userId)
+    }
+
+    /**
+     * Returns whether the lockscreen is disabled, either because the keyguard service is disabled
+     * or because an adb command has disabled the lockscreen.
+     */
+    private fun isLockscreenDisabled(
+        userId: Int = selectedUserInteractor.getSelectedUserId()
+    ): Boolean {
+        return lockPatternUtils.isLockScreenDisabled(userId)
+    }
+
+    /**
+     * Returns the duration within which we can return to GONE without auth after a screen timeout
+     * (or power button press, if lock instantly is disabled).
+     *
+     * This takes into account the user's settings as well as device policy maximums.
+     */
+    private fun getCanIgnoreAuthAndReturnToGoneDuration(
+        userId: Int = selectedUserInteractor.getSelectedUserId()
+    ): Long {
+        // The timeout duration from settings (Security > Device Unlock > Gear icon > "Lock after
+        // screen timeout".
+        val durationSetting: Long =
+            secureSettings
+                .getIntForUser(
+                    Secure.LOCK_SCREEN_LOCK_AFTER_TIMEOUT,
+                    KEYGUARD_CAN_IGNORE_AUTH_DURATION,
+                    userId
+                )
+                .toLong()
+
+        // Device policy maximum timeout.
+        val durationDevicePolicyMax =
+            lockPatternUtils.devicePolicyManager.getMaximumTimeToLock(null, userId)
+
+        return if (durationDevicePolicyMax <= 0) {
+            durationSetting
+        } else {
+            var displayTimeout =
+                systemSettings
+                    .getIntForUser(
+                        Settings.System.SCREEN_OFF_TIMEOUT,
+                        KEYGUARD_DISPLAY_TIMEOUT_DELAY_DEFAULT,
+                        userId
+                    )
+                    .toLong()
+
+            // Ignore negative values. I don't know why this would be negative, but this check has
+            // been around since 2016 and I see no upside to removing it.
+            displayTimeout = max(displayTimeout, 0)
+
+            // Respect the shorter of: the device policy (maximum duration between last user action
+            // and fully locking) or the "Lock after screen timeout" setting.
+            max(min(durationDevicePolicyMax - displayTimeout, durationSetting), 0)
+        }
+    }
+
+    companion object {
+        private const val DELAYED_KEYGUARD_ACTION =
+            "com.android.internal.policy.impl.PhoneWindowManager.DELAYED_KEYGUARD"
+        private const val DELAYED_LOCK_PROFILE_ACTION =
+            "com.android.internal.policy.impl.PhoneWindowManager.DELAYED_LOCK"
+        private const val SYSTEMUI_PERMISSION = "com.android.systemui.permission.SELF"
+        private const val SEQ_EXTRA_KEY = "count"
+
+        private const val KEYGUARD_CAN_IGNORE_AUTH_DURATION = 5000
+        private const val KEYGUARD_DISPLAY_TIMEOUT_DELAY_DEFAULT = 30000
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/WindowManagerLockscreenVisibilityInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/WindowManagerLockscreenVisibilityInteractor.kt
index 523370c..e1b333d 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/WindowManagerLockscreenVisibilityInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/WindowManagerLockscreenVisibilityInteractor.kt
@@ -21,14 +21,17 @@
 import com.android.compose.animation.scene.ObservableTransitionState
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.deviceentry.domain.interactor.DeviceEntryInteractor
+import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository
 import com.android.systemui.keyguard.shared.model.BiometricUnlockMode
 import com.android.systemui.keyguard.shared.model.Edge
 import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.keyguard.shared.model.KeyguardState.Companion.deviceIsAsleepInState
 import com.android.systemui.keyguard.shared.model.TransitionState
 import com.android.systemui.scene.domain.interactor.SceneInteractor
 import com.android.systemui.scene.shared.flag.SceneContainerFlag
 import com.android.systemui.scene.shared.model.Scenes
 import com.android.systemui.statusbar.notification.domain.interactor.NotificationLaunchAnimationInteractor
+import com.android.systemui.util.kotlin.Utils.Companion.toTriple
 import com.android.systemui.util.kotlin.sample
 import com.android.systemui.utils.coroutines.flow.flatMapLatestConflated
 import dagger.Lazy
@@ -41,11 +44,13 @@
 import kotlinx.coroutines.flow.flowOf
 import kotlinx.coroutines.flow.map
 
+@OptIn(ExperimentalCoroutinesApi::class)
 @SysUISingleton
 class WindowManagerLockscreenVisibilityInteractor
 @Inject
 constructor(
     keyguardInteractor: KeyguardInteractor,
+    transitionRepository: KeyguardTransitionRepository,
     transitionInteractor: KeyguardTransitionInteractor,
     surfaceBehindInteractor: KeyguardSurfaceBehindInteractor,
     fromLockscreenInteractor: FromLockscreenTransitionInteractor,
@@ -54,9 +59,15 @@
     notificationLaunchAnimationInteractor: NotificationLaunchAnimationInteractor,
     sceneInteractor: Lazy<SceneInteractor>,
     deviceEntryInteractor: Lazy<DeviceEntryInteractor>,
+    wakeToGoneInteractor: KeyguardWakeDirectlyToGoneInteractor,
 ) {
     private val defaultSurfaceBehindVisibility =
-        transitionInteractor.finishedKeyguardState.map(::isSurfaceVisible)
+        combine(
+            transitionInteractor.finishedKeyguardState,
+            wakeToGoneInteractor.canWakeDirectlyToGone,
+        ) { finishedState, canWakeDirectlyToGone ->
+            isSurfaceVisible(finishedState) || canWakeDirectlyToGone
+        }
 
     /**
      * Surface visibility provided by the From*TransitionInteractor responsible for the currently
@@ -204,9 +215,13 @@
         if (SceneContainerFlag.isEnabled) {
             isDeviceNotEntered
         } else {
-            transitionInteractor.currentKeyguardState
-                .sample(transitionInteractor.startedStepWithPrecedingStep, ::Pair)
-                .map { (currentState, startedWithPrev) ->
+            combine(
+                    transitionInteractor.currentKeyguardState,
+                    wakeToGoneInteractor.canWakeDirectlyToGone,
+                    ::Pair
+                )
+                .sample(transitionInteractor.startedStepWithPrecedingStep, ::toTriple)
+                .map { (currentState, canWakeDirectlyToGone, startedWithPrev) ->
                     val startedFromStep = startedWithPrev.previousValue
                     val startedStep = startedWithPrev.newValue
                     val returningToGoneAfterCancellation =
@@ -214,16 +229,33 @@
                             startedFromStep.transitionState == TransitionState.CANCELED &&
                             startedFromStep.from == KeyguardState.GONE
 
-                    if (!returningToGoneAfterCancellation) {
-                        // By default, apply the lockscreen visibility of the current state.
-                        deviceEntryInteractor.get().isLockscreenEnabled() &&
-                            KeyguardState.lockscreenVisibleInState(currentState)
-                    } else {
-                        // If we're transitioning to GONE after a prior canceled transition from
-                        // GONE, then this is the camera launch transition from an asleep state back
-                        // to GONE. We don't want to show the lockscreen since we're aborting the
-                        // lock and going back to GONE.
+                    val transitionInfo = transitionRepository.currentTransitionInfoInternal.value
+                    val wakingDirectlyToGone =
+                        deviceIsAsleepInState(transitionInfo.from) &&
+                            transitionInfo.to == KeyguardState.GONE
+
+                    if (returningToGoneAfterCancellation || wakingDirectlyToGone) {
+                        // GONE -> AOD/DOZING (cancel) -> GONE is the camera launch transition,
+                        // which means we never want to show the lockscreen throughout the
+                        // transition. Same for waking directly to gone, due to the lockscreen being
+                        // disabled or because the device was woken back up before the lock timeout
+                        // duration elapsed.
                         KeyguardState.lockscreenVisibleInState(KeyguardState.GONE)
+                    } else if (canWakeDirectlyToGone) {
+                        // Never show the lockscreen if we can wake directly to GONE. This means
+                        // that the lock timeout has not yet elapsed, or the keyguard is disabled.
+                        // In either case, we don't show the activity lock screen until one of those
+                        // conditions changes.
+                        false
+                    } else if (
+                        currentState == KeyguardState.DREAMING &&
+                            deviceEntryInteractor.get().isUnlocked.value
+                    ) {
+                        // Dreams dismiss keyguard and return to GONE if they can.
+                        false
+                    } else {
+                        // Otherwise, use the visibility of the current state.
+                        KeyguardState.lockscreenVisibleInState(currentState)
                     }
                 }
                 .distinctUntilChanged()
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaCarouselController.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaCarouselController.kt
index 48970f5..46c5c18 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaCarouselController.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaCarouselController.kt
@@ -845,8 +845,25 @@
         commonViewModels.addAll(viewModels)
 
         // Ensure we only show the needed UMOs in media carousel.
-        val viewSet = viewModels.toHashSet()
-        controllerByViewModel.filter { !viewSet.contains(it.key) }.forEach { onRemoved(it.key) }
+        val viewIds =
+            viewModels
+                .map { mediaCommonViewModel ->
+                    when (mediaCommonViewModel) {
+                        is MediaCommonViewModel.MediaControl ->
+                            mediaCommonViewModel.instanceId.toString()
+                        is MediaCommonViewModel.MediaRecommendations -> mediaCommonViewModel.key
+                    }
+                }
+                .toHashSet()
+        controllerByViewModel
+            .filter {
+                when (val viewModel = it.key) {
+                    is MediaCommonViewModel.MediaControl ->
+                        !viewIds.contains(viewModel.instanceId.toString())
+                    is MediaCommonViewModel.MediaRecommendations -> !viewIds.contains(viewModel.key)
+                }
+            }
+            .forEach { onRemoved(it.key) }
     }
 
     private suspend fun getMediaLockScreenSetting(): Boolean {
diff --git a/packages/SystemUI/src/com/android/systemui/power/shared/model/WakeSleepReason.kt b/packages/SystemUI/src/com/android/systemui/power/shared/model/WakeSleepReason.kt
index 75055668..776a8f4 100644
--- a/packages/SystemUI/src/com/android/systemui/power/shared/model/WakeSleepReason.kt
+++ b/packages/SystemUI/src/com/android/systemui/power/shared/model/WakeSleepReason.kt
@@ -54,7 +54,10 @@
     OTHER(isTouch = false, PowerManager.WAKE_REASON_UNKNOWN),
 
     /** Device goes to sleep due to folding of a foldable device. */
-    FOLD(isTouch = false, PowerManager.GO_TO_SLEEP_REASON_DEVICE_FOLD);
+    FOLD(isTouch = false, PowerManager.GO_TO_SLEEP_REASON_DEVICE_FOLD),
+
+    /** Device goes to sleep because it timed out. */
+    TIMEOUT(isTouch = false, PowerManager.GO_TO_SLEEP_REASON_TIMEOUT);
 
     companion object {
         fun fromPowerManagerWakeReason(reason: Int): WakeSleepReason {
@@ -75,6 +78,7 @@
         fun fromPowerManagerSleepReason(reason: Int): WakeSleepReason {
             return when (reason) {
                 PowerManager.GO_TO_SLEEP_REASON_POWER_BUTTON -> POWER_BUTTON
+                PowerManager.GO_TO_SLEEP_REASON_TIMEOUT -> TIMEOUT
                 PowerManager.GO_TO_SLEEP_REASON_DEVICE_FOLD -> FOLD
                 else -> OTHER
             }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/SettingObserver.java b/packages/SystemUI/src/com/android/systemui/qs/SettingObserver.java
index 6092348..2287f4d 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/SettingObserver.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/SettingObserver.java
@@ -19,6 +19,7 @@
 import android.database.ContentObserver;
 import android.os.Handler;
 
+import com.android.systemui.Flags;
 import com.android.systemui.statusbar.policy.Listenable;
 import com.android.systemui.util.settings.SecureSettings;
 import com.android.systemui.util.settings.SettingsProxy;
@@ -74,10 +75,20 @@
         mListening = listening;
         if (listening) {
             mObservedValue = getValueFromProvider();
-            mSettingsProxy.registerContentObserverSync(
-                    mSettingsProxy.getUriFor(mSettingName), false, this);
+            if (Flags.qsRegisterSettingObserverOnBgThread()) {
+                mSettingsProxy.registerContentObserverAsync(
+                        mSettingsProxy.getUriFor(mSettingName), false, this,
+                        () -> mObservedValue = getValueFromProvider());
+            } else {
+                mSettingsProxy.registerContentObserverSync(
+                        mSettingsProxy.getUriFor(mSettingName), false, this);
+            }
         } else {
-            mSettingsProxy.unregisterContentObserverSync(this);
+            if (Flags.qsRegisterSettingObserverOnBgThread()) {
+                mSettingsProxy.unregisterContentObserverAsync(this);
+            } else {
+                mSettingsProxy.unregisterContentObserverSync(this);
+            }
             mObservedValue = mDefaultValue;
         }
     }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/Tile.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/Tile.kt
index 2f87b01..3fdd7f7 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/Tile.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/Tile.kt
@@ -22,7 +22,6 @@
 import android.service.quicksettings.Tile.STATE_ACTIVE
 import android.service.quicksettings.Tile.STATE_INACTIVE
 import android.text.TextUtils
-import android.util.Log
 import androidx.appcompat.content.res.AppCompatResources
 import androidx.compose.animation.graphics.ExperimentalAnimationGraphicsApi
 import androidx.compose.animation.graphics.res.animatedVectorResource
@@ -465,7 +464,6 @@
     animateToEnd: Boolean = false,
     modifier: Modifier = Modifier,
 ) {
-    Log.d("Fabian", "Recomposing tile icon")
     val iconModifier = modifier.size(dimensionResource(id = R.dimen.qs_icon_size))
     val context = LocalContext.current
     val loadedDrawable =
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
index 04de2c2..c1caeed 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
@@ -1774,8 +1774,9 @@
         // the small clock here
         // With migrateClocksToBlueprint, weather clock will have behaviors similar to other clocks
         if (!MigrateClocksToBlueprint.isEnabled()) {
+            boolean bypassEnabled = mKeyguardBypassController.getBypassEnabled();
             if (mKeyguardStatusViewController.isLargeClockBlockingNotificationShelf()
-                    && hasVisibleNotifications() && isOnAod()) {
+                    && hasVisibleNotifications() && (isOnAod() || bypassEnabled)) {
                 return SMALL;
             }
         }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/casttootherdevice/ui/viewmodel/CastToOtherDeviceChipViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/casttootherdevice/ui/viewmodel/CastToOtherDeviceChipViewModel.kt
index 4183cdd..f5e17df 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/casttootherdevice/ui/viewmodel/CastToOtherDeviceChipViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/casttootherdevice/ui/viewmodel/CastToOtherDeviceChipViewModel.kt
@@ -18,7 +18,6 @@
 
 import android.content.Context
 import androidx.annotation.DrawableRes
-import com.android.systemui.animation.DialogTransitionAnimator
 import com.android.systemui.common.shared.model.ContentDescription
 import com.android.systemui.common.shared.model.Icon
 import com.android.systemui.dagger.SysUISingleton
@@ -58,7 +57,6 @@
     private val mediaProjectionChipInteractor: MediaProjectionChipInteractor,
     private val mediaRouterChipInteractor: MediaRouterChipInteractor,
     private val systemClock: SystemClock,
-    private val dialogTransitionAnimator: DialogTransitionAnimator,
     private val endMediaProjectionDialogHelper: EndMediaProjectionDialogHelper,
 ) : OngoingActivityChipViewModel {
     /**
@@ -175,7 +173,6 @@
             startTimeMs = systemClock.elapsedRealtime(),
             createDialogLaunchOnClickListener(
                 createCastScreenToOtherDeviceDialogDelegate(state),
-                dialogTransitionAnimator,
             ),
         )
     }
@@ -191,7 +188,6 @@
             colors = ColorsModel.Red,
             createDialogLaunchOnClickListener(
                 createGenericCastToOtherDeviceDialogDelegate(deviceName),
-                dialogTransitionAnimator,
             ),
         )
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/screenrecord/ui/viewmodel/ScreenRecordChipViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/screenrecord/ui/viewmodel/ScreenRecordChipViewModel.kt
index df25d57..e201652 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/screenrecord/ui/viewmodel/ScreenRecordChipViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/screenrecord/ui/viewmodel/ScreenRecordChipViewModel.kt
@@ -19,7 +19,6 @@
 import android.app.ActivityManager
 import android.content.Context
 import androidx.annotation.DrawableRes
-import com.android.systemui.animation.DialogTransitionAnimator
 import com.android.systemui.common.shared.model.ContentDescription
 import com.android.systemui.common.shared.model.Icon
 import com.android.systemui.dagger.SysUISingleton
@@ -52,7 +51,6 @@
     private val interactor: ScreenRecordChipInteractor,
     private val systemClock: SystemClock,
     private val endMediaProjectionDialogHelper: EndMediaProjectionDialogHelper,
-    private val dialogTransitionAnimator: DialogTransitionAnimator,
 ) : OngoingActivityChipViewModel {
     override val chip: StateFlow<OngoingActivityChipModel> =
         interactor.screenRecordState
@@ -78,7 +76,6 @@
                             startTimeMs = systemClock.elapsedRealtime(),
                             createDialogLaunchOnClickListener(
                                 createDelegate(state.recordedTask),
-                                dialogTransitionAnimator,
                             ),
                         )
                     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/sharetoapp/ui/viewmodel/ShareToAppChipViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/sharetoapp/ui/viewmodel/ShareToAppChipViewModel.kt
index c097720..45260e18 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/sharetoapp/ui/viewmodel/ShareToAppChipViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/sharetoapp/ui/viewmodel/ShareToAppChipViewModel.kt
@@ -18,7 +18,6 @@
 
 import android.content.Context
 import androidx.annotation.DrawableRes
-import com.android.systemui.animation.DialogTransitionAnimator
 import com.android.systemui.common.shared.model.ContentDescription
 import com.android.systemui.common.shared.model.Icon
 import com.android.systemui.dagger.SysUISingleton
@@ -52,7 +51,6 @@
     private val context: Context,
     private val mediaProjectionChipInteractor: MediaProjectionChipInteractor,
     private val systemClock: SystemClock,
-    private val dialogTransitionAnimator: DialogTransitionAnimator,
     private val endMediaProjectionDialogHelper: EndMediaProjectionDialogHelper,
 ) : OngoingActivityChipViewModel {
     override val chip: StateFlow<OngoingActivityChipModel> =
@@ -89,10 +87,7 @@
             colors = ColorsModel.Red,
             // TODO(b/332662551): Maybe use a MediaProjection API to fetch this time.
             startTimeMs = systemClock.elapsedRealtime(),
-            createDialogLaunchOnClickListener(
-                createShareToAppDialogDelegate(state),
-                dialogTransitionAnimator
-            ),
+            createDialogLaunchOnClickListener(createShareToAppDialogDelegate(state)),
         )
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipViewModel.kt
index 0dbf5d6..65f94ac 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipViewModel.kt
@@ -17,10 +17,7 @@
 package com.android.systemui.statusbar.chips.ui.viewmodel
 
 import android.view.View
-import com.android.systemui.animation.DialogTransitionAnimator
-import com.android.systemui.res.R
 import com.android.systemui.statusbar.chips.ui.model.OngoingActivityChipModel
-import com.android.systemui.statusbar.chips.ui.view.ChipBackgroundContainer
 import com.android.systemui.statusbar.phone.SystemUIDialog
 import kotlinx.coroutines.flow.StateFlow
 
@@ -36,19 +33,10 @@
         /** Creates a chip click listener that launches a dialog created by [dialogDelegate]. */
         fun createDialogLaunchOnClickListener(
             dialogDelegate: SystemUIDialog.Delegate,
-            dialogTransitionAnimator: DialogTransitionAnimator,
         ): View.OnClickListener {
             return View.OnClickListener { view ->
                 val dialog = dialogDelegate.createDialog()
-                val launchableView =
-                    view.requireViewById<ChipBackgroundContainer>(
-                        R.id.ongoing_activity_chip_background
-                    )
-                // TODO(b/343699052): This makes a beautiful animate-in, but the
-                //  animate-out looks odd because the dialog animates back into the chip
-                //  but then the chip disappears. If we aren't able to address
-                //  b/343699052 in time for launch, we should just use `dialog.show`.
-                dialogTransitionAnimator.showFromView(dialog, launchableView)
+                dialog.show()
             }
         }
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RichOngoingNotificationContentExtractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RichOngoingNotificationContentExtractor.kt
index b5ea861..b8af369 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RichOngoingNotificationContentExtractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RichOngoingNotificationContentExtractor.kt
@@ -17,6 +17,7 @@
 package com.android.systemui.statusbar.notification.row
 
 import android.app.Notification
+import android.app.Notification.RichOngoingStyle
 import android.app.PendingIntent
 import android.content.Context
 import android.util.Log
@@ -68,12 +69,14 @@
         builder: Notification.Builder,
         systemUIContext: Context,
         packageContext: Context
-    ): RichOngoingContentModel? =
+    ): RichOngoingContentModel? {
+        if (builder.style !is RichOngoingStyle) return null
+
         try {
             val sbn = entry.sbn
             val notification = sbn.notification
             val icon = IconModel(notification.smallIcon)
-            if (sbn.packageName == "com.google.android.deskclock") {
+            return if (sbn.packageName == "com.google.android.deskclock") {
                 when (notification.channelId) {
                     "Timers v2" -> {
                         parseTimerNotification(notification, icon)
@@ -90,8 +93,9 @@
             } else null
         } catch (e: Exception) {
             Log.e("RONs", "Error parsing RON", e)
-            null
+            return null
         }
+    }
 
     /**
      * FOR PROTOTYPING ONLY: create a RON TimerContentModel using the time information available
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java
index a2d7281..a2e44df 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java
@@ -31,7 +31,6 @@
 import com.android.internal.logging.UiEventLogger;
 import com.android.internal.policy.SystemBarUtils;
 import com.android.systemui.dagger.SysUISingleton;
-import com.android.systemui.dagger.qualifiers.Background;
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.plugins.statusbar.StatusBarStateController.StateListener;
@@ -172,10 +171,8 @@
                 updateResources();
             }
         });
-        if (!NotificationsHeadsUpRefactor.isEnabled()) {
-            javaAdapter.alwaysCollectFlow(shadeInteractor.isAnyExpanded(),
+        javaAdapter.alwaysCollectFlow(shadeInteractor.isAnyExpanded(),
                     this::onShadeOrQsExpanded);
-        }
     }
 
     public void setAnimationStateHandler(AnimationStateHandler handler) {
@@ -270,10 +267,9 @@
     }
 
     private void onShadeOrQsExpanded(Boolean isExpanded) {
-        NotificationsHeadsUpRefactor.assertInLegacyMode();
         if (isExpanded != mIsExpanded) {
             mIsExpanded = isExpanded;
-            if (isExpanded) {
+            if (!NotificationsHeadsUpRefactor.isEnabled() && isExpanded) {
                 mHeadsUpAnimatingAway.setValue(false);
             }
         }
diff --git a/packages/SystemUI/src/com/android/systemui/util/ConvenienceExtensions.kt b/packages/SystemUI/src/com/android/systemui/util/ConvenienceExtensions.kt
index ec7aabb..f2132248 100644
--- a/packages/SystemUI/src/com/android/systemui/util/ConvenienceExtensions.kt
+++ b/packages/SystemUI/src/com/android/systemui/util/ConvenienceExtensions.kt
@@ -20,6 +20,7 @@
 import android.util.IndentingPrintWriter
 import android.view.View
 import android.view.ViewGroup
+import dagger.Lazy
 import java.io.PrintWriter
 
 /** [Sequence] that yields all of the direct children of this [ViewGroup] */
@@ -56,3 +57,8 @@
         getBoundsOnScreen(bounds)
         return bounds
     }
+
+/** Extension method to convert [dagger.Lazy] to [kotlin.Lazy] for object of any class [T]. */
+fun <T> Lazy<T>.toKotlinLazy(): kotlin.Lazy<T> {
+    return lazy { this.get() }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractorTest.kt
index 42ab25f..032794c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractorTest.kt
@@ -56,6 +56,7 @@
 import kotlinx.coroutines.test.runCurrent
 import kotlinx.coroutines.test.runTest
 import org.junit.Before
+import org.junit.Ignore
 import org.junit.runner.RunWith
 import org.mockito.Mockito.reset
 import org.mockito.Mockito.spy
@@ -97,6 +98,7 @@
 
     @Test
     @EnableFlags(Flags.FLAG_KEYGUARD_WM_STATE_REFACTOR)
+    @Ignore("Until b/349837588 is fixed")
     fun testTransitionToOccluded_ifDreamEnds_occludingActivityOnTop() =
         testScope.runTest {
             kosmos.fakeKeyguardRepository.setDreaming(true)
@@ -156,6 +158,7 @@
             reset(transitionRepository)
 
             kosmos.keyguardOcclusionRepository.setShowWhenLockedActivityInfo(onTop = false)
+            kosmos.fakeKeyguardRepository.setDreaming(false)
             runCurrent()
 
             assertThat(transitionRepository)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/InWindowLauncherUnlockAnimationInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/InWindowLauncherUnlockAnimationInteractorTest.kt
index 459e41d..ea5a41f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/InWindowLauncherUnlockAnimationInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/InWindowLauncherUnlockAnimationInteractorTest.kt
@@ -18,25 +18,22 @@
 
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
-import com.android.systemui.SysUITestModule
 import com.android.systemui.SysuiTestCase
-import com.android.systemui.TestMocksModule
-import com.android.systemui.biometrics.domain.BiometricsDomainLayerModule
 import com.android.systemui.coroutines.collectValues
-import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.keyguard.data.repository.FakeKeyguardSurfaceBehindRepository
 import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
-import com.android.systemui.keyguard.data.repository.InWindowLauncherUnlockAnimationRepository
+import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
+import com.android.systemui.keyguard.data.repository.inWindowLauncherUnlockAnimationRepository
+import com.android.systemui.keyguard.data.repository.keyguardSurfaceBehindRepository
 import com.android.systemui.keyguard.shared.model.KeyguardState
 import com.android.systemui.keyguard.shared.model.TransitionState
 import com.android.systemui.keyguard.shared.model.TransitionStep
 import com.android.systemui.keyguard.util.mockTopActivityClassName
+import com.android.systemui.kosmos.applicationCoroutineScope
+import com.android.systemui.kosmos.testScope
 import com.android.systemui.shared.system.ActivityManagerWrapper
-import com.android.systemui.user.domain.UserDomainLayerModule
-import dagger.BindsInstance
-import dagger.Component
+import com.android.systemui.shared.system.activityManagerWrapper
+import com.android.systemui.testKosmos
 import junit.framework.Assert.assertEquals
-import kotlinx.coroutines.test.TestScope
 import kotlinx.coroutines.test.runCurrent
 import kotlinx.coroutines.test.runTest
 import org.junit.Before
@@ -49,10 +46,16 @@
 @RunWith(AndroidJUnit4::class)
 @kotlinx.coroutines.ExperimentalCoroutinesApi
 class InWindowLauncherUnlockAnimationInteractorTest : SysuiTestCase() {
-    private lateinit var underTest: InWindowLauncherUnlockAnimationInteractor
-
-    private lateinit var testComponent: TestComponent
-    private lateinit var testScope: TestScope
+    private val kosmos = testKosmos()
+    private val underTest =
+        InWindowLauncherUnlockAnimationInteractor(
+            kosmos.inWindowLauncherUnlockAnimationRepository,
+            kosmos.applicationCoroutineScope,
+            kosmos.keyguardTransitionInteractor,
+            { kosmos.keyguardSurfaceBehindRepository },
+            kosmos.activityManagerWrapper,
+        )
+    private val testScope = kosmos.testScope
     private lateinit var transitionRepository: FakeKeyguardTransitionRepository
     @Mock private lateinit var activityManagerWrapper: ActivityManagerWrapper
 
@@ -62,19 +65,9 @@
     fun setUp() {
         MockitoAnnotations.initMocks(this)
 
-        testComponent =
-            DaggerInWindowLauncherUnlockAnimationInteractorTest_TestComponent.factory()
-                .create(
-                    test = this,
-                    mocks =
-                        TestMocksModule(
-                            activityManagerWrapper = activityManagerWrapper,
-                        ),
-                )
-        underTest = testComponent.underTest
-        testScope = testComponent.testScope
-        transitionRepository = testComponent.transitionRepository
+        transitionRepository = kosmos.fakeKeyguardTransitionRepository
 
+        activityManagerWrapper = kosmos.activityManagerWrapper
         activityManagerWrapper.mockTopActivityClassName(launcherClassName)
     }
 
@@ -92,7 +85,7 @@
             )
 
             // Put launcher on top
-            testComponent.inWindowLauncherUnlockAnimationRepository.setLauncherActivityClass(
+            kosmos.inWindowLauncherUnlockAnimationRepository.setLauncherActivityClass(
                 launcherClassName
             )
             activityManagerWrapper.mockTopActivityClassName(launcherClassName)
@@ -175,7 +168,7 @@
             )
 
             // Put not launcher on top
-            testComponent.inWindowLauncherUnlockAnimationRepository.setLauncherActivityClass(
+            kosmos.inWindowLauncherUnlockAnimationRepository.setLauncherActivityClass(
                 launcherClassName
             )
             activityManagerWrapper.mockTopActivityClassName("not_launcher")
@@ -252,7 +245,7 @@
             )
 
             // Put launcher on top
-            testComponent.inWindowLauncherUnlockAnimationRepository.setLauncherActivityClass(
+            kosmos.inWindowLauncherUnlockAnimationRepository.setLauncherActivityClass(
                 launcherClassName
             )
             activityManagerWrapper.mockTopActivityClassName(launcherClassName)
@@ -296,7 +289,7 @@
             )
 
             // Put Launcher on top and begin transitioning to GONE.
-            testComponent.inWindowLauncherUnlockAnimationRepository.setLauncherActivityClass(
+            kosmos.inWindowLauncherUnlockAnimationRepository.setLauncherActivityClass(
                 launcherClassName
             )
             activityManagerWrapper.mockTopActivityClassName(launcherClassName)
@@ -316,7 +309,7 @@
                 values
             )
 
-            testComponent.surfaceBehindRepository.setSurfaceRemoteAnimationTargetAvailable(true)
+            kosmos.keyguardSurfaceBehindRepository.setSurfaceRemoteAnimationTargetAvailable(true)
             runCurrent()
 
             assertEquals(
@@ -360,7 +353,7 @@
             )
 
             // Put Launcher on top and begin transitioning to GONE.
-            testComponent.inWindowLauncherUnlockAnimationRepository.setLauncherActivityClass(
+            kosmos.inWindowLauncherUnlockAnimationRepository.setLauncherActivityClass(
                 launcherClassName
             )
             activityManagerWrapper.mockTopActivityClassName(launcherClassName)
@@ -402,7 +395,7 @@
             )
 
             // Put Launcher on top and begin transitioning to GONE.
-            testComponent.inWindowLauncherUnlockAnimationRepository.setLauncherActivityClass(
+            kosmos.inWindowLauncherUnlockAnimationRepository.setLauncherActivityClass(
                 launcherClassName
             )
             activityManagerWrapper.mockTopActivityClassName(launcherClassName)
@@ -427,7 +420,7 @@
                     to = KeyguardState.AOD,
                 )
             )
-            testComponent.surfaceBehindRepository.setSurfaceRemoteAnimationTargetAvailable(true)
+            kosmos.keyguardSurfaceBehindRepository.setSurfaceRemoteAnimationTargetAvailable(true)
             runCurrent()
 
             assertEquals(
@@ -437,29 +430,4 @@
                 values
             )
         }
-
-    @SysUISingleton
-    @Component(
-        modules =
-            [
-                SysUITestModule::class,
-                BiometricsDomainLayerModule::class,
-                UserDomainLayerModule::class,
-            ]
-    )
-    interface TestComponent {
-        val underTest: InWindowLauncherUnlockAnimationInteractor
-        val testScope: TestScope
-        val transitionRepository: FakeKeyguardTransitionRepository
-        val surfaceBehindRepository: FakeKeyguardSurfaceBehindRepository
-        val inWindowLauncherUnlockAnimationRepository: InWindowLauncherUnlockAnimationRepository
-
-        @Component.Factory
-        interface Factory {
-            fun create(
-                @BindsInstance test: SysuiTestCase,
-                mocks: TestMocksModule,
-            ): TestComponent
-        }
-    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardWakeDirectlyToGoneInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardWakeDirectlyToGoneInteractorTest.kt
new file mode 100644
index 0000000..7e249e8
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardWakeDirectlyToGoneInteractorTest.kt
@@ -0,0 +1,378 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.domain.interactor
+
+import android.app.AlarmManager
+import android.app.admin.alarmManager
+import android.app.admin.devicePolicyManager
+import android.content.BroadcastReceiver
+import android.content.Intent
+import android.content.mockedContext
+import android.os.PowerManager
+import android.os.UserHandle
+import android.platform.test.annotations.EnableFlags
+import android.provider.Settings
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.internal.widget.lockPatternUtils
+import com.android.systemui.Flags
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectValues
+import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
+import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
+import com.android.systemui.keyguard.shared.model.BiometricUnlockMode
+import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.setAsleepForTest
+import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.setAwakeForTest
+import com.android.systemui.power.domain.interactor.powerInteractor
+import com.android.systemui.testKosmos
+import com.android.systemui.util.settings.fakeSettings
+import junit.framework.Assert.assertEquals
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers.anyInt
+import org.mockito.ArgumentMatchers.anyLong
+import org.mockito.kotlin.any
+import org.mockito.kotlin.doAnswer
+import org.mockito.kotlin.eq
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.verify
+import org.mockito.kotlin.whenever
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class KeyguardWakeDirectlyToGoneInteractorTest : SysuiTestCase() {
+
+    private var lastRegisteredBroadcastReceiver: BroadcastReceiver? = null
+    private val kosmos =
+        testKosmos().apply {
+            whenever(mockedContext.user).thenReturn(mock<UserHandle>())
+            doAnswer { invocation ->
+                    lastRegisteredBroadcastReceiver = invocation.arguments[0] as BroadcastReceiver
+                }
+                .whenever(mockedContext)
+                .registerReceiver(any(), any(), any(), any(), any())
+        }
+
+    private val testScope = kosmos.testScope
+    private val underTest = kosmos.keyguardWakeDirectlyToGoneInteractor
+    private val lockPatternUtils = kosmos.lockPatternUtils
+    private val repository = kosmos.fakeKeyguardRepository
+    private val transitionRepository = kosmos.fakeKeyguardTransitionRepository
+
+    @Test
+    @EnableFlags(Flags.FLAG_KEYGUARD_WM_STATE_REFACTOR)
+    fun testCanWakeDirectlyToGone_keyguardServiceEnabledThenDisabled() =
+        testScope.runTest {
+            val canWake by collectValues(underTest.canWakeDirectlyToGone)
+
+            assertEquals(
+                listOf(
+                    false, // Defaults to false.
+                ),
+                canWake
+            )
+
+            repository.setKeyguardEnabled(false)
+            runCurrent()
+
+            assertEquals(
+                listOf(
+                    false, // Default to false.
+                    true, // True now that keyguard service is disabled
+                ),
+                canWake
+            )
+
+            repository.setKeyguardEnabled(true)
+            runCurrent()
+
+            assertEquals(
+                listOf(
+                    false,
+                    true,
+                    false,
+                ),
+                canWake
+            )
+        }
+
+    @Test
+    @EnableFlags(Flags.FLAG_KEYGUARD_WM_STATE_REFACTOR)
+    fun testCanWakeDirectlyToGone_lockscreenDisabledThenEnabled() =
+        testScope.runTest {
+            val canWake by collectValues(underTest.canWakeDirectlyToGone)
+
+            assertEquals(
+                listOf(
+                    false, // Defaults to false.
+                ),
+                canWake
+            )
+
+            whenever(lockPatternUtils.isLockScreenDisabled(anyInt())).thenReturn(true)
+            runCurrent()
+
+            assertEquals(
+                listOf(
+                    // Still false - isLockScreenDisabled only causes canWakeDirectlyToGone to
+                    // update on the next wake/sleep event.
+                    false,
+                ),
+                canWake
+            )
+
+            kosmos.powerInteractor.setAsleepForTest()
+            runCurrent()
+
+            assertEquals(
+                listOf(
+                    false,
+                    // True since we slept after setting isLockScreenDisabled=true
+                    true,
+                ),
+                canWake
+            )
+
+            kosmos.powerInteractor.setAwakeForTest()
+            runCurrent()
+
+            kosmos.powerInteractor.setAsleepForTest()
+            runCurrent()
+
+            assertEquals(
+                listOf(
+                    false,
+                    true,
+                ),
+                canWake
+            )
+
+            whenever(lockPatternUtils.isLockScreenDisabled(anyInt())).thenReturn(false)
+            kosmos.powerInteractor.setAwakeForTest()
+            runCurrent()
+
+            assertEquals(
+                listOf(
+                    false,
+                    true,
+                    false,
+                ),
+                canWake
+            )
+        }
+
+    @Test
+    @EnableFlags(Flags.FLAG_KEYGUARD_WM_STATE_REFACTOR)
+    fun testCanWakeDirectlyToGone_wakeAndUnlock() =
+        testScope.runTest {
+            val canWake by collectValues(underTest.canWakeDirectlyToGone)
+
+            assertEquals(
+                listOf(
+                    false, // Defaults to false.
+                ),
+                canWake
+            )
+
+            repository.setBiometricUnlockState(BiometricUnlockMode.WAKE_AND_UNLOCK)
+            runCurrent()
+
+            assertEquals(listOf(false, true), canWake)
+
+            repository.setBiometricUnlockState(BiometricUnlockMode.NONE)
+            runCurrent()
+
+            assertEquals(listOf(false, true, false), canWake)
+        }
+
+    @Test
+    @EnableFlags(Flags.FLAG_KEYGUARD_WM_STATE_REFACTOR)
+    fun testCanWakeDirectlyToGone_andSetsAlarm_ifPowerButtonDoesNotLockImmediately() =
+        testScope.runTest {
+            val canWake by collectValues(underTest.canWakeDirectlyToGone)
+
+            assertEquals(
+                listOf(
+                    false, // Defaults to false.
+                ),
+                canWake
+            )
+
+            repository.setCanIgnoreAuthAndReturnToGone(true)
+            runCurrent()
+
+            assertEquals(listOf(false, true), canWake)
+
+            repository.setCanIgnoreAuthAndReturnToGone(false)
+            runCurrent()
+
+            assertEquals(listOf(false, true, false), canWake)
+        }
+
+    @Test
+    @EnableFlags(Flags.FLAG_KEYGUARD_WM_STATE_REFACTOR)
+    fun testSetsCanIgnoreAuth_andSetsAlarm_whenTimingOut() =
+        testScope.runTest {
+            val canWake by collectValues(underTest.canWakeDirectlyToGone)
+
+            assertEquals(
+                listOf(
+                    false, // Defaults to false.
+                ),
+                canWake
+            )
+
+            whenever(kosmos.devicePolicyManager.getMaximumTimeToLock(eq(null), anyInt()))
+                .thenReturn(-1)
+            kosmos.fakeSettings.putInt(Settings.Secure.LOCK_SCREEN_LOCK_AFTER_TIMEOUT, 500)
+
+            transitionRepository.sendTransitionSteps(
+                from = KeyguardState.LOCKSCREEN,
+                to = KeyguardState.GONE,
+                testScope,
+            )
+
+            kosmos.powerInteractor.setAsleepForTest(
+                sleepReason = PowerManager.GO_TO_SLEEP_REASON_TIMEOUT
+            )
+            runCurrent()
+
+            assertEquals(
+                listOf(
+                    false,
+                    true,
+                ),
+                canWake
+            )
+
+            verify(kosmos.alarmManager)
+                .setExactAndAllowWhileIdle(
+                    eq(AlarmManager.ELAPSED_REALTIME_WAKEUP),
+                    anyLong(),
+                    any(),
+                )
+        }
+
+    @Test
+    @EnableFlags(Flags.FLAG_KEYGUARD_WM_STATE_REFACTOR)
+    fun testCancelsFirstAlarm_onWake_withSecondAlarmSet() =
+        testScope.runTest {
+            val canWake by collectValues(underTest.canWakeDirectlyToGone)
+
+            assertEquals(
+                listOf(
+                    false, // Defaults to false.
+                ),
+                canWake
+            )
+
+            whenever(kosmos.devicePolicyManager.getMaximumTimeToLock(eq(null), anyInt()))
+                .thenReturn(-1)
+            kosmos.fakeSettings.putInt(Settings.Secure.LOCK_SCREEN_LOCK_AFTER_TIMEOUT, 500)
+
+            transitionRepository.sendTransitionSteps(
+                from = KeyguardState.LOCKSCREEN,
+                to = KeyguardState.GONE,
+                testScope,
+            )
+
+            kosmos.powerInteractor.setAsleepForTest(
+                sleepReason = PowerManager.GO_TO_SLEEP_REASON_TIMEOUT
+            )
+            transitionRepository.sendTransitionSteps(
+                from = KeyguardState.LOCKSCREEN,
+                to = KeyguardState.AOD,
+                testScope = testScope,
+            )
+            runCurrent()
+
+            assertEquals(
+                listOf(
+                    false,
+                    // Timed out, so we can ignore auth/return to GONE.
+                    true,
+                ),
+                canWake
+            )
+
+            verify(kosmos.alarmManager)
+                .setExactAndAllowWhileIdle(
+                    eq(AlarmManager.ELAPSED_REALTIME_WAKEUP),
+                    anyLong(),
+                    any(),
+                )
+
+            kosmos.powerInteractor.setAwakeForTest()
+            transitionRepository.sendTransitionSteps(
+                from = KeyguardState.AOD,
+                to = KeyguardState.GONE,
+                testScope = testScope,
+            )
+            runCurrent()
+
+            assertEquals(
+                listOf(
+                    false,
+                    true,
+                    // Should be canceled by the wakeup, but there would still be an
+                    // alarm in flight that should be canceled.
+                    false,
+                ),
+                canWake
+            )
+
+            kosmos.powerInteractor.setAsleepForTest(
+                sleepReason = PowerManager.GO_TO_SLEEP_REASON_TIMEOUT
+            )
+            runCurrent()
+
+            assertEquals(
+                listOf(
+                    false,
+                    true,
+                    false,
+                    // Back to sleep.
+                    true,
+                ),
+                canWake
+            )
+
+            // Simulate the first sleep's alarm coming in.
+            lastRegisteredBroadcastReceiver?.onReceive(
+                kosmos.mockedContext,
+                Intent("com.android.internal.policy.impl.PhoneWindowManager.DELAYED_KEYGUARD")
+            )
+            runCurrent()
+
+            // It should not have any effect.
+            assertEquals(
+                listOf(
+                    false,
+                    true,
+                    false,
+                    true,
+                ),
+                canWake
+            )
+        }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/binder/InWindowLauncherUnlockAnimationManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/binder/InWindowLauncherUnlockAnimationManagerTest.kt
index c7f4416..0cfc20d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/binder/InWindowLauncherUnlockAnimationManagerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/binder/InWindowLauncherUnlockAnimationManagerTest.kt
@@ -18,17 +18,15 @@
 
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
-import com.android.systemui.SysUITestModule
 import com.android.systemui.SysuiTestCase
-import com.android.systemui.biometrics.domain.BiometricsDomainLayerModule
-import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.keyguard.domain.interactor.inWindowLauncherUnlockAnimationInteractor
 import com.android.systemui.keyguard.ui.view.InWindowLauncherUnlockAnimationManager
+import com.android.systemui.keyguard.ui.viewmodel.InWindowLauncherAnimationViewModel
+import com.android.systemui.kosmos.applicationCoroutineScope
+import com.android.systemui.kosmos.testScope
 import com.android.systemui.shared.system.smartspace.ILauncherUnlockAnimationController
-import com.android.systemui.user.domain.UserDomainLayerModule
+import com.android.systemui.testKosmos
 import com.android.systemui.util.mockito.any
-import dagger.BindsInstance
-import dagger.Component
-import kotlinx.coroutines.test.TestScope
 import kotlinx.coroutines.test.runTest
 import org.junit.Before
 import org.junit.Test
@@ -45,10 +43,9 @@
 @RunWith(AndroidJUnit4::class)
 @kotlinx.coroutines.ExperimentalCoroutinesApi
 class InWindowLauncherUnlockAnimationManagerTest : SysuiTestCase() {
+    private val kosmos = testKosmos()
     private lateinit var underTest: InWindowLauncherUnlockAnimationManager
-
-    private lateinit var testComponent: TestComponent
-    private lateinit var testScope: TestScope
+    private val testScope = kosmos.testScope
 
     @Mock private lateinit var launcherUnlockAnimationController: ILauncherUnlockAnimationController
 
@@ -56,14 +53,14 @@
     fun setUp() {
         MockitoAnnotations.initMocks(this)
 
-        testComponent =
-            DaggerInWindowLauncherUnlockAnimationManagerTest_TestComponent.factory()
-                .create(
-                    test = this,
-                )
-        underTest = testComponent.underTest
-        testScope = testComponent.testScope
-
+        underTest =
+            InWindowLauncherUnlockAnimationManager(
+                kosmos.inWindowLauncherUnlockAnimationInteractor,
+                InWindowLauncherAnimationViewModel(
+                    kosmos.inWindowLauncherUnlockAnimationInteractor
+                ),
+                kosmos.applicationCoroutineScope
+            )
         underTest.setLauncherUnlockController("launcherClass", launcherUnlockAnimationController)
     }
 
@@ -114,25 +111,4 @@
 
             verifyNoMoreInteractions(launcherUnlockAnimationController)
         }
-
-    @SysUISingleton
-    @Component(
-        modules =
-            [
-                SysUITestModule::class,
-                BiometricsDomainLayerModule::class,
-                UserDomainLayerModule::class,
-            ]
-    )
-    interface TestComponent {
-        val underTest: InWindowLauncherUnlockAnimationManager
-        val testScope: TestScope
-
-        @Component.Factory
-        interface Factory {
-            fun create(
-                @BindsInstance test: SysuiTestCase,
-            ): TestComponent
-        }
-    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/LockscreenShadeTransitionControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/LockscreenShadeTransitionControllerTest.kt
index 69e8f47..9e6a498 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/LockscreenShadeTransitionControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/LockscreenShadeTransitionControllerTest.kt
@@ -6,26 +6,23 @@
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.systemui.ExpandHelper
-import com.android.systemui.SysUITestModule
 import com.android.systemui.SysuiTestCase
-import com.android.systemui.TestMocksModule
-import com.android.systemui.biometrics.domain.BiometricsDomainLayerModule
 import com.android.systemui.classifier.FalsingCollectorFake
 import com.android.systemui.classifier.FalsingManagerFake
-import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.flags.FakeFeatureFlagsClassicModule
 import com.android.systemui.flags.Flags
+import com.android.systemui.flags.fakeFeatureFlagsClassic
 import com.android.systemui.keyguard.domain.interactor.NaturalScrollingSettingObserver
+import com.android.systemui.kosmos.testScope
 import com.android.systemui.media.controls.ui.controller.MediaHierarchyManager
 import com.android.systemui.plugins.qs.QS
-import com.android.systemui.power.domain.interactor.PowerInteractor
 import com.android.systemui.qs.ui.adapter.FakeQSSceneAdapter
 import com.android.systemui.res.R
+import com.android.systemui.shade.data.repository.shadeRepository
 import com.android.systemui.shade.domain.interactor.ShadeLockscreenInteractor
-import com.android.systemui.shade.data.repository.FakeShadeRepository
-import com.android.systemui.shade.domain.interactor.ShadeInteractor
+import com.android.systemui.shade.domain.interactor.shadeInteractor
 import com.android.systemui.statusbar.disableflags.data.model.DisableFlagsModel
-import com.android.systemui.statusbar.disableflags.data.repository.FakeDisableFlagsRepository
+import com.android.systemui.statusbar.disableflags.data.repository.disableFlagsRepository
+import com.android.systemui.statusbar.disableflags.data.repository.fakeDisableFlagsRepository
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow
 import com.android.systemui.statusbar.notification.row.NotificationTestHelper
 import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout
@@ -33,17 +30,16 @@
 import com.android.systemui.statusbar.phone.CentralSurfaces
 import com.android.systemui.statusbar.phone.KeyguardBypassController
 import com.android.systemui.statusbar.phone.ScrimController
-import com.android.systemui.statusbar.policy.FakeConfigurationController
 import com.android.systemui.statusbar.policy.ResourcesSplitShadeStateController
-import com.android.systemui.user.domain.UserDomainLayerModule
+import com.android.systemui.statusbar.policy.configurationController
+import com.android.systemui.statusbar.policy.fakeConfigurationController
+import com.android.systemui.testKosmos
 import com.android.systemui.util.mockito.any
 import com.android.systemui.util.mockito.argumentCaptor
 import com.android.systemui.util.mockito.mock
-import dagger.BindsInstance
-import dagger.Component
 import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.test.TestScope
 import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
 import org.junit.After
 import org.junit.Assert.assertFalse
 import org.junit.Assert.assertNotNull
@@ -65,8 +61,8 @@
 import org.mockito.Mockito.never
 import org.mockito.Mockito.verify
 import org.mockito.Mockito.verifyZeroInteractions
-import org.mockito.junit.MockitoJUnit
 import org.mockito.Mockito.`when` as whenever
+import org.mockito.junit.MockitoJUnit
 
 private fun <T> anyObject(): T {
     return Mockito.anyObject<T>()
@@ -77,15 +73,14 @@
 @RunWith(AndroidJUnit4::class)
 @OptIn(ExperimentalCoroutinesApi::class)
 class LockscreenShadeTransitionControllerTest : SysuiTestCase() {
-
+    private val kosmos =
+        testKosmos().apply {
+            fakeFeatureFlagsClassic.apply { set(Flags.FULL_SCREEN_USER_SWITCHER, false) }
+        }
     private lateinit var transitionController: LockscreenShadeTransitionController
-    private lateinit var testComponent: TestComponent
-    private val configurationController
-        get() = testComponent.configurationController
-    private val disableFlagsRepository
-        get() = testComponent.disableFlagsRepository
-    private val testScope
-        get() = testComponent.testScope
+    private val configurationController = kosmos.fakeConfigurationController
+    private val disableFlagsRepository = kosmos.fakeDisableFlagsRepository
+    private val testScope = kosmos.testScope
 
     private val qsSceneAdapter = FakeQSSceneAdapter({ mock() })
 
@@ -134,26 +129,6 @@
         whenever(keyguardBypassController.bypassEnabled).thenReturn(false)
         whenever(naturalScrollingSettingObserver.isNaturalScrollingEnabled).thenReturn(true)
 
-        testComponent =
-            DaggerLockscreenShadeTransitionControllerTest_TestComponent.factory()
-                .create(
-                    test = this,
-                    featureFlags =
-                        FakeFeatureFlagsClassicModule {
-                            set(Flags.FULL_SCREEN_USER_SWITCHER, false)
-                        },
-                    mocks =
-                        TestMocksModule(
-                            notificationShadeDepthController = depthController,
-                            keyguardBypassController = keyguardBypassController,
-                            mediaHierarchyManager = mediaHierarchyManager,
-                            notificationLockscreenUserManager = lockScreenUserManager,
-                            notificationStackScrollLayoutController = nsslController,
-                            scrimController = scrimController,
-                            statusBarStateController = statusbarStateController,
-                        )
-                )
-
         transitionController =
             LockscreenShadeTransitionController(
                 statusBarStateController = statusbarStateController,
@@ -191,10 +166,10 @@
                 falsingManager = FalsingManagerFake(),
                 dumpManager = mock(),
                 qsTransitionControllerFactory = { qsTransitionController },
-                shadeRepository = testComponent.shadeRepository,
-                shadeInteractor = testComponent.shadeInteractor,
+                shadeRepository = kosmos.shadeRepository,
+                shadeInteractor = kosmos.shadeInteractor,
                 splitShadeStateController = ResourcesSplitShadeStateController(),
-                shadeLockscreenInteractorLazy = {shadeLockscreenInteractor},
+                shadeLockscreenInteractorLazy = { shadeLockscreenInteractor },
                 naturalScrollingSettingObserver = naturalScrollingSettingObserver,
                 lazyQSSceneAdapter = { qsSceneAdapter }
             )
@@ -214,387 +189,424 @@
     }
 
     @Test
-    fun testCantDragDownWhenQSExpanded() {
-        assertTrue("Can't drag down on keyguard", transitionController.canDragDown())
-        whenever(qS.isFullyCollapsed).thenReturn(false)
-        assertFalse("Can drag down when QS is expanded", transitionController.canDragDown())
-    }
+    fun testCantDragDownWhenQSExpanded() =
+        testScope.runTest {
+            assertTrue("Can't drag down on keyguard", transitionController.canDragDown())
+            whenever(qS.isFullyCollapsed).thenReturn(false)
+            assertFalse("Can drag down when QS is expanded", transitionController.canDragDown())
+        }
 
     @Test
-    fun testCanDragDownInLockedDownShade() {
-        whenever(statusbarStateController.state).thenReturn(StatusBarState.SHADE_LOCKED)
-        assertFalse("Can drag down in shade locked", transitionController.canDragDown())
-        whenever(nsslController.isInLockedDownShade).thenReturn(true)
-        assertTrue("Can't drag down in locked down shade", transitionController.canDragDown())
-    }
+    fun testCanDragDownInLockedDownShade() =
+        testScope.runTest {
+            whenever(statusbarStateController.state).thenReturn(StatusBarState.SHADE_LOCKED)
+            assertFalse("Can drag down in shade locked", transitionController.canDragDown())
+            whenever(nsslController.isInLockedDownShade).thenReturn(true)
+            assertTrue("Can't drag down in locked down shade", transitionController.canDragDown())
+        }
 
     @Test
-    fun testGoingToLockedShade() {
-        transitionController.goToLockedShade(null)
-        verify(statusbarStateController).setState(StatusBarState.SHADE_LOCKED)
-    }
+    fun testGoingToLockedShade() =
+        testScope.runTest {
+            transitionController.goToLockedShade(null)
+            verify(statusbarStateController).setState(StatusBarState.SHADE_LOCKED)
+        }
 
     @Test
-    fun testWakingToShadeLockedWhenDozing() {
-        whenever(statusbarStateController.isDozing).thenReturn(true)
-        transitionController.goToLockedShade(null)
-        verify(statusbarStateController).setState(StatusBarState.SHADE_LOCKED)
-        assertTrue("Not waking to shade locked", transitionController.isWakingToShadeLocked)
-    }
+    fun testWakingToShadeLockedWhenDozing() =
+        testScope.runTest {
+            whenever(statusbarStateController.isDozing).thenReturn(true)
+            transitionController.goToLockedShade(null)
+            verify(statusbarStateController).setState(StatusBarState.SHADE_LOCKED)
+            assertTrue("Not waking to shade locked", transitionController.isWakingToShadeLocked)
+        }
 
     @Test
-    fun testNotWakingToShadeLockedWhenNotDozing() {
-        whenever(statusbarStateController.isDozing).thenReturn(false)
-        transitionController.goToLockedShade(null)
-        verify(statusbarStateController).setState(StatusBarState.SHADE_LOCKED)
-        assertFalse(
-            "Waking to shade locked when not dozing",
-            transitionController.isWakingToShadeLocked
-        )
-    }
-
-    @Test
-    fun testGoToLockedShadeOnlyOnKeyguard() {
-        whenever(statusbarStateController.state).thenReturn(StatusBarState.SHADE_LOCKED)
-        transitionController.goToLockedShade(null)
-        whenever(statusbarStateController.state).thenReturn(StatusBarState.SHADE)
-        transitionController.goToLockedShade(null)
-        verify(statusbarStateController, never()).setState(anyInt())
-    }
-
-    @Test
-    fun testDontGoWhenShadeDisabled() {
-        disableFlagsRepository.disableFlags.value =
-            DisableFlagsModel(
-                disable2 = DISABLE2_NOTIFICATION_SHADE,
+    fun testNotWakingToShadeLockedWhenNotDozing() =
+        testScope.runTest {
+            whenever(statusbarStateController.isDozing).thenReturn(false)
+            transitionController.goToLockedShade(null)
+            verify(statusbarStateController).setState(StatusBarState.SHADE_LOCKED)
+            assertFalse(
+                "Waking to shade locked when not dozing",
+                transitionController.isWakingToShadeLocked
             )
-        testScope.runCurrent()
-        transitionController.goToLockedShade(null)
-        verify(statusbarStateController, never()).setState(anyInt())
-    }
+        }
 
     @Test
-    fun testUserExpandsViewOnGoingToFullShade() {
-        assertFalse("Row shouldn't be user expanded yet", row.isUserExpanded)
-        transitionController.goToLockedShade(row)
-        assertTrue("Row wasn't user expanded on drag down", row.isUserExpanded)
-    }
+    fun testGoToLockedShadeOnlyOnKeyguard() =
+        testScope.runTest {
+            whenever(statusbarStateController.state).thenReturn(StatusBarState.SHADE_LOCKED)
+            transitionController.goToLockedShade(null)
+            whenever(statusbarStateController.state).thenReturn(StatusBarState.SHADE)
+            transitionController.goToLockedShade(null)
+            verify(statusbarStateController, never()).setState(anyInt())
+        }
 
     @Test
-    fun testTriggeringBouncerNoNotificationsOnLockscreen() {
-        whenever(lockScreenUserManager.shouldShowLockscreenNotifications()).thenReturn(false)
-        transitionController.goToLockedShade(null)
-        verify(statusbarStateController, never()).setState(anyInt())
-        verify(statusbarStateController).setLeaveOpenOnKeyguardHide(true)
-        verify(centralSurfaces).showBouncerWithDimissAndCancelIfKeyguard(anyObject(), anyObject())
-    }
+    fun testDontGoWhenShadeDisabled() =
+        testScope.runTest {
+            disableFlagsRepository.disableFlags.value =
+                DisableFlagsModel(
+                    disable2 = DISABLE2_NOTIFICATION_SHADE,
+                )
+            testScope.runCurrent()
+            transitionController.goToLockedShade(null)
+            verify(statusbarStateController, never()).setState(anyInt())
+        }
 
     @Test
-    fun testGoToLockedShadeCreatesQSAnimation() {
-        transitionController.goToLockedShade(null)
-        verify(statusbarStateController).setState(StatusBarState.SHADE_LOCKED)
-        verify(shadeLockscreenInteractor).transitionToExpandedShade(anyLong())
-        assertNotNull(transitionController.dragDownAnimator)
-    }
+    fun testUserExpandsViewOnGoingToFullShade() =
+        testScope.runTest {
+            assertFalse("Row shouldn't be user expanded yet", row.isUserExpanded)
+            transitionController.goToLockedShade(row)
+            assertTrue("Row wasn't user expanded on drag down", row.isUserExpanded)
+        }
 
     @Test
-    fun testGoToLockedShadeDoesntCreateQSAnimation() {
-        transitionController.goToLockedShade(null, needsQSAnimation = false)
-        verify(statusbarStateController).setState(StatusBarState.SHADE_LOCKED)
-        verify(shadeLockscreenInteractor).transitionToExpandedShade(anyLong())
-        assertNull(transitionController.dragDownAnimator)
-    }
+    fun testTriggeringBouncerNoNotificationsOnLockscreen() =
+        testScope.runTest {
+            whenever(lockScreenUserManager.shouldShowLockscreenNotifications()).thenReturn(false)
+            transitionController.goToLockedShade(null)
+            verify(statusbarStateController, never()).setState(anyInt())
+            verify(statusbarStateController).setLeaveOpenOnKeyguardHide(true)
+            verify(centralSurfaces)
+                .showBouncerWithDimissAndCancelIfKeyguard(anyObject(), anyObject())
+        }
 
     @Test
-    fun testGoToLockedShadeAlwaysCreatesQSAnimationInSplitShade() {
-        enableSplitShade()
-        transitionController.goToLockedShade(null, needsQSAnimation = true)
-        verify(shadeLockscreenInteractor).transitionToExpandedShade(anyLong())
-        assertNotNull(transitionController.dragDownAnimator)
-    }
+    fun testGoToLockedShadeCreatesQSAnimation() =
+        testScope.runTest {
+            transitionController.goToLockedShade(null)
+            verify(statusbarStateController).setState(StatusBarState.SHADE_LOCKED)
+            verify(shadeLockscreenInteractor).transitionToExpandedShade(anyLong())
+            assertNotNull(transitionController.dragDownAnimator)
+        }
 
     @Test
-    fun testGoToLockedShadeCancelDoesntLeaveShadeOpenOnKeyguardHide() {
-        whenever(lockScreenUserManager.shouldShowLockscreenNotifications()).thenReturn(false)
-        whenever(lockScreenUserManager.isLockscreenPublicMode(any())).thenReturn(true)
-        transitionController.goToLockedShade(null)
-        val captor = argumentCaptor<Runnable>()
-        verify(centralSurfaces).showBouncerWithDimissAndCancelIfKeyguard(isNull(), captor.capture())
-        captor.value.run()
-        verify(statusbarStateController).setLeaveOpenOnKeyguardHide(false)
-    }
+    fun testGoToLockedShadeDoesntCreateQSAnimation() =
+        testScope.runTest {
+            transitionController.goToLockedShade(null, needsQSAnimation = false)
+            verify(statusbarStateController).setState(StatusBarState.SHADE_LOCKED)
+            verify(shadeLockscreenInteractor).transitionToExpandedShade(anyLong())
+            assertNull(transitionController.dragDownAnimator)
+        }
 
     @Test
-    fun testDragDownAmountDoesntCallOutInLockedDownShade() {
-        whenever(nsslController.isInLockedDownShade).thenReturn(true)
-        transitionController.dragDownAmount = 10f
-        verify(nsslController, never()).setTransitionToFullShadeAmount(anyFloat())
-        verify(mediaHierarchyManager, never()).setTransitionToFullShadeAmount(anyFloat())
-        verify(scrimController, never()).setTransitionToFullShadeProgress(anyFloat(), anyFloat())
-        verify(transitionControllerCallback, never())
-            .setTransitionToFullShadeAmount(anyFloat(), anyBoolean(), anyLong())
-        verify(qsTransitionController, never()).dragDownAmount = anyFloat()
-    }
+    fun testGoToLockedShadeAlwaysCreatesQSAnimationInSplitShade() =
+        testScope.runTest {
+            enableSplitShade()
+            transitionController.goToLockedShade(null, needsQSAnimation = true)
+            verify(shadeLockscreenInteractor).transitionToExpandedShade(anyLong())
+            assertNotNull(transitionController.dragDownAnimator)
+        }
 
     @Test
-    fun testDragDownAmountCallsOut() {
-        transitionController.dragDownAmount = 10f
-        verify(nsslController).setTransitionToFullShadeAmount(anyFloat())
-        verify(mediaHierarchyManager).setTransitionToFullShadeAmount(anyFloat())
-        verify(scrimController).setTransitionToFullShadeProgress(anyFloat(), anyFloat())
-        verify(transitionControllerCallback)
-            .setTransitionToFullShadeAmount(anyFloat(), anyBoolean(), anyLong())
-        verify(qsTransitionController).dragDownAmount = 10f
-        verify(depthController).transitionToFullShadeProgress = anyFloat()
-    }
+    fun testGoToLockedShadeCancelDoesntLeaveShadeOpenOnKeyguardHide() =
+        testScope.runTest {
+            whenever(lockScreenUserManager.shouldShowLockscreenNotifications()).thenReturn(false)
+            whenever(lockScreenUserManager.isLockscreenPublicMode(any())).thenReturn(true)
+            transitionController.goToLockedShade(null)
+            val captor = argumentCaptor<Runnable>()
+            verify(centralSurfaces)
+                .showBouncerWithDimissAndCancelIfKeyguard(isNull(), captor.capture())
+            captor.value.run()
+            verify(statusbarStateController).setLeaveOpenOnKeyguardHide(false)
+        }
 
     @Test
-    fun testDragDownAmount_depthDistanceIsZero_setsProgressToZero() {
-        context
-            .getOrCreateTestableResources()
-            .addOverride(R.dimen.lockscreen_shade_depth_controller_transition_distance, 0)
-        configurationController.notifyConfigurationChanged()
-
-        transitionController.dragDownAmount = 10f
-
-        verify(depthController).transitionToFullShadeProgress = 0f
-    }
+    fun testDragDownAmountDoesntCallOutInLockedDownShade() =
+        testScope.runTest {
+            whenever(nsslController.isInLockedDownShade).thenReturn(true)
+            transitionController.dragDownAmount = 10f
+            verify(nsslController, never()).setTransitionToFullShadeAmount(anyFloat())
+            verify(mediaHierarchyManager, never()).setTransitionToFullShadeAmount(anyFloat())
+            verify(scrimController, never())
+                .setTransitionToFullShadeProgress(anyFloat(), anyFloat())
+            verify(transitionControllerCallback, never())
+                .setTransitionToFullShadeAmount(anyFloat(), anyBoolean(), anyLong())
+            verify(qsTransitionController, never()).dragDownAmount = anyFloat()
+        }
 
     @Test
-    fun testDragDownAmount_depthDistanceNonZero_setsProgressBasedOnDistance() {
-        context
-            .getOrCreateTestableResources()
-            .addOverride(R.dimen.lockscreen_shade_depth_controller_transition_distance, 100)
-        configurationController.notifyConfigurationChanged()
-
-        transitionController.dragDownAmount = 10f
-
-        verify(depthController).transitionToFullShadeProgress = 0.1f
-    }
+    fun testDragDownAmountCallsOut() =
+        testScope.runTest {
+            transitionController.dragDownAmount = 10f
+            verify(nsslController).setTransitionToFullShadeAmount(anyFloat())
+            verify(mediaHierarchyManager).setTransitionToFullShadeAmount(anyFloat())
+            verify(scrimController).setTransitionToFullShadeProgress(anyFloat(), anyFloat())
+            verify(transitionControllerCallback)
+                .setTransitionToFullShadeAmount(anyFloat(), anyBoolean(), anyLong())
+            verify(qsTransitionController).dragDownAmount = 10f
+            verify(depthController).transitionToFullShadeProgress = anyFloat()
+        }
 
     @Test
-    fun setDragAmount_setsKeyguardTransitionProgress() {
-        transitionController.dragDownAmount = 10f
+    fun testDragDownAmount_depthDistanceIsZero_setsProgressToZero() =
+        testScope.runTest {
+            context
+                .getOrCreateTestableResources()
+                .addOverride(R.dimen.lockscreen_shade_depth_controller_transition_distance, 0)
+            configurationController.notifyConfigurationChanged()
 
-        verify(shadeLockscreenInteractor).setKeyguardTransitionProgress(anyFloat(), anyInt())
-    }
+            transitionController.dragDownAmount = 10f
+
+            verify(depthController).transitionToFullShadeProgress = 0f
+        }
 
     @Test
-    fun setDragAmount_setsKeyguardAlphaBasedOnDistance() {
-        val alphaDistance =
-            context.resources.getDimensionPixelSize(
-                R.dimen.lockscreen_shade_npvc_keyguard_content_alpha_transition_distance
-            )
-        transitionController.dragDownAmount = 10f
+    fun testDragDownAmount_depthDistanceNonZero_setsProgressBasedOnDistance() =
+        testScope.runTest {
+            context
+                .getOrCreateTestableResources()
+                .addOverride(R.dimen.lockscreen_shade_depth_controller_transition_distance, 100)
+            configurationController.notifyConfigurationChanged()
 
-        val expectedAlpha = 1 - 10f / alphaDistance
-        verify(shadeLockscreenInteractor).setKeyguardTransitionProgress(eq(expectedAlpha), anyInt())
-    }
+            transitionController.dragDownAmount = 10f
+
+            verify(depthController).transitionToFullShadeProgress = 0.1f
+        }
 
     @Test
-    fun setDragAmount_notInSplitShade_setsKeyguardTranslationToZero() {
-        val mediaTranslationY = 123
-        disableSplitShade()
-        whenever(mediaHierarchyManager.isCurrentlyInGuidedTransformation()).thenReturn(true)
-        whenever(mediaHierarchyManager.getGuidedTransformationTranslationY())
-            .thenReturn(mediaTranslationY)
+    fun setDragAmount_setsKeyguardTransitionProgress() =
+        testScope.runTest {
+            transitionController.dragDownAmount = 10f
 
-        transitionController.dragDownAmount = 10f
-
-        verify(shadeLockscreenInteractor).setKeyguardTransitionProgress(anyFloat(), eq(0))
-    }
+            verify(shadeLockscreenInteractor).setKeyguardTransitionProgress(anyFloat(), anyInt())
+        }
 
     @Test
-    fun setDragAmount_inSplitShade_setsKeyguardTranslationBasedOnMediaTranslation() {
-        val mediaTranslationY = 123
-        enableSplitShade()
-        whenever(mediaHierarchyManager.isCurrentlyInGuidedTransformation()).thenReturn(true)
-        whenever(mediaHierarchyManager.getGuidedTransformationTranslationY())
-            .thenReturn(mediaTranslationY)
+    fun setDragAmount_setsKeyguardAlphaBasedOnDistance() =
+        testScope.runTest {
+            val alphaDistance =
+                context.resources.getDimensionPixelSize(
+                    R.dimen.lockscreen_shade_npvc_keyguard_content_alpha_transition_distance
+                )
+            transitionController.dragDownAmount = 10f
 
-        transitionController.dragDownAmount = 10f
+            val expectedAlpha = 1 - 10f / alphaDistance
+            verify(shadeLockscreenInteractor)
+                .setKeyguardTransitionProgress(eq(expectedAlpha), anyInt())
+        }
 
-        verify(shadeLockscreenInteractor)
+    @Test
+    fun setDragAmount_notInSplitShade_setsKeyguardTranslationToZero() =
+        testScope.runTest {
+            val mediaTranslationY = 123
+            disableSplitShade()
+            whenever(mediaHierarchyManager.isCurrentlyInGuidedTransformation()).thenReturn(true)
+            whenever(mediaHierarchyManager.getGuidedTransformationTranslationY())
+                .thenReturn(mediaTranslationY)
+
+            transitionController.dragDownAmount = 10f
+
+            verify(shadeLockscreenInteractor).setKeyguardTransitionProgress(anyFloat(), eq(0))
+        }
+
+    @Test
+    fun setDragAmount_inSplitShade_setsKeyguardTranslationBasedOnMediaTranslation() =
+        testScope.runTest {
+            val mediaTranslationY = 123
+            enableSplitShade()
+            whenever(mediaHierarchyManager.isCurrentlyInGuidedTransformation()).thenReturn(true)
+            whenever(mediaHierarchyManager.getGuidedTransformationTranslationY())
+                .thenReturn(mediaTranslationY)
+
+            transitionController.dragDownAmount = 10f
+
+            verify(shadeLockscreenInteractor)
                 .setKeyguardTransitionProgress(anyFloat(), eq(mediaTranslationY))
-    }
+        }
 
     @Test
-    fun setDragAmount_inSplitShade_mediaNotShowing_setsKeyguardTranslationBasedOnDistance() {
-        enableSplitShade()
-        whenever(mediaHierarchyManager.isCurrentlyInGuidedTransformation()).thenReturn(false)
-        whenever(mediaHierarchyManager.getGuidedTransformationTranslationY()).thenReturn(123)
+    fun setDragAmount_inSplitShade_mediaNotShowing_setsKeyguardTranslationBasedOnDistance() =
+        testScope.runTest {
+            enableSplitShade()
+            whenever(mediaHierarchyManager.isCurrentlyInGuidedTransformation()).thenReturn(false)
+            whenever(mediaHierarchyManager.getGuidedTransformationTranslationY()).thenReturn(123)
 
-        transitionController.dragDownAmount = 10f
+            transitionController.dragDownAmount = 10f
 
-        val distance =
-            context.resources.getDimensionPixelSize(
-                R.dimen.lockscreen_shade_keyguard_transition_distance
+            val distance =
+                context.resources.getDimensionPixelSize(
+                    R.dimen.lockscreen_shade_keyguard_transition_distance
+                )
+            val offset =
+                context.resources.getDimensionPixelSize(
+                    R.dimen.lockscreen_shade_keyguard_transition_vertical_offset
+                )
+            val expectedTranslation = 10f / distance * offset
+            verify(shadeLockscreenInteractor)
+                .setKeyguardTransitionProgress(anyFloat(), eq(expectedTranslation.toInt()))
+        }
+
+    @Test
+    fun setDragDownAmount_setsValueOnMediaHierarchyManager() =
+        testScope.runTest {
+            transitionController.dragDownAmount = 10f
+
+            verify(mediaHierarchyManager).setTransitionToFullShadeAmount(10f)
+        }
+
+    @Test
+    fun setDragAmount_setsScrimProgressBasedOnScrimDistance() =
+        testScope.runTest {
+            val distance = 10
+            context.orCreateTestableResources.addOverride(
+                R.dimen.lockscreen_shade_scrim_transition_distance,
+                distance
             )
-        val offset =
-            context.resources.getDimensionPixelSize(
-                R.dimen.lockscreen_shade_keyguard_transition_vertical_offset
+            configurationController.notifyConfigurationChanged()
+
+            transitionController.dragDownAmount = 5f
+
+            verify(scrimController)
+                .transitionToFullShadeProgress(
+                    progress = eq(0.5f),
+                    lockScreenNotificationsProgress = anyFloat()
+                )
+        }
+
+    @Test
+    fun setDragAmount_setsNotificationsScrimProgressBasedOnNotificationsScrimDistanceAndDelay() =
+        testScope.runTest {
+            val distance = 100
+            val delay = 10
+            context.orCreateTestableResources.addOverride(
+                R.dimen.lockscreen_shade_notifications_scrim_transition_distance,
+                distance
             )
-        val expectedTranslation = 10f / distance * offset
-        verify(shadeLockscreenInteractor)
-            .setKeyguardTransitionProgress(anyFloat(), eq(expectedTranslation.toInt()))
-    }
-
-    @Test
-    fun setDragDownAmount_setsValueOnMediaHierarchyManager() {
-        transitionController.dragDownAmount = 10f
-
-        verify(mediaHierarchyManager).setTransitionToFullShadeAmount(10f)
-    }
-
-    @Test
-    fun setDragAmount_setsScrimProgressBasedOnScrimDistance() {
-        val distance = 10
-        context.orCreateTestableResources.addOverride(
-            R.dimen.lockscreen_shade_scrim_transition_distance,
-            distance
-        )
-        configurationController.notifyConfigurationChanged()
-
-        transitionController.dragDownAmount = 5f
-
-        verify(scrimController)
-            .transitionToFullShadeProgress(
-                progress = eq(0.5f),
-                lockScreenNotificationsProgress = anyFloat()
+            context.orCreateTestableResources.addOverride(
+                R.dimen.lockscreen_shade_notifications_scrim_transition_delay,
+                delay
             )
-    }
+            configurationController.notifyConfigurationChanged()
+
+            transitionController.dragDownAmount = 20f
+
+            verify(scrimController)
+                .transitionToFullShadeProgress(
+                    progress = anyFloat(),
+                    lockScreenNotificationsProgress = eq(0.1f)
+                )
+        }
 
     @Test
-    fun setDragAmount_setsNotificationsScrimProgressBasedOnNotificationsScrimDistanceAndDelay() {
-        val distance = 100
-        val delay = 10
-        context.orCreateTestableResources.addOverride(
-            R.dimen.lockscreen_shade_notifications_scrim_transition_distance,
-            distance
-        )
-        context.orCreateTestableResources.addOverride(
-            R.dimen.lockscreen_shade_notifications_scrim_transition_delay,
-            delay
-        )
-        configurationController.notifyConfigurationChanged()
-
-        transitionController.dragDownAmount = 20f
-
-        verify(scrimController)
-            .transitionToFullShadeProgress(
-                progress = anyFloat(),
-                lockScreenNotificationsProgress = eq(0.1f)
+    fun setDragAmount_dragAmountLessThanNotifDelayDistance_setsNotificationsScrimProgressToZero() =
+        testScope.runTest {
+            val distance = 100
+            val delay = 50
+            context.orCreateTestableResources.addOverride(
+                R.dimen.lockscreen_shade_notifications_scrim_transition_distance,
+                distance
             )
-    }
-
-    @Test
-    fun setDragAmount_dragAmountLessThanNotifDelayDistance_setsNotificationsScrimProgressToZero() {
-        val distance = 100
-        val delay = 50
-        context.orCreateTestableResources.addOverride(
-            R.dimen.lockscreen_shade_notifications_scrim_transition_distance,
-            distance
-        )
-        context.orCreateTestableResources.addOverride(
-            R.dimen.lockscreen_shade_notifications_scrim_transition_delay,
-            delay
-        )
-        configurationController.notifyConfigurationChanged()
-
-        transitionController.dragDownAmount = 20f
-
-        verify(scrimController)
-            .transitionToFullShadeProgress(
-                progress = anyFloat(),
-                lockScreenNotificationsProgress = eq(0f)
+            context.orCreateTestableResources.addOverride(
+                R.dimen.lockscreen_shade_notifications_scrim_transition_delay,
+                delay
             )
-    }
+            configurationController.notifyConfigurationChanged()
+
+            transitionController.dragDownAmount = 20f
+
+            verify(scrimController)
+                .transitionToFullShadeProgress(
+                    progress = anyFloat(),
+                    lockScreenNotificationsProgress = eq(0f)
+                )
+        }
 
     @Test
-    fun setDragAmount_dragAmountMoreThanTotalDistance_setsNotificationsScrimProgressToOne() {
-        val distance = 100
-        val delay = 50
-        context.orCreateTestableResources.addOverride(
-            R.dimen.lockscreen_shade_notifications_scrim_transition_distance,
-            distance
-        )
-        context.orCreateTestableResources.addOverride(
-            R.dimen.lockscreen_shade_notifications_scrim_transition_delay,
-            delay
-        )
-        configurationController.notifyConfigurationChanged()
-
-        transitionController.dragDownAmount = 999999f
-
-        verify(scrimController)
-            .transitionToFullShadeProgress(
-                progress = anyFloat(),
-                lockScreenNotificationsProgress = eq(1f)
+    fun setDragAmount_dragAmountMoreThanTotalDistance_setsNotificationsScrimProgressToOne() =
+        testScope.runTest {
+            val distance = 100
+            val delay = 50
+            context.orCreateTestableResources.addOverride(
+                R.dimen.lockscreen_shade_notifications_scrim_transition_distance,
+                distance
             )
-    }
-
-    @Test
-    fun setDragDownAmount_inSplitShade_setsValueOnMediaHierarchyManager() {
-        enableSplitShade()
-
-        transitionController.dragDownAmount = 10f
-
-        verify(mediaHierarchyManager).setTransitionToFullShadeAmount(10f)
-    }
-
-    @Test
-    fun setDragAmount_notInSplitShade_forwardsToSingleShadeOverScroller() {
-        disableSplitShade()
-
-        transitionController.dragDownAmount = 10f
-
-        verify(singleShadeOverScroller).expansionDragDownAmount = 10f
-        verifyZeroInteractions(splitShadeOverScroller)
-    }
-
-    @Test
-    fun setDragAmount_inSplitShade_forwardsToSplitShadeOverScroller() {
-        enableSplitShade()
-
-        transitionController.dragDownAmount = 10f
-
-        verify(splitShadeOverScroller).expansionDragDownAmount = 10f
-        verifyZeroInteractions(singleShadeOverScroller)
-    }
-
-    @Test
-    fun setDragDownAmount_inSplitShade_setsKeyguardStatusBarAlphaBasedOnDistance() {
-        val alphaDistance =
-            context.resources.getDimensionPixelSize(
-                R.dimen.lockscreen_shade_npvc_keyguard_content_alpha_transition_distance
+            context.orCreateTestableResources.addOverride(
+                R.dimen.lockscreen_shade_notifications_scrim_transition_delay,
+                delay
             )
-        val dragDownAmount = 10f
-        enableSplitShade()
+            configurationController.notifyConfigurationChanged()
 
-        transitionController.dragDownAmount = dragDownAmount
+            transitionController.dragDownAmount = 999999f
 
-        val expectedAlpha = 1 - dragDownAmount / alphaDistance
-        verify(shadeLockscreenInteractor).setKeyguardStatusBarAlpha(expectedAlpha)
-    }
+            verify(scrimController)
+                .transitionToFullShadeProgress(
+                    progress = anyFloat(),
+                    lockScreenNotificationsProgress = eq(1f)
+                )
+        }
 
     @Test
-    fun setDragDownAmount_notInSplitShade_setsKeyguardStatusBarAlphaToMinusOne() {
-        disableSplitShade()
+    fun setDragDownAmount_inSplitShade_setsValueOnMediaHierarchyManager() =
+        testScope.runTest {
+            enableSplitShade()
 
-        transitionController.dragDownAmount = 10f
+            transitionController.dragDownAmount = 10f
 
-        verify(shadeLockscreenInteractor).setKeyguardStatusBarAlpha(-1f)
-    }
+            verify(mediaHierarchyManager).setTransitionToFullShadeAmount(10f)
+        }
 
     @Test
-    fun nullQs_canDragDownFromAdapter() {
-        transitionController.qS = null
+    fun setDragAmount_notInSplitShade_forwardsToSingleShadeOverScroller() =
+        testScope.runTest {
+            disableSplitShade()
 
-        qsSceneAdapter.isQsFullyCollapsed = true
-        assertTrue("Can't drag down on keyguard", transitionController.canDragDown())
-        qsSceneAdapter.isQsFullyCollapsed = false
-        assertFalse("Can drag down when QS is expanded", transitionController.canDragDown())
-    }
+            transitionController.dragDownAmount = 10f
+
+            verify(singleShadeOverScroller).expansionDragDownAmount = 10f
+            verifyZeroInteractions(splitShadeOverScroller)
+        }
+
+    @Test
+    fun setDragAmount_inSplitShade_forwardsToSplitShadeOverScroller() =
+        testScope.runTest {
+            enableSplitShade()
+
+            transitionController.dragDownAmount = 10f
+
+            verify(splitShadeOverScroller).expansionDragDownAmount = 10f
+            verifyZeroInteractions(singleShadeOverScroller)
+        }
+
+    @Test
+    fun setDragDownAmount_inSplitShade_setsKeyguardStatusBarAlphaBasedOnDistance() =
+        testScope.runTest {
+            val alphaDistance =
+                context.resources.getDimensionPixelSize(
+                    R.dimen.lockscreen_shade_npvc_keyguard_content_alpha_transition_distance
+                )
+            val dragDownAmount = 10f
+            enableSplitShade()
+
+            transitionController.dragDownAmount = dragDownAmount
+
+            val expectedAlpha = 1 - dragDownAmount / alphaDistance
+            verify(shadeLockscreenInteractor).setKeyguardStatusBarAlpha(expectedAlpha)
+        }
+
+    @Test
+    fun setDragDownAmount_notInSplitShade_setsKeyguardStatusBarAlphaToMinusOne() =
+        testScope.runTest {
+            disableSplitShade()
+
+            transitionController.dragDownAmount = 10f
+
+            verify(shadeLockscreenInteractor).setKeyguardStatusBarAlpha(-1f)
+        }
+
+    @Test
+    fun nullQs_canDragDownFromAdapter() =
+        testScope.runTest {
+            transitionController.qS = null
+
+            qsSceneAdapter.isQsFullyCollapsed = true
+            assertTrue("Can't drag down on keyguard", transitionController.canDragDown())
+            qsSceneAdapter.isQsFullyCollapsed = false
+            assertFalse("Can drag down when QS is expanded", transitionController.canDragDown())
+        }
 
     private fun enableSplitShade() {
         setSplitShadeEnabled(true)
@@ -619,32 +631,4 @@
     ) {
         setTransitionToFullShadeProgress(progress, lockScreenNotificationsProgress)
     }
-
-    @SysUISingleton
-    @Component(
-        modules =
-            [
-                SysUITestModule::class,
-                UserDomainLayerModule::class,
-                BiometricsDomainLayerModule::class,
-            ]
-    )
-    interface TestComponent {
-
-        val configurationController: FakeConfigurationController
-        val disableFlagsRepository: FakeDisableFlagsRepository
-        val powerInteractor: PowerInteractor
-        val shadeInteractor: ShadeInteractor
-        val shadeRepository: FakeShadeRepository
-        val testScope: TestScope
-
-        @Component.Factory
-        interface Factory {
-            fun create(
-                    @BindsInstance test: SysuiTestCase,
-                    featureFlags: FakeFeatureFlagsClassicModule,
-                    mocks: TestMocksModule,
-            ): TestComponent
-        }
-    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/casttootherdevice/ui/viewmodel/CastToOtherDeviceChipViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/casttootherdevice/ui/viewmodel/CastToOtherDeviceChipViewModelTest.kt
index fe29140..18a62cf 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/casttootherdevice/ui/viewmodel/CastToOtherDeviceChipViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/casttootherdevice/ui/viewmodel/CastToOtherDeviceChipViewModelTest.kt
@@ -19,7 +19,6 @@
 import android.view.View
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
-import com.android.systemui.animation.mockDialogTransitionAnimator
 import com.android.systemui.common.shared.model.ContentDescription
 import com.android.systemui.common.shared.model.Icon
 import com.android.systemui.coroutines.collectLastValue
@@ -38,7 +37,6 @@
 import com.android.systemui.statusbar.chips.mediaprojection.domain.interactor.MediaProjectionChipInteractorTest.Companion.setUpPackageManagerForMediaProjection
 import com.android.systemui.statusbar.chips.ui.model.ColorsModel
 import com.android.systemui.statusbar.chips.ui.model.OngoingActivityChipModel
-import com.android.systemui.statusbar.chips.ui.view.ChipBackgroundContainer
 import com.android.systemui.statusbar.phone.SystemUIDialog
 import com.android.systemui.statusbar.phone.mockSystemUIDialogFactory
 import com.android.systemui.statusbar.policy.CastDevice
@@ -47,9 +45,7 @@
 import kotlin.test.Test
 import kotlinx.coroutines.test.runTest
 import org.junit.Before
-import org.mockito.ArgumentMatchers
 import org.mockito.kotlin.any
-import org.mockito.kotlin.eq
 import org.mockito.kotlin.mock
 import org.mockito.kotlin.verify
 import org.mockito.kotlin.whenever
@@ -65,17 +61,6 @@
     private val mockScreenCastDialog = mock<SystemUIDialog>()
     private val mockGenericCastDialog = mock<SystemUIDialog>()
 
-    private val chipBackgroundView = mock<ChipBackgroundContainer>()
-    private val chipView =
-        mock<View>().apply {
-            whenever(
-                    this.requireViewById<ChipBackgroundContainer>(
-                        R.id.ongoing_activity_chip_background
-                    )
-                )
-                .thenReturn(chipBackgroundView)
-        }
-
     private val underTest = kosmos.castToOtherDeviceChipViewModel
 
     @Before
@@ -306,14 +291,8 @@
             val clickListener = ((latest as OngoingActivityChipModel.Shown).onClickListener)
             assertThat(clickListener).isNotNull()
 
-            clickListener!!.onClick(chipView)
-            verify(kosmos.mockDialogTransitionAnimator)
-                .showFromView(
-                    eq(mockScreenCastDialog),
-                    eq(chipBackgroundView),
-                    eq(null),
-                    ArgumentMatchers.anyBoolean(),
-                )
+            clickListener!!.onClick(mock<View>())
+            verify(mockScreenCastDialog).show()
         }
 
     @Test
@@ -330,14 +309,8 @@
             val clickListener = ((latest as OngoingActivityChipModel.Shown).onClickListener)
             assertThat(clickListener).isNotNull()
 
-            clickListener!!.onClick(chipView)
-            verify(kosmos.mockDialogTransitionAnimator)
-                .showFromView(
-                    eq(mockScreenCastDialog),
-                    eq(chipBackgroundView),
-                    eq(null),
-                    ArgumentMatchers.anyBoolean(),
-                )
+            clickListener!!.onClick(mock<View>())
+            verify(mockScreenCastDialog).show()
         }
 
     @Test
@@ -359,13 +332,7 @@
             val clickListener = ((latest as OngoingActivityChipModel.Shown).onClickListener)
             assertThat(clickListener).isNotNull()
 
-            clickListener!!.onClick(chipView)
-            verify(kosmos.mockDialogTransitionAnimator)
-                .showFromView(
-                    eq(mockGenericCastDialog),
-                    eq(chipBackgroundView),
-                    eq(null),
-                    ArgumentMatchers.anyBoolean(),
-                )
+            clickListener!!.onClick(mock<View>())
+            verify(mockGenericCastDialog).show()
         }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/screenrecord/ui/viewmodel/ScreenRecordChipViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/screenrecord/ui/viewmodel/ScreenRecordChipViewModelTest.kt
index 0a06cc7..90f94c8 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/screenrecord/ui/viewmodel/ScreenRecordChipViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/screenrecord/ui/viewmodel/ScreenRecordChipViewModelTest.kt
@@ -19,7 +19,6 @@
 import android.view.View
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
-import com.android.systemui.animation.mockDialogTransitionAnimator
 import com.android.systemui.common.shared.model.Icon
 import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.kosmos.Kosmos
@@ -34,7 +33,6 @@
 import com.android.systemui.statusbar.chips.screenrecord.ui.view.EndScreenRecordingDialogDelegate
 import com.android.systemui.statusbar.chips.ui.model.ColorsModel
 import com.android.systemui.statusbar.chips.ui.model.OngoingActivityChipModel
-import com.android.systemui.statusbar.chips.ui.view.ChipBackgroundContainer
 import com.android.systemui.statusbar.phone.SystemUIDialog
 import com.android.systemui.statusbar.phone.mockSystemUIDialogFactory
 import com.android.systemui.util.time.fakeSystemClock
@@ -42,9 +40,7 @@
 import kotlin.test.Test
 import kotlinx.coroutines.test.runTest
 import org.junit.Before
-import org.mockito.ArgumentMatchers
 import org.mockito.kotlin.any
-import org.mockito.kotlin.eq
 import org.mockito.kotlin.mock
 import org.mockito.kotlin.verify
 import org.mockito.kotlin.whenever
@@ -58,17 +54,6 @@
     private val systemClock = kosmos.fakeSystemClock
     private val mockSystemUIDialog = mock<SystemUIDialog>()
 
-    private val chipBackgroundView = mock<ChipBackgroundContainer>()
-    private val chipView =
-        mock<View>().apply {
-            whenever(
-                    this.requireViewById<ChipBackgroundContainer>(
-                        R.id.ongoing_activity_chip_background
-                    )
-                )
-                .thenReturn(chipBackgroundView)
-        }
-
     private val underTest = kosmos.screenRecordChipViewModel
 
     @Before
@@ -197,15 +182,9 @@
             val clickListener = ((latest as OngoingActivityChipModel.Shown).onClickListener)
             assertThat(clickListener).isNotNull()
 
-            clickListener!!.onClick(chipView)
+            clickListener!!.onClick(mock<View>())
             // EndScreenRecordingDialogDelegate will test that the dialog has the right message
-            verify(kosmos.mockDialogTransitionAnimator)
-                .showFromView(
-                    eq(mockSystemUIDialog),
-                    eq(chipBackgroundView),
-                    eq(null),
-                    ArgumentMatchers.anyBoolean(),
-                )
+            verify(mockSystemUIDialog).show()
         }
 
     @Test
@@ -219,15 +198,9 @@
             val clickListener = ((latest as OngoingActivityChipModel.Shown).onClickListener)
             assertThat(clickListener).isNotNull()
 
-            clickListener!!.onClick(chipView)
+            clickListener!!.onClick(mock<View>())
             // EndScreenRecordingDialogDelegate will test that the dialog has the right message
-            verify(kosmos.mockDialogTransitionAnimator)
-                .showFromView(
-                    eq(mockSystemUIDialog),
-                    eq(chipBackgroundView),
-                    eq(null),
-                    ArgumentMatchers.anyBoolean(),
-                )
+            verify(mockSystemUIDialog).show()
         }
 
     @Test
@@ -244,14 +217,8 @@
             val clickListener = ((latest as OngoingActivityChipModel.Shown).onClickListener)
             assertThat(clickListener).isNotNull()
 
-            clickListener!!.onClick(chipView)
+            clickListener!!.onClick(mock<View>())
             // EndScreenRecordingDialogDelegate will test that the dialog has the right message
-            verify(kosmos.mockDialogTransitionAnimator)
-                .showFromView(
-                    eq(mockSystemUIDialog),
-                    eq(chipBackgroundView),
-                    eq(null),
-                    ArgumentMatchers.anyBoolean(),
-                )
+            verify(mockSystemUIDialog).show()
         }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/sharetoapp/ui/viewmodel/ShareToAppChipViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/sharetoapp/ui/viewmodel/ShareToAppChipViewModelTest.kt
index 3028d00..29fd792 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/sharetoapp/ui/viewmodel/ShareToAppChipViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/sharetoapp/ui/viewmodel/ShareToAppChipViewModelTest.kt
@@ -19,7 +19,6 @@
 import android.view.View
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
-import com.android.systemui.animation.mockDialogTransitionAnimator
 import com.android.systemui.common.shared.model.Icon
 import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.kosmos.Kosmos
@@ -35,7 +34,6 @@
 import com.android.systemui.statusbar.chips.sharetoapp.ui.view.EndShareToAppDialogDelegate
 import com.android.systemui.statusbar.chips.ui.model.ColorsModel
 import com.android.systemui.statusbar.chips.ui.model.OngoingActivityChipModel
-import com.android.systemui.statusbar.chips.ui.view.ChipBackgroundContainer
 import com.android.systemui.statusbar.phone.SystemUIDialog
 import com.android.systemui.statusbar.phone.mockSystemUIDialogFactory
 import com.android.systemui.util.time.fakeSystemClock
@@ -43,9 +41,7 @@
 import kotlin.test.Test
 import kotlinx.coroutines.test.runTest
 import org.junit.Before
-import org.mockito.ArgumentMatchers
 import org.mockito.kotlin.any
-import org.mockito.kotlin.eq
 import org.mockito.kotlin.mock
 import org.mockito.kotlin.verify
 import org.mockito.kotlin.whenever
@@ -59,17 +55,6 @@
 
     private val mockShareDialog = mock<SystemUIDialog>()
 
-    private val chipBackgroundView = mock<ChipBackgroundContainer>()
-    private val chipView =
-        mock<View>().apply {
-            whenever(
-                    this.requireViewById<ChipBackgroundContainer>(
-                        R.id.ongoing_activity_chip_background
-                    )
-                )
-                .thenReturn(chipBackgroundView)
-        }
-
     private val underTest = kosmos.shareToAppChipViewModel
 
     @Before
@@ -193,14 +178,8 @@
             val clickListener = ((latest as OngoingActivityChipModel.Shown).onClickListener)
             assertThat(clickListener).isNotNull()
 
-            clickListener!!.onClick(chipView)
-            verify(kosmos.mockDialogTransitionAnimator)
-                .showFromView(
-                    eq(mockShareDialog),
-                    eq(chipBackgroundView),
-                    eq(null),
-                    ArgumentMatchers.anyBoolean(),
-                )
+            clickListener!!.onClick(mock<View>())
+            verify(mockShareDialog).show()
         }
 
     @Test
@@ -216,13 +195,7 @@
             val clickListener = ((latest as OngoingActivityChipModel.Shown).onClickListener)
             assertThat(clickListener).isNotNull()
 
-            clickListener!!.onClick(chipView)
-            verify(kosmos.mockDialogTransitionAnimator)
-                .showFromView(
-                    eq(mockShareDialog),
-                    eq(chipBackgroundView),
-                    eq(null),
-                    ArgumentMatchers.anyBoolean(),
-                )
+            clickListener!!.onClick(mock<View>())
+            verify(mockShareDialog).show()
         }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipViewModelTest.kt
index c9c7359..2e0c773 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipViewModelTest.kt
@@ -19,50 +19,25 @@
 import android.view.View
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
-import com.android.systemui.animation.DialogTransitionAnimator
-import com.android.systemui.res.R
-import com.android.systemui.statusbar.chips.ui.view.ChipBackgroundContainer
 import com.android.systemui.statusbar.chips.ui.viewmodel.OngoingActivityChipViewModel.Companion.createDialogLaunchOnClickListener
 import com.android.systemui.statusbar.phone.SystemUIDialog
 import kotlin.test.Test
-import org.mockito.ArgumentMatchers.anyBoolean
-import org.mockito.kotlin.eq
 import org.mockito.kotlin.mock
 import org.mockito.kotlin.verify
-import org.mockito.kotlin.whenever
 
 @SmallTest
 class OngoingActivityChipViewModelTest : SysuiTestCase() {
     private val mockSystemUIDialog = mock<SystemUIDialog>()
     private val dialogDelegate = SystemUIDialog.Delegate { mockSystemUIDialog }
-    private val dialogTransitionAnimator = mock<DialogTransitionAnimator>()
-
-    private val chipBackgroundView = mock<ChipBackgroundContainer>()
-    private val chipView =
-        mock<View>().apply {
-            whenever(
-                    this.requireViewById<ChipBackgroundContainer>(
-                        R.id.ongoing_activity_chip_background
-                    )
-                )
-                .thenReturn(chipBackgroundView)
-        }
 
     @Test
     fun createDialogLaunchOnClickListener_showsDialogOnClick() {
-        val clickListener =
-            createDialogLaunchOnClickListener(dialogDelegate, dialogTransitionAnimator)
+        val clickListener = createDialogLaunchOnClickListener(dialogDelegate)
 
         // Dialogs must be created on the main thread
         context.mainExecutor.execute {
-            clickListener.onClick(chipView)
-            verify(dialogTransitionAnimator)
-                .showFromView(
-                    eq(mockSystemUIDialog),
-                    eq(chipBackgroundView),
-                    eq(null),
-                    anyBoolean(),
-                )
+            clickListener.onClick(mock<View>())
+            verify(mockSystemUIDialog).show()
         }
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/icon/domain/interactor/NotificationIconsInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/icon/domain/interactor/NotificationIconsInteractorTest.kt
index 26f5370..f07303e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/icon/domain/interactor/NotificationIconsInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/icon/domain/interactor/NotificationIconsInteractorTest.kt
@@ -17,21 +17,19 @@
 
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
-import com.android.systemui.SysUITestComponent
-import com.android.systemui.SysUITestModule
 import com.android.systemui.SysuiTestCase
-import com.android.systemui.TestMocksModule
-import com.android.systemui.biometrics.domain.BiometricsDomainLayerModule
-import com.android.systemui.collectLastValue
-import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.deviceentry.data.repository.FakeDeviceEntryRepository
-import com.android.systemui.runTest
-import com.android.systemui.statusbar.data.repository.NotificationListenerSettingsRepository
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.deviceentry.data.repository.fakeDeviceEntryRepository
+import com.android.systemui.deviceentry.domain.interactor.deviceEntryInteractor
+import com.android.systemui.kosmos.testDispatcher
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.statusbar.data.repository.notificationListenerSettingsRepository
 import com.android.systemui.statusbar.notification.data.model.activeNotificationModel
-import com.android.systemui.statusbar.notification.data.repository.ActiveNotificationListRepository
 import com.android.systemui.statusbar.notification.data.repository.ActiveNotificationsStore
-import com.android.systemui.statusbar.notification.domain.interactor.HeadsUpNotificationIconInteractor
-import com.android.systemui.statusbar.notification.domain.interactor.NotificationsKeyguardInteractor
+import com.android.systemui.statusbar.notification.data.repository.activeNotificationListRepository
+import com.android.systemui.statusbar.notification.data.repository.notificationsKeyguardViewStateRepository
+import com.android.systemui.statusbar.notification.domain.interactor.activeNotificationsInteractor
+import com.android.systemui.statusbar.notification.domain.interactor.headsUpNotificationIconInteractor
 import com.android.systemui.statusbar.notification.shared.byIsAmbient
 import com.android.systemui.statusbar.notification.shared.byIsLastMessageFromReply
 import com.android.systemui.statusbar.notification.shared.byIsPulsing
@@ -39,15 +37,15 @@
 import com.android.systemui.statusbar.notification.shared.byIsSilent
 import com.android.systemui.statusbar.notification.shared.byIsSuppressedFromStatusBar
 import com.android.systemui.statusbar.notification.shared.byKey
-import com.android.systemui.user.domain.UserDomainLayerModule
+import com.android.systemui.statusbar.notification.stack.domain.interactor.notificationsKeyguardInteractor
+import com.android.systemui.testKosmos
 import com.android.systemui.util.mockito.eq
-import com.android.systemui.util.mockito.mock
 import com.android.systemui.util.mockito.whenever
-import com.android.wm.shell.bubbles.Bubbles
+import com.android.wm.shell.bubbles.bubbles
+import com.android.wm.shell.bubbles.bubblesOptional
 import com.google.common.truth.Truth.assertThat
-import dagger.BindsInstance
-import dagger.Component
-import java.util.Optional
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.runTest
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -55,29 +53,22 @@
 @SmallTest
 @RunWith(AndroidJUnit4::class)
 class NotificationIconsInteractorTest : SysuiTestCase() {
+    private val kosmos = testKosmos()
+    private val testScope = kosmos.testScope
+    private val activeNotificationListRepository = kosmos.activeNotificationListRepository
+    private val notificationsKeyguardInteractor = kosmos.notificationsKeyguardInteractor
 
-    private val bubbles: Bubbles = mock()
-
-    @Component(modules = [SysUITestModule::class])
-    @SysUISingleton
-    interface TestComponent : SysUITestComponent<NotificationIconsInteractor> {
-
-        val activeNotificationListRepository: ActiveNotificationListRepository
-        val notificationsKeyguardInteractor: NotificationsKeyguardInteractor
-
-        @Component.Factory
-        interface Factory {
-            fun create(@BindsInstance test: SysuiTestCase, mocks: TestMocksModule): TestComponent
-        }
-    }
-
-    val testComponent: TestComponent =
-        DaggerNotificationIconsInteractorTest_TestComponent.factory()
-            .create(test = this, mocks = TestMocksModule(bubbles = Optional.of(bubbles)))
+    private val underTest =
+        NotificationIconsInteractor(
+            kosmos.activeNotificationsInteractor,
+            kosmos.bubblesOptional,
+            kosmos.headsUpNotificationIconInteractor,
+            kosmos.notificationsKeyguardViewStateRepository
+        )
 
     @Before
     fun setup() {
-        testComponent.apply {
+        testScope.apply {
             activeNotificationListRepository.activeNotifications.value =
                 ActiveNotificationsStore.Builder()
                     .apply { testIcons.forEach(::addIndividualNotif) }
@@ -87,22 +78,22 @@
 
     @Test
     fun filteredEntrySet() =
-        testComponent.runTest {
+        testScope.runTest {
             val filteredSet by collectLastValue(underTest.filteredNotifSet())
             assertThat(filteredSet).containsExactlyElementsIn(testIcons)
         }
 
     @Test
     fun filteredEntrySet_noExpandedBubbles() =
-        testComponent.runTest {
-            whenever(bubbles.isBubbleExpanded(eq("notif1"))).thenReturn(true)
+        testScope.runTest {
+            whenever(kosmos.bubbles.isBubbleExpanded(eq("notif1"))).thenReturn(true)
             val filteredSet by collectLastValue(underTest.filteredNotifSet())
             assertThat(filteredSet).comparingElementsUsing(byKey).doesNotContain("notif1")
         }
 
     @Test
     fun filteredEntrySet_noAmbient() =
-        testComponent.runTest {
+        testScope.runTest {
             val filteredSet by collectLastValue(underTest.filteredNotifSet(showAmbient = false))
             assertThat(filteredSet).comparingElementsUsing(byIsAmbient).doesNotContain(true)
             assertThat(filteredSet)
@@ -112,21 +103,21 @@
 
     @Test
     fun filteredEntrySet_noLowPriority() =
-        testComponent.runTest {
+        testScope.runTest {
             val filteredSet by collectLastValue(underTest.filteredNotifSet(showLowPriority = false))
             assertThat(filteredSet).comparingElementsUsing(byIsSilent).doesNotContain(true)
         }
 
     @Test
     fun filteredEntrySet_noDismissed() =
-        testComponent.runTest {
+        testScope.runTest {
             val filteredSet by collectLastValue(underTest.filteredNotifSet(showDismissed = false))
             assertThat(filteredSet).comparingElementsUsing(byIsRowDismissed).doesNotContain(true)
         }
 
     @Test
     fun filteredEntrySet_noRepliedMessages() =
-        testComponent.runTest {
+        testScope.runTest {
             val filteredSet by
                 collectLastValue(underTest.filteredNotifSet(showRepliedMessages = false))
             assertThat(filteredSet)
@@ -136,7 +127,7 @@
 
     @Test
     fun filteredEntrySet_noPulsing_notifsNotFullyHidden() =
-        testComponent.runTest {
+        testScope.runTest {
             val filteredSet by collectLastValue(underTest.filteredNotifSet(showPulsing = false))
             notificationsKeyguardInteractor.setNotificationsFullyHidden(false)
             assertThat(filteredSet).comparingElementsUsing(byIsPulsing).doesNotContain(true)
@@ -144,65 +135,46 @@
 
     @Test
     fun filteredEntrySet_noPulsing_notifsFullyHidden() =
-        testComponent.runTest {
+        testScope.runTest {
             val filteredSet by collectLastValue(underTest.filteredNotifSet(showPulsing = false))
             notificationsKeyguardInteractor.setNotificationsFullyHidden(true)
             assertThat(filteredSet).comparingElementsUsing(byIsPulsing).contains(true)
         }
 }
 
+@OptIn(ExperimentalCoroutinesApi::class)
 @SmallTest
 @RunWith(AndroidJUnit4::class)
 class AlwaysOnDisplayNotificationIconsInteractorTest : SysuiTestCase() {
+    private val kosmos = testKosmos()
+    private val testScope = kosmos.testScope
 
-    private val bubbles: Bubbles = mock()
-
-    @Component(
-        modules =
-            [
-                SysUITestModule::class,
-                BiometricsDomainLayerModule::class,
-                UserDomainLayerModule::class,
-            ]
-    )
-    @SysUISingleton
-    interface TestComponent : SysUITestComponent<AlwaysOnDisplayNotificationIconsInteractor> {
-
-        val activeNotificationListRepository: ActiveNotificationListRepository
-        val deviceEntryRepository: FakeDeviceEntryRepository
-        val notificationsKeyguardInteractor: NotificationsKeyguardInteractor
-
-        @Component.Factory
-        interface Factory {
-            fun create(@BindsInstance test: SysuiTestCase, mocks: TestMocksModule): TestComponent
-        }
-    }
-
-    private val testComponent: TestComponent =
-        DaggerAlwaysOnDisplayNotificationIconsInteractorTest_TestComponent.factory()
-            .create(test = this, mocks = TestMocksModule(bubbles = Optional.of(bubbles)))
+    private val underTest =
+        AlwaysOnDisplayNotificationIconsInteractor(
+            kosmos.testDispatcher,
+            kosmos.deviceEntryInteractor,
+            kosmos.notificationIconsInteractor,
+        )
 
     @Before
     fun setup() {
-        testComponent.apply {
-            activeNotificationListRepository.activeNotifications.value =
-                ActiveNotificationsStore.Builder()
-                    .apply { testIcons.forEach(::addIndividualNotif) }
-                    .build()
-        }
+        kosmos.activeNotificationListRepository.activeNotifications.value =
+            ActiveNotificationsStore.Builder()
+                .apply { testIcons.forEach(::addIndividualNotif) }
+                .build()
     }
 
     @Test
     fun filteredEntrySet_noExpandedBubbles() =
-        testComponent.runTest {
-            whenever(bubbles.isBubbleExpanded(eq("notif1"))).thenReturn(true)
+        testScope.runTest {
+            whenever(kosmos.bubbles.isBubbleExpanded(eq("notif1"))).thenReturn(true)
             val filteredSet by collectLastValue(underTest.aodNotifs)
             assertThat(filteredSet).comparingElementsUsing(byKey).doesNotContain("notif1")
         }
 
     @Test
     fun filteredEntrySet_noAmbient() =
-        testComponent.runTest {
+        testScope.runTest {
             val filteredSet by collectLastValue(underTest.aodNotifs)
             assertThat(filteredSet).comparingElementsUsing(byIsAmbient).doesNotContain(true)
             assertThat(filteredSet)
@@ -212,14 +184,14 @@
 
     @Test
     fun filteredEntrySet_noDismissed() =
-        testComponent.runTest {
+        testScope.runTest {
             val filteredSet by collectLastValue(underTest.aodNotifs)
             assertThat(filteredSet).comparingElementsUsing(byIsRowDismissed).doesNotContain(true)
         }
 
     @Test
     fun filteredEntrySet_noRepliedMessages() =
-        testComponent.runTest {
+        testScope.runTest {
             val filteredSet by collectLastValue(underTest.aodNotifs)
             assertThat(filteredSet)
                 .comparingElementsUsing(byIsLastMessageFromReply)
@@ -228,37 +200,37 @@
 
     @Test
     fun filteredEntrySet_showPulsing_notifsNotFullyHidden_bypassDisabled() =
-        testComponent.runTest {
+        testScope.runTest {
             val filteredSet by collectLastValue(underTest.aodNotifs)
-            deviceEntryRepository.setBypassEnabled(false)
-            notificationsKeyguardInteractor.setNotificationsFullyHidden(false)
+            kosmos.fakeDeviceEntryRepository.setBypassEnabled(false)
+            kosmos.notificationsKeyguardInteractor.setNotificationsFullyHidden(false)
             assertThat(filteredSet).comparingElementsUsing(byIsPulsing).contains(true)
         }
 
     @Test
     fun filteredEntrySet_showPulsing_notifsFullyHidden_bypassDisabled() =
-        testComponent.runTest {
+        testScope.runTest {
             val filteredSet by collectLastValue(underTest.aodNotifs)
-            deviceEntryRepository.setBypassEnabled(false)
-            notificationsKeyguardInteractor.setNotificationsFullyHidden(true)
+            kosmos.fakeDeviceEntryRepository.setBypassEnabled(false)
+            kosmos.notificationsKeyguardInteractor.setNotificationsFullyHidden(true)
             assertThat(filteredSet).comparingElementsUsing(byIsPulsing).contains(true)
         }
 
     @Test
     fun filteredEntrySet_noPulsing_notifsNotFullyHidden_bypassEnabled() =
-        testComponent.runTest {
+        testScope.runTest {
             val filteredSet by collectLastValue(underTest.aodNotifs)
-            deviceEntryRepository.setBypassEnabled(true)
-            notificationsKeyguardInteractor.setNotificationsFullyHidden(false)
+            kosmos.fakeDeviceEntryRepository.setBypassEnabled(true)
+            kosmos.notificationsKeyguardInteractor.setNotificationsFullyHidden(false)
             assertThat(filteredSet).comparingElementsUsing(byIsPulsing).doesNotContain(true)
         }
 
     @Test
     fun filteredEntrySet_showPulsing_notifsFullyHidden_bypassEnabled() =
-        testComponent.runTest {
+        testScope.runTest {
             val filteredSet by collectLastValue(underTest.aodNotifs)
-            deviceEntryRepository.setBypassEnabled(true)
-            notificationsKeyguardInteractor.setNotificationsFullyHidden(true)
+            kosmos.fakeDeviceEntryRepository.setBypassEnabled(true)
+            kosmos.notificationsKeyguardInteractor.setNotificationsFullyHidden(true)
             assertThat(filteredSet).comparingElementsUsing(byIsPulsing).contains(true)
         }
 }
@@ -266,32 +238,19 @@
 @SmallTest
 @RunWith(AndroidJUnit4::class)
 class StatusBarNotificationIconsInteractorTest : SysuiTestCase() {
-
-    private val bubbles: Bubbles = mock()
-
-    @Component(modules = [SysUITestModule::class])
-    @SysUISingleton
-    interface TestComponent : SysUITestComponent<StatusBarNotificationIconsInteractor> {
-
-        val activeNotificationListRepository: ActiveNotificationListRepository
-        val headsUpIconsInteractor: HeadsUpNotificationIconInteractor
-        val notificationsKeyguardInteractor: NotificationsKeyguardInteractor
-        val notificationListenerSettingsRepository: NotificationListenerSettingsRepository
-
-        @Component.Factory
-        interface Factory {
-            fun create(@BindsInstance test: SysuiTestCase, mocks: TestMocksModule): TestComponent
-        }
-    }
-
-    val testComponent: TestComponent =
-        DaggerStatusBarNotificationIconsInteractorTest_TestComponent.factory()
-            .create(test = this, mocks = TestMocksModule(bubbles = Optional.of(bubbles)))
+    private val kosmos = testKosmos()
+    private val testScope = kosmos.testScope
+    private val underTest =
+        StatusBarNotificationIconsInteractor(
+            kosmos.testDispatcher,
+            kosmos.notificationIconsInteractor,
+            kosmos.notificationListenerSettingsRepository,
+        )
 
     @Before
     fun setup() {
-        testComponent.apply {
-            activeNotificationListRepository.activeNotifications.value =
+        testScope.apply {
+            kosmos.activeNotificationListRepository.activeNotifications.value =
                 ActiveNotificationsStore.Builder()
                     .apply { testIcons.forEach(::addIndividualNotif) }
                     .build()
@@ -300,15 +259,15 @@
 
     @Test
     fun filteredEntrySet_noExpandedBubbles() =
-        testComponent.runTest {
-            whenever(bubbles.isBubbleExpanded(eq("notif1"))).thenReturn(true)
+        testScope.runTest {
+            whenever(kosmos.bubbles.isBubbleExpanded(eq("notif1"))).thenReturn(true)
             val filteredSet by collectLastValue(underTest.statusBarNotifs)
             assertThat(filteredSet).comparingElementsUsing(byKey).doesNotContain("notif1")
         }
 
     @Test
     fun filteredEntrySet_noAmbient() =
-        testComponent.runTest {
+        testScope.runTest {
             val filteredSet by collectLastValue(underTest.statusBarNotifs)
             assertThat(filteredSet).comparingElementsUsing(byIsAmbient).doesNotContain(true)
             assertThat(filteredSet)
@@ -318,30 +277,30 @@
 
     @Test
     fun filteredEntrySet_noLowPriority_whenDontShowSilentIcons() =
-        testComponent.runTest {
+        testScope.runTest {
             val filteredSet by collectLastValue(underTest.statusBarNotifs)
-            notificationListenerSettingsRepository.showSilentStatusIcons.value = false
+            kosmos.notificationListenerSettingsRepository.showSilentStatusIcons.value = false
             assertThat(filteredSet).comparingElementsUsing(byIsSilent).doesNotContain(true)
         }
 
     @Test
     fun filteredEntrySet_showLowPriority_whenShowSilentIcons() =
-        testComponent.runTest {
+        testScope.runTest {
             val filteredSet by collectLastValue(underTest.statusBarNotifs)
-            notificationListenerSettingsRepository.showSilentStatusIcons.value = true
+            kosmos.notificationListenerSettingsRepository.showSilentStatusIcons.value = true
             assertThat(filteredSet).comparingElementsUsing(byIsSilent).contains(true)
         }
 
     @Test
     fun filteredEntrySet_noDismissed() =
-        testComponent.runTest {
+        testScope.runTest {
             val filteredSet by collectLastValue(underTest.statusBarNotifs)
             assertThat(filteredSet).comparingElementsUsing(byIsRowDismissed).doesNotContain(true)
         }
 
     @Test
     fun filteredEntrySet_noRepliedMessages() =
-        testComponent.runTest {
+        testScope.runTest {
             val filteredSet by collectLastValue(underTest.statusBarNotifs)
             assertThat(filteredSet)
                 .comparingElementsUsing(byIsLastMessageFromReply)
@@ -350,9 +309,9 @@
 
     @Test
     fun filteredEntrySet_includesIsolatedIcon() =
-        testComponent.runTest {
+        testScope.runTest {
             val filteredSet by collectLastValue(underTest.statusBarNotifs)
-            headsUpIconsInteractor.setIsolatedIconNotificationKey("notif5")
+            kosmos.headsUpNotificationIconInteractor.setIsolatedIconNotificationKey("notif5")
             assertThat(filteredSet).comparingElementsUsing(byKey).contains("notif5")
         }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerAlwaysOnDisplayViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerAlwaysOnDisplayViewModelTest.kt
index 894e02e..1f4e80e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerAlwaysOnDisplayViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerAlwaysOnDisplayViewModelTest.kt
@@ -16,111 +16,81 @@
 
 package com.android.systemui.statusbar.notification.icon.ui.viewmodel
 
+import android.content.res.mainResources
 import android.platform.test.annotations.DisableFlags
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.systemui.Flags.FLAG_KEYGUARD_WM_STATE_REFACTOR
 import com.android.systemui.Flags.FLAG_NEW_AOD_TRANSITION
-import com.android.systemui.SysUITestComponent
-import com.android.systemui.SysUITestModule
 import com.android.systemui.SysuiTestCase
-import com.android.systemui.TestMocksModule
-import com.android.systemui.biometrics.domain.BiometricsDomainLayerModule
-import com.android.systemui.collectLastValue
-import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.flags.FakeFeatureFlagsClassicModule
+import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.flags.Flags
-import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
-import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
+import com.android.systemui.flags.fakeFeatureFlagsClassic
+import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
+import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
+import com.android.systemui.keyguard.domain.interactor.keyguardInteractor
+import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor
 import com.android.systemui.keyguard.shared.model.DozeStateModel
 import com.android.systemui.keyguard.shared.model.DozeTransitionModel
 import com.android.systemui.keyguard.shared.model.KeyguardState
 import com.android.systemui.keyguard.shared.model.TransitionState
 import com.android.systemui.keyguard.shared.model.TransitionStep
-import com.android.systemui.power.data.repository.FakePowerRepository
+import com.android.systemui.kosmos.testDispatcher
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.power.data.repository.fakePowerRepository
 import com.android.systemui.power.shared.model.WakeSleepReason
 import com.android.systemui.power.shared.model.WakefulnessState
-import com.android.systemui.runCurrent
-import com.android.systemui.runTest
-import com.android.systemui.statusbar.phone.DozeParameters
-import com.android.systemui.statusbar.phone.ScreenOffAnimationController
-import com.android.systemui.statusbar.policy.data.repository.FakeDeviceProvisioningRepository
-import com.android.systemui.user.domain.UserDomainLayerModule
-import com.android.systemui.util.mockito.mock
+import com.android.systemui.shade.domain.interactor.shadeInteractor
+import com.android.systemui.statusbar.notification.icon.domain.interactor.alwaysOnDisplayNotificationIconsInteractor
+import com.android.systemui.statusbar.phone.dozeParameters
+import com.android.systemui.testKosmos
 import com.android.systemui.util.mockito.whenever
 import com.google.common.truth.Truth.assertThat
-import dagger.BindsInstance
-import dagger.Component
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
 
+@OptIn(ExperimentalCoroutinesApi::class)
 @SmallTest
 @RunWith(AndroidJUnit4::class)
 class NotificationIconContainerAlwaysOnDisplayViewModelTest : SysuiTestCase() {
-
-    @SysUISingleton
-    @Component(
-        modules =
-            [
-                SysUITestModule::class,
-                BiometricsDomainLayerModule::class,
-                UserDomainLayerModule::class,
-            ]
-    )
-    interface TestComponent :
-        SysUITestComponent<NotificationIconContainerAlwaysOnDisplayViewModel> {
-
-        val deviceProvisioningRepository: FakeDeviceProvisioningRepository
-        val keyguardRepository: FakeKeyguardRepository
-        val keyguardTransitionRepository: FakeKeyguardTransitionRepository
-        val powerRepository: FakePowerRepository
-
-        @Component.Factory
-        interface Factory {
-            fun create(
-                @BindsInstance test: SysuiTestCase,
-                mocks: TestMocksModule,
-                featureFlags: FakeFeatureFlagsClassicModule,
-            ): TestComponent
+    private val kosmos =
+        testKosmos().apply {
+            fakeFeatureFlagsClassic.apply { set(Flags.FULL_SCREEN_USER_SWITCHER, value = false) }
         }
-    }
 
-    private val dozeParams: DozeParameters = mock()
-    private val screenOffAnimController: ScreenOffAnimationController = mock()
-
-    private val testComponent: TestComponent =
-        DaggerNotificationIconContainerAlwaysOnDisplayViewModelTest_TestComponent.factory()
-            .create(
-                test = this,
-                featureFlags =
-                    FakeFeatureFlagsClassicModule {
-                        set(Flags.FULL_SCREEN_USER_SWITCHER, value = false)
-                    },
-                mocks =
-                    TestMocksModule(
-                        dozeParameters = dozeParams,
-                        screenOffAnimationController = screenOffAnimController,
-                    ),
-            )
+    val underTest =
+        NotificationIconContainerAlwaysOnDisplayViewModel(
+            kosmos.testDispatcher,
+            kosmos.alwaysOnDisplayNotificationIconsInteractor,
+            kosmos.keyguardInteractor,
+            kosmos.keyguardTransitionInteractor,
+            kosmos.mainResources,
+            kosmos.shadeInteractor,
+        )
+    val testScope = kosmos.testScope
+    val keyguardRepository = kosmos.fakeKeyguardRepository
+    val keyguardTransitionRepository = kosmos.fakeKeyguardTransitionRepository
+    val powerRepository = kosmos.fakePowerRepository
 
     @Before
     fun setup() {
-        testComponent.apply {
-            keyguardRepository.setKeyguardShowing(true)
-            keyguardRepository.setKeyguardOccluded(false)
-            powerRepository.updateWakefulness(
-                rawState = WakefulnessState.AWAKE,
-                lastWakeReason = WakeSleepReason.OTHER,
-                lastSleepReason = WakeSleepReason.OTHER,
-            )
-        }
+        keyguardRepository.setKeyguardShowing(true)
+        keyguardRepository.setKeyguardOccluded(false)
+        kosmos.fakePowerRepository.updateWakefulness(
+            rawState = WakefulnessState.AWAKE,
+            lastWakeReason = WakeSleepReason.OTHER,
+            lastSleepReason = WakeSleepReason.OTHER,
+        )
         mSetFlagsRule.enableFlags(FLAG_NEW_AOD_TRANSITION)
     }
 
     @Test
     fun animationsEnabled_isFalse_whenDeviceAsleepAndNotPulsing() =
-        testComponent.runTest {
+        testScope.runTest {
             powerRepository.updateWakefulness(
                 rawState = WakefulnessState.ASLEEP,
                 lastWakeReason = WakeSleepReason.POWER_BUTTON,
@@ -143,7 +113,7 @@
 
     @Test
     fun animationsEnabled_isTrue_whenDeviceAsleepAndPulsing() =
-        testComponent.runTest {
+        testScope.runTest {
             powerRepository.updateWakefulness(
                 rawState = WakefulnessState.ASLEEP,
                 lastWakeReason = WakeSleepReason.POWER_BUTTON,
@@ -166,7 +136,7 @@
 
     @Test
     fun animationsEnabled_isFalse_whenStartingToSleepAndNotControlScreenOff() =
-        testComponent.runTest {
+        testScope.runTest {
             powerRepository.updateWakefulness(
                 rawState = WakefulnessState.STARTING_TO_SLEEP,
                 lastWakeReason = WakeSleepReason.POWER_BUTTON,
@@ -179,7 +149,7 @@
                     transitionState = TransitionState.STARTED,
                 )
             )
-            whenever(dozeParams.shouldControlScreenOff()).thenReturn(false)
+            whenever(kosmos.dozeParameters.shouldControlScreenOff()).thenReturn(false)
             val animationsEnabled by collectLastValue(underTest.areContainerChangesAnimated)
             runCurrent()
             assertThat(animationsEnabled).isFalse()
@@ -187,7 +157,7 @@
 
     @Test
     fun animationsEnabled_isTrue_whenStartingToSleepAndControlScreenOff() =
-        testComponent.runTest {
+        testScope.runTest {
             val animationsEnabled by collectLastValue(underTest.areContainerChangesAnimated)
             assertThat(animationsEnabled).isTrue()
 
@@ -203,13 +173,13 @@
                     transitionState = TransitionState.STARTED,
                 )
             )
-            whenever(dozeParams.shouldControlScreenOff()).thenReturn(true)
+            whenever(kosmos.dozeParameters.shouldControlScreenOff()).thenReturn(true)
             assertThat(animationsEnabled).isTrue()
         }
 
     @Test
     fun animationsEnabled_isTrue_whenNotAsleep() =
-        testComponent.runTest {
+        testScope.runTest {
             powerRepository.updateWakefulness(
                 rawState = WakefulnessState.AWAKE,
                 lastWakeReason = WakeSleepReason.POWER_BUTTON,
@@ -228,7 +198,7 @@
     @Test
     @DisableFlags(FLAG_KEYGUARD_WM_STATE_REFACTOR)
     fun animationsEnabled_isTrue_whenKeyguardIsShowing() =
-        testComponent.runTest {
+        testScope.runTest {
             keyguardTransitionRepository.sendTransitionStep(
                 TransitionStep(
                     transitionState = TransitionState.STARTED,
@@ -257,7 +227,7 @@
 
     @Test
     fun tintAlpha_isZero_whenNotOnAodOrDozing() =
-        testComponent.runTest {
+        testScope.runTest {
             val tintAlpha by collectLastValue(underTest.tintAlpha)
             runCurrent()
             keyguardTransitionRepository.sendTransitionSteps(
@@ -271,7 +241,7 @@
 
     @Test
     fun tintAlpha_isOne_whenOnAod() =
-        testComponent.runTest {
+        testScope.runTest {
             val tintAlpha by collectLastValue(underTest.tintAlpha)
             runCurrent()
             keyguardTransitionRepository.sendTransitionSteps(
@@ -285,7 +255,7 @@
 
     @Test
     fun tintAlpha_isOne_whenDozing() =
-        testComponent.runTest {
+        testScope.runTest {
             val tintAlpha by collectLastValue(underTest.tintAlpha)
             runCurrent()
             keyguardTransitionRepository.sendTransitionSteps(
@@ -298,7 +268,7 @@
 
     @Test
     fun tintAlpha_isOne_whenTransitionFromAodToDoze() =
-        testComponent.runTest {
+        testScope.runTest {
             keyguardTransitionRepository.sendTransitionSteps(
                 from = KeyguardState.GONE,
                 to = KeyguardState.AOD,
@@ -332,7 +302,7 @@
 
     @Test
     fun tintAlpha_isFraction_midTransitionToAod() =
-        testComponent.runTest {
+        testScope.runTest {
             val tintAlpha by collectLastValue(underTest.tintAlpha)
             runCurrent()
 
@@ -361,7 +331,7 @@
 
     @Test
     fun iconAnimationsEnabled_whenOnLockScreen() =
-        testComponent.runTest {
+        testScope.runTest {
             val iconAnimationsEnabled by collectLastValue(underTest.areIconAnimationsEnabled)
             runCurrent()
 
@@ -376,7 +346,7 @@
 
     @Test
     fun iconAnimationsDisabled_whenOnAod() =
-        testComponent.runTest {
+        testScope.runTest {
             val iconAnimationsEnabled by collectLastValue(underTest.areIconAnimationsEnabled)
             runCurrent()
 
@@ -391,7 +361,7 @@
 
     @Test
     fun iconAnimationsDisabled_whenDozing() =
-        testComponent.runTest {
+        testScope.runTest {
             val iconAnimationsEnabled by collectLastValue(underTest.areIconAnimationsEnabled)
             runCurrent()
 
diff --git a/packages/SystemUI/tests/utils/src/android/app/admin/AlarmManagerKosmos.kt b/packages/SystemUI/tests/utils/src/android/app/admin/AlarmManagerKosmos.kt
new file mode 100644
index 0000000..a7b5873
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/android/app/admin/AlarmManagerKosmos.kt
@@ -0,0 +1,23 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app.admin
+
+import android.app.AlarmManager
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.util.mockito.mock
+
+var Kosmos.alarmManager by Kosmos.Fixture { mock<AlarmManager>() }
diff --git a/packages/SystemUI/tests/utils/src/com/android/app/admin/DevicePolicyManagerKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/app/admin/DevicePolicyManagerKosmos.kt
new file mode 100644
index 0000000..f51e122
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/app/admin/DevicePolicyManagerKosmos.kt
@@ -0,0 +1,22 @@
+/*
+ * Copyright (C) 2024 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.app.admin
+
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.util.mockito.mock
+
+val Kosmos.devicePolicyManager by Kosmos.Fixture { mock<android.app.admin.DevicePolicyManager>() }
diff --git a/packages/SystemUI/tests/utils/src/com/android/internal/widget/LockPatternUtilsKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/internal/widget/LockPatternUtilsKosmos.kt
index d9ea5e9..b511270 100644
--- a/packages/SystemUI/tests/utils/src/com/android/internal/widget/LockPatternUtilsKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/internal/widget/LockPatternUtilsKosmos.kt
@@ -16,7 +16,14 @@
 
 package com.android.internal.widget
 
+import android.app.admin.devicePolicyManager
 import com.android.systemui.kosmos.Kosmos
 import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.whenever
 
-var Kosmos.lockPatternUtils by Kosmos.Fixture { mock<LockPatternUtils>() }
+var Kosmos.lockPatternUtils by
+    Kosmos.Fixture {
+        mock<LockPatternUtils>().apply {
+            whenever(this.devicePolicyManager).thenReturn(this@Fixture.devicePolicyManager)
+        }
+    }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt
index 5bae6ec..87143ef 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt
@@ -135,6 +135,9 @@
 
     private var isShowKeyguardWhenReenabled: Boolean = false
 
+    private val _canIgnoreAuthAndReturnToGone = MutableStateFlow(false)
+    override val canIgnoreAuthAndReturnToGone = _canIgnoreAuthAndReturnToGone.asStateFlow()
+
     override fun setQuickSettingsVisible(isVisible: Boolean) {
         _isQuickSettingsVisible.value = isVisible
     }
@@ -278,6 +281,10 @@
     override fun isShowKeyguardWhenReenabled(): Boolean {
         return isShowKeyguardWhenReenabled
     }
+
+    override fun setCanIgnoreAuthAndReturnToGone(canWake: Boolean) {
+        _canIgnoreAuthAndReturnToGone.value = canWake
+    }
 }
 
 @Module
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractorKosmos.kt
index ae138c8..ef789d1 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractorKosmos.kt
@@ -37,5 +37,6 @@
             powerInteractor = powerInteractor,
             keyguardOcclusionInteractor = keyguardOcclusionInteractor,
             deviceEntryRepository = deviceEntryRepository,
+            wakeToGoneInteractor = keyguardWakeDirectlyToGoneInteractor,
         )
     }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractorKosmos.kt
index e7e007f..446652c 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractorKosmos.kt
@@ -39,5 +39,6 @@
             powerInteractor = powerInteractor,
             keyguardOcclusionInteractor = keyguardOcclusionInteractor,
             deviceEntryRepository = deviceEntryRepository,
+            wakeToGoneInteractor = keyguardWakeDirectlyToGoneInteractor,
         )
     }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractorKosmos.kt
index a9be06d..6c3de44 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractorKosmos.kt
@@ -16,13 +16,16 @@
 
 package com.android.systemui.keyguard.domain.interactor
 
+import com.android.systemui.deviceentry.domain.interactor.deviceEntryInteractor
 import com.android.systemui.keyguard.data.repository.keyguardTransitionRepository
 import com.android.systemui.kosmos.Kosmos
 import com.android.systemui.kosmos.applicationCoroutineScope
 import com.android.systemui.kosmos.testDispatcher
 import com.android.systemui.power.domain.interactor.powerInteractor
 import com.android.systemui.statusbar.domain.interactor.keyguardOcclusionInteractor
+import kotlinx.coroutines.ExperimentalCoroutinesApi
 
+@OptIn(ExperimentalCoroutinesApi::class)
 var Kosmos.fromDreamingTransitionInteractor by
     Kosmos.Fixture {
         FromDreamingTransitionInteractor(
@@ -36,5 +39,6 @@
             glanceableHubTransitions = glanceableHubTransitions,
             powerInteractor = powerInteractor,
             keyguardOcclusionInteractor = keyguardOcclusionInteractor,
+            deviceEntryInteractor = deviceEntryInteractor,
         )
     }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardWakeDirectlyToGoneInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardWakeDirectlyToGoneInteractorKosmos.kt
new file mode 100644
index 0000000..63e168d
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardWakeDirectlyToGoneInteractorKosmos.kt
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.domain.interactor
+
+import android.app.admin.alarmManager
+import android.content.mockedContext
+import com.android.internal.widget.lockPatternUtils
+import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.applicationCoroutineScope
+import com.android.systemui.power.domain.interactor.powerInteractor
+import com.android.systemui.user.domain.interactor.selectedUserInteractor
+import com.android.systemui.util.settings.fakeSettings
+import com.android.systemui.util.time.systemClock
+
+val Kosmos.keyguardWakeDirectlyToGoneInteractor by
+    Kosmos.Fixture {
+        KeyguardWakeDirectlyToGoneInteractor(
+            applicationCoroutineScope,
+            mockedContext,
+            fakeKeyguardRepository,
+            systemClock,
+            alarmManager,
+            keyguardTransitionInteractor,
+            powerInteractor,
+            fakeSettings,
+            lockPatternUtils,
+            fakeSettings,
+            selectedUserInteractor,
+        )
+    }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/WindowManagerLockscreenVisibilityInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/WindowManagerLockscreenVisibilityInteractorKosmos.kt
index bd9c0be..8bb2fce 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/WindowManagerLockscreenVisibilityInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/WindowManagerLockscreenVisibilityInteractorKosmos.kt
@@ -17,6 +17,7 @@
 package com.android.systemui.keyguard.domain.interactor
 
 import com.android.systemui.deviceentry.domain.interactor.deviceEntryInteractor
+import com.android.systemui.keyguard.data.repository.keyguardTransitionRepository
 import com.android.systemui.kosmos.Kosmos
 import com.android.systemui.scene.domain.interactor.sceneInteractor
 import com.android.systemui.statusbar.notification.domain.interactor.notificationLaunchAnimationInteractor
@@ -25,6 +26,7 @@
     Kosmos.Fixture {
         WindowManagerLockscreenVisibilityInteractor(
             keyguardInteractor = keyguardInteractor,
+            transitionRepository = keyguardTransitionRepository,
             transitionInteractor = keyguardTransitionInteractor,
             surfaceBehindInteractor = keyguardSurfaceBehindInteractor,
             fromLockscreenInteractor = fromLockscreenTransitionInteractor,
@@ -33,5 +35,6 @@
             notificationLaunchAnimationInteractor = notificationLaunchAnimationInteractor,
             sceneInteractor = { sceneInteractor },
             deviceEntryInteractor = { deviceEntryInteractor },
+            wakeToGoneInteractor = keyguardWakeDirectlyToGoneInteractor,
         )
     }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/chips/casttootherdevice/ui/viewmodel/CastToOtherDeviceChipViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/chips/casttootherdevice/ui/viewmodel/CastToOtherDeviceChipViewModelKosmos.kt
index 144fe26..2335f21 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/chips/casttootherdevice/ui/viewmodel/CastToOtherDeviceChipViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/chips/casttootherdevice/ui/viewmodel/CastToOtherDeviceChipViewModelKosmos.kt
@@ -17,7 +17,6 @@
 package com.android.systemui.statusbar.chips.casttootherdevice.ui.viewmodel
 
 import android.content.applicationContext
-import com.android.systemui.animation.mockDialogTransitionAnimator
 import com.android.systemui.kosmos.Kosmos
 import com.android.systemui.kosmos.applicationCoroutineScope
 import com.android.systemui.statusbar.chips.casttootherdevice.domain.interactor.mediaRouterChipInteractor
@@ -34,6 +33,5 @@
             mediaRouterChipInteractor = mediaRouterChipInteractor,
             systemClock = fakeSystemClock,
             endMediaProjectionDialogHelper = endMediaProjectionDialogHelper,
-            dialogTransitionAnimator = mockDialogTransitionAnimator,
         )
     }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/chips/screenrecord/ui/viewmodel/ScreenRecordChipViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/chips/screenrecord/ui/viewmodel/ScreenRecordChipViewModelKosmos.kt
index 1d06947..2773f82 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/chips/screenrecord/ui/viewmodel/ScreenRecordChipViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/chips/screenrecord/ui/viewmodel/ScreenRecordChipViewModelKosmos.kt
@@ -17,7 +17,6 @@
 package com.android.systemui.statusbar.chips.screenrecord.ui.viewmodel
 
 import android.content.applicationContext
-import com.android.systemui.animation.mockDialogTransitionAnimator
 import com.android.systemui.kosmos.Kosmos
 import com.android.systemui.kosmos.applicationCoroutineScope
 import com.android.systemui.statusbar.chips.mediaprojection.ui.view.endMediaProjectionDialogHelper
@@ -31,7 +30,6 @@
             context = applicationContext,
             interactor = screenRecordChipInteractor,
             endMediaProjectionDialogHelper = endMediaProjectionDialogHelper,
-            dialogTransitionAnimator = mockDialogTransitionAnimator,
             systemClock = fakeSystemClock,
         )
     }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/chips/sharetoapp/ui/viewmodel/ShareToAppChipViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/chips/sharetoapp/ui/viewmodel/ShareToAppChipViewModelKosmos.kt
index 2e475a3..1b3108c 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/chips/sharetoapp/ui/viewmodel/ShareToAppChipViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/chips/sharetoapp/ui/viewmodel/ShareToAppChipViewModelKosmos.kt
@@ -17,7 +17,6 @@
 package com.android.systemui.statusbar.chips.sharetoapp.ui.viewmodel
 
 import android.content.applicationContext
-import com.android.systemui.animation.mockDialogTransitionAnimator
 import com.android.systemui.kosmos.Kosmos
 import com.android.systemui.kosmos.applicationCoroutineScope
 import com.android.systemui.statusbar.chips.mediaprojection.domain.interactor.mediaProjectionChipInteractor
@@ -32,6 +31,5 @@
             mediaProjectionChipInteractor = mediaProjectionChipInteractor,
             systemClock = fakeSystemClock,
             endMediaProjectionDialogHelper = endMediaProjectionDialogHelper,
-            dialogTransitionAnimator = mockDialogTransitionAnimator,
         )
     }
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java
index 9fc64a9..099cb28 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java
@@ -26,7 +26,6 @@
 import android.annotation.NonNull;
 import android.content.Context;
 import android.graphics.Region;
-import android.hardware.input.InputManager;
 import android.os.Looper;
 import android.os.PowerManager;
 import android.os.SystemClock;
@@ -56,7 +55,6 @@
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
 import java.util.ArrayList;
-import java.util.Objects;
 import java.util.StringJoiner;
 
 /**
@@ -748,8 +746,6 @@
 
         if ((mEnabledFeatures & FLAG_FEATURE_MOUSE_KEYS) != 0) {
             mMouseKeysInterceptor = new MouseKeysInterceptor(mAms,
-                    Objects.requireNonNull(mContext.getSystemService(
-                            InputManager.class)),
                     Looper.myLooper(),
                     Display.DEFAULT_DISPLAY);
             addFirstEventHandler(Display.DEFAULT_DISPLAY, mMouseKeysInterceptor);
diff --git a/services/accessibility/java/com/android/server/accessibility/MouseKeysInterceptor.java b/services/accessibility/java/com/android/server/accessibility/MouseKeysInterceptor.java
index 3f0f23f..56da231 100644
--- a/services/accessibility/java/com/android/server/accessibility/MouseKeysInterceptor.java
+++ b/services/accessibility/java/com/android/server/accessibility/MouseKeysInterceptor.java
@@ -23,7 +23,6 @@
 import android.annotation.RequiresPermission;
 import android.companion.virtual.VirtualDeviceManager;
 import android.companion.virtual.VirtualDeviceParams;
-import android.hardware.input.InputManager;
 import android.hardware.input.VirtualMouse;
 import android.hardware.input.VirtualMouseButtonEvent;
 import android.hardware.input.VirtualMouseConfig;
@@ -60,8 +59,8 @@
  * In case multiple physical keyboard are connected to a device,
  * mouse keys of each physical keyboard will control a single (global) mouse pointer.
  */
-public class MouseKeysInterceptor extends BaseEventStreamTransformation implements Handler.Callback,
-        InputManager.InputDeviceListener {
+public class MouseKeysInterceptor extends BaseEventStreamTransformation
+        implements Handler.Callback {
     private static final String LOG_TAG = "MouseKeysInterceptor";
 
     // To enable these logs, run: 'adb shell setprop log.tag.MouseKeysInterceptor DEBUG'
@@ -77,11 +76,8 @@
     private static final int INTERVAL_MILLIS = 10;
 
     private final AccessibilityManagerService mAms;
-    private final InputManager mInputManager;
     private final Handler mHandler;
 
-    private final int mDisplayId;
-
     VirtualDeviceManager.VirtualDevice mVirtualDevice = null;
 
     private VirtualMouse mVirtualMouse = null;
@@ -100,23 +96,23 @@
     /** Last time the key action was performed */
     private long mLastTimeKeyActionPerformed = 0;
 
-    // TODO (b/346706749): This is currently using the numpad key bindings for mouse keys.
-    //  Decide the final mouse key bindings with UX input.
+    /** Whether scroll toggle is on */
+    private boolean mScrollToggleOn = false;
+
     public enum MouseKeyEvent {
-        DIAGONAL_DOWN_LEFT_MOVE(KeyEvent.KEYCODE_NUMPAD_1),
-        DOWN_MOVE(KeyEvent.KEYCODE_NUMPAD_2),
-        DIAGONAL_DOWN_RIGHT_MOVE(KeyEvent.KEYCODE_NUMPAD_3),
-        LEFT_MOVE(KeyEvent.KEYCODE_NUMPAD_4),
-        RIGHT_MOVE(KeyEvent.KEYCODE_NUMPAD_6),
-        DIAGONAL_UP_LEFT_MOVE(KeyEvent.KEYCODE_NUMPAD_7),
-        UP_MOVE(KeyEvent.KEYCODE_NUMPAD_8),
-        DIAGONAL_UP_RIGHT_MOVE(KeyEvent.KEYCODE_NUMPAD_9),
-        LEFT_CLICK(KeyEvent.KEYCODE_NUMPAD_5),
-        RIGHT_CLICK(KeyEvent.KEYCODE_NUMPAD_DOT),
-        HOLD(KeyEvent.KEYCODE_NUMPAD_MULTIPLY),
-        RELEASE(KeyEvent.KEYCODE_NUMPAD_SUBTRACT),
-        SCROLL_UP(KeyEvent.KEYCODE_A),
-        SCROLL_DOWN(KeyEvent.KEYCODE_S);
+        DIAGONAL_UP_LEFT_MOVE(KeyEvent.KEYCODE_7),
+        UP_MOVE_OR_SCROLL(KeyEvent.KEYCODE_8),
+        DIAGONAL_UP_RIGHT_MOVE(KeyEvent.KEYCODE_9),
+        LEFT_MOVE(KeyEvent.KEYCODE_U),
+        RIGHT_MOVE(KeyEvent.KEYCODE_O),
+        DIAGONAL_DOWN_LEFT_MOVE(KeyEvent.KEYCODE_J),
+        DOWN_MOVE_OR_SCROLL(KeyEvent.KEYCODE_K),
+        DIAGONAL_DOWN_RIGHT_MOVE(KeyEvent.KEYCODE_L),
+        LEFT_CLICK(KeyEvent.KEYCODE_I),
+        RIGHT_CLICK(KeyEvent.KEYCODE_SLASH),
+        HOLD(KeyEvent.KEYCODE_M),
+        RELEASE(KeyEvent.KEYCODE_COMMA),
+        SCROLL_TOGGLE(KeyEvent.KEYCODE_PERIOD);
 
         private final int mKeyCode;
         MouseKeyEvent(int enumValue) {
@@ -149,22 +145,19 @@
      * Construct a new MouseKeysInterceptor.
      *
      * @param service The service to notify of key events
-     * @param inputManager InputManager to track changes to connected input devices
      * @param looper Looper to use for callbacks and messages
      * @param displayId Display ID to send mouse events to
      */
     @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
-    public MouseKeysInterceptor(AccessibilityManagerService service, InputManager inputManager,
-            Looper looper, int displayId) {
+    public MouseKeysInterceptor(AccessibilityManagerService service, Looper looper, int displayId) {
         mAms = service;
-        mInputManager = inputManager;
         mHandler = new Handler(looper, this);
-        mInputManager.registerInputDeviceListener(this, mHandler);
-        mDisplayId = displayId;
         // Create the virtual mouse on a separate thread since virtual device creation
         // should happen on an auxiliary thread, and not from the handler's thread.
+        // This is because virtual device creation is a blocking operation and can cause a
+        // deadlock if it is called from the handler's thread.
         new Thread(() -> {
-            mVirtualMouse = createVirtualMouse();
+            mVirtualMouse = createVirtualMouse(displayId);
         }).start();
 
     }
@@ -193,22 +186,23 @@
 
     /**
      * Performs a mouse scroll action based on the provided key code.
+     * The scroll action will only be performed if the scroll toggle is on.
      * This method interprets the key code as a mouse scroll and sends
      * the corresponding {@code VirtualMouseScrollEvent#mYAxisMovement}.
 
      * @param keyCode The key code representing the mouse scroll action.
      *                Supported keys are:
      *                <ul>
-     *                  <li>{@link MouseKeysInterceptor.MouseKeyEvent SCROLL_UP}
-     *                  <li>{@link MouseKeysInterceptor.MouseKeyEvent SCROLL_DOWN}
+     *                  <li>{@link MouseKeysInterceptor.MouseKeyEvent#UP_MOVE_OR_SCROLL}
+     *                  <li>{@link MouseKeysInterceptor.MouseKeyEvent#DOWN_MOVE_OR_SCROLL}
      *                </ul>
      */
     @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
     private void performMouseScrollAction(int keyCode) {
         MouseKeyEvent mouseKeyEvent = MouseKeyEvent.from(keyCode);
         float y = switch (mouseKeyEvent) {
-            case SCROLL_UP -> 1.0f;
-            case SCROLL_DOWN -> -1.0f;
+            case UP_MOVE_OR_SCROLL -> 1.0f;
+            case DOWN_MOVE_OR_SCROLL -> -1.0f;
             default -> 0.0f;
         };
         if (mVirtualMouse != null) {
@@ -231,8 +225,8 @@
      * @param keyCode The key code representing the mouse button action.
      *                Supported keys are:
      *                <ul>
-     *                  <li>{@link MouseKeysInterceptor.MouseKeyEvent LEFT_CLICK} (Primary Button)
-     *                  <li>{@link MouseKeysInterceptor.MouseKeyEvent RIGHT_CLICK} (Secondary
+     *                  <li>{@link MouseKeysInterceptor.MouseKeyEvent#LEFT_CLICK} (Primary Button)
+     *                  <li>{@link MouseKeysInterceptor.MouseKeyEvent#RIGHT_CLICK} (Secondary
      *                  Button)
      *                </ul>
      */
@@ -264,17 +258,20 @@
      * The method calculates the relative movement of the mouse pointer
      * and sends the corresponding event to the virtual mouse.
      *
+     * The UP and DOWN pointer actions will only take place for their respective keys
+     * if the scroll toggle is off.
+     *
      * @param keyCode The key code representing the direction or button press.
      *                Supported keys are:
      *                <ul>
-     *                  <li>{@link MouseKeysInterceptor.MouseKeyEvent DIAGONAL_DOWN_LEFT}
-     *                  <li>{@link MouseKeysInterceptor.MouseKeyEvent DOWN}
-     *                  <li>{@link MouseKeysInterceptor.MouseKeyEvent DIAGONAL_DOWN_RIGHT}
-     *                  <li>{@link MouseKeysInterceptor.MouseKeyEvent LEFT}
-     *                  <li>{@link MouseKeysInterceptor.MouseKeyEvent RIGHT}
-     *                  <li>{@link MouseKeysInterceptor.MouseKeyEvent DIAGONAL_UP_LEFT}
-     *                  <li>{@link MouseKeysInterceptor.MouseKeyEvent UP}
-     *                  <li>{@link MouseKeysInterceptor.MouseKeyEvent DIAGONAL_UP_RIGHT}
+     *                  <li>{@link MouseKeysInterceptor.MouseKeyEvent#DIAGONAL_DOWN_LEFT_MOVE}
+     *                  <li>{@link MouseKeysInterceptor.MouseKeyEvent#DOWN_MOVE_OR_SCROLL}
+     *                  <li>{@link MouseKeysInterceptor.MouseKeyEvent#DIAGONAL_DOWN_RIGHT_MOVE}
+     *                  <li>{@link MouseKeysInterceptor.MouseKeyEvent#LEFT_MOVE}
+     *                  <li>{@link MouseKeysInterceptor.MouseKeyEvent#RIGHT_MOVE}
+     *                  <li>{@link MouseKeysInterceptor.MouseKeyEvent#DIAGONAL_UP_LEFT_MOVE}
+     *                  <li>{@link MouseKeysInterceptor.MouseKeyEvent#UP_MOVE_OR_SCROLL}
+     *                  <li>{@link MouseKeysInterceptor.MouseKeyEvent#DIAGONAL_UP_RIGHT_MOVE}
      *                </ul>
      */
     @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
@@ -287,8 +284,10 @@
                 x = -MOUSE_POINTER_MOVEMENT_STEP / sqrt(2);
                 y = MOUSE_POINTER_MOVEMENT_STEP / sqrt(2);
             }
-            case DOWN_MOVE -> {
-                y = MOUSE_POINTER_MOVEMENT_STEP;
+            case DOWN_MOVE_OR_SCROLL -> {
+                if (!mScrollToggleOn) {
+                    y = MOUSE_POINTER_MOVEMENT_STEP;
+                }
             }
             case DIAGONAL_DOWN_RIGHT_MOVE -> {
                 x = MOUSE_POINTER_MOVEMENT_STEP / sqrt(2);
@@ -304,8 +303,10 @@
                 x = -MOUSE_POINTER_MOVEMENT_STEP / sqrt(2);
                 y = -MOUSE_POINTER_MOVEMENT_STEP / sqrt(2);
             }
-            case UP_MOVE -> {
-                y = -MOUSE_POINTER_MOVEMENT_STEP;
+            case UP_MOVE_OR_SCROLL -> {
+                if (!mScrollToggleOn) {
+                    y = -MOUSE_POINTER_MOVEMENT_STEP;
+                }
             }
             case DIAGONAL_UP_RIGHT_MOVE -> {
                 x = MOUSE_POINTER_MOVEMENT_STEP / sqrt(2);
@@ -333,8 +334,8 @@
     }
 
     private boolean isMouseScrollKey(int keyCode) {
-        return keyCode == MouseKeyEvent.SCROLL_UP.getKeyCodeValue()
-                || keyCode == MouseKeyEvent.SCROLL_DOWN.getKeyCodeValue();
+        return keyCode == MouseKeyEvent.UP_MOVE_OR_SCROLL.getKeyCodeValue()
+                || keyCode == MouseKeyEvent.DOWN_MOVE_OR_SCROLL.getKeyCodeValue();
     }
 
     /**
@@ -343,7 +344,7 @@
      * @return The created VirtualMouse.
      */
     @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
-    private VirtualMouse createVirtualMouse() {
+    private VirtualMouse createVirtualMouse(int displayId) {
         final VirtualDeviceManagerInternal localVdm =
                 LocalServices.getService(VirtualDeviceManagerInternal.class);
         mVirtualDevice = localVdm.createVirtualDevice(
@@ -351,7 +352,7 @@
         VirtualMouse virtualMouse = mVirtualDevice.createVirtualMouse(
                 new VirtualMouseConfig.Builder()
                 .setInputDeviceName("Mouse Keys Virtual Mouse")
-                .setAssociatedDisplayId(mDisplayId)
+                .setAssociatedDisplayId(displayId)
                 .build());
         return virtualMouse;
     }
@@ -375,42 +376,56 @@
         if (!isMouseKey(keyCode)) {
             // Pass non-mouse key events to the next handler
             super.onKeyEvent(event, policyFlags);
-        } else if (keyCode == MouseKeyEvent.HOLD.getKeyCodeValue()) {
-            sendVirtualMouseButtonEvent(VirtualMouseButtonEvent.BUTTON_PRIMARY,
-                    VirtualMouseButtonEvent.ACTION_BUTTON_PRESS);
-        } else if (keyCode == MouseKeyEvent.RELEASE.getKeyCodeValue()) {
-            sendVirtualMouseButtonEvent(VirtualMouseButtonEvent.BUTTON_PRIMARY,
-                    VirtualMouseButtonEvent.ACTION_BUTTON_RELEASE);
-        } else if (isDown && isMouseButtonKey(keyCode)) {
-            performMouseButtonAction(keyCode);
-        } else if (isDown && isMouseScrollKey(keyCode)) {
-            // If the scroll key is pressed down and no other key is active,
-            // set it as the active key and send a message to scroll the pointer
-            if (mActiveScrollKey == KEY_NOT_SET) {
-                mActiveScrollKey = keyCode;
-                mLastTimeKeyActionPerformed = event.getDownTime();
-                mHandler.sendEmptyMessage(MESSAGE_SCROLL_MOUSE_POINTER);
-            }
         } else if (isDown) {
-            // This is a directional key.
-            // If the key is pressed down and no other key is active,
-            // set it as the active key and send a message to move the pointer
-            if (mActiveMoveKey == KEY_NOT_SET) {
-                mActiveMoveKey = keyCode;
-                mLastTimeKeyActionPerformed = event.getDownTime();
-                mHandler.sendEmptyMessage(MESSAGE_MOVE_MOUSE_POINTER);
+            if (keyCode == MouseKeyEvent.SCROLL_TOGGLE.getKeyCodeValue()) {
+                mScrollToggleOn = !mScrollToggleOn;
+                if (DEBUG) {
+                    Slog.d(LOG_TAG, "Scroll toggle " + (mScrollToggleOn ? "ON" : "OFF"));
+                }
+            } else if (keyCode == MouseKeyEvent.HOLD.getKeyCodeValue()) {
+                sendVirtualMouseButtonEvent(
+                        VirtualMouseButtonEvent.BUTTON_PRIMARY,
+                        VirtualMouseButtonEvent.ACTION_BUTTON_PRESS
+                );
+            } else if (keyCode == MouseKeyEvent.RELEASE.getKeyCodeValue()) {
+                sendVirtualMouseButtonEvent(
+                        VirtualMouseButtonEvent.BUTTON_PRIMARY,
+                        VirtualMouseButtonEvent.ACTION_BUTTON_RELEASE
+                );
+            } else if (isMouseButtonKey(keyCode)) {
+                performMouseButtonAction(keyCode);
+            } else if (mScrollToggleOn && isMouseScrollKey(keyCode)) {
+                // If the scroll key is pressed down and no other key is active,
+                // set it as the active key and send a message to scroll the pointer
+                if (mActiveScrollKey == KEY_NOT_SET) {
+                    mActiveScrollKey = keyCode;
+                    mLastTimeKeyActionPerformed = event.getDownTime();
+                    mHandler.sendEmptyMessage(MESSAGE_SCROLL_MOUSE_POINTER);
+                }
+            } else {
+                // This is a directional key.
+                // If the key is pressed down and no other key is active,
+                // set it as the active key and send a message to move the pointer
+                if (mActiveMoveKey == KEY_NOT_SET) {
+                    mActiveMoveKey = keyCode;
+                    mLastTimeKeyActionPerformed = event.getDownTime();
+                    mHandler.sendEmptyMessage(MESSAGE_MOVE_MOUSE_POINTER);
+                }
             }
-        } else if (mActiveMoveKey == keyCode) {
-            // If the key is released, and it is the active key, stop moving the pointer
-            mActiveMoveKey = KEY_NOT_SET;
-            mHandler.removeMessages(MESSAGE_MOVE_MOUSE_POINTER);
-        } else if (mActiveScrollKey == keyCode) {
-            // If the key is released, and it is the active key, stop scrolling the pointer
-            mActiveScrollKey = KEY_NOT_SET;
-            mHandler.removeMessages(MESSAGE_SCROLL_MOUSE_POINTER);
         } else {
-            Slog.i(LOG_TAG, "Dropping event with key code: '" + keyCode
-                    + "', with no matching down event from deviceId = " + event.getDeviceId());
+            // Up event received
+            if (mActiveMoveKey == keyCode) {
+                // If the key is released, and it is the active key, stop moving the pointer
+                mActiveMoveKey = KEY_NOT_SET;
+                mHandler.removeMessages(MESSAGE_MOVE_MOUSE_POINTER);
+            } else if (mActiveScrollKey == keyCode) {
+                // If the key is released, and it is the active key, stop scrolling the pointer
+                mActiveScrollKey = KEY_NOT_SET;
+                mHandler.removeMessages(MESSAGE_SCROLL_MOUSE_POINTER);
+            } else {
+                Slog.i(LOG_TAG, "Dropping event with key code: '" + keyCode
+                        + "', with no matching down event from deviceId = " + event.getDeviceId());
+            }
         }
     }
 
@@ -470,14 +485,6 @@
         }
     }
 
-    @Override
-    public void onInputDeviceAdded(int deviceId) {
-    }
-
-    @Override
-    public void onInputDeviceRemoved(int deviceId) {
-    }
-
     @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
     @Override
     public void onDestroy() {
@@ -485,14 +492,8 @@
         mActiveMoveKey = KEY_NOT_SET;
         mActiveScrollKey = KEY_NOT_SET;
         mLastTimeKeyActionPerformed = 0;
+
         mHandler.removeCallbacksAndMessages(null);
-
         mVirtualDevice.close();
-        mInputManager.unregisterInputDeviceListener(this);
     }
-
-    @Override
-    public void onInputDeviceChanged(int deviceId) {
-    }
-
 }
diff --git a/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java b/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java
index e3d5c54..803b125 100644
--- a/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java
+++ b/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java
@@ -17,6 +17,7 @@
 package com.android.server.media.projection;
 
 import static android.Manifest.permission.MANAGE_MEDIA_PROJECTION;
+import static android.Manifest.permission.RECORD_SENSITIVE_CONTENT;
 import static android.app.ActivityManagerInternal.MEDIA_PROJECTION_TOKEN_EVENT_CREATED;
 import static android.app.ActivityManagerInternal.MEDIA_PROJECTION_TOKEN_EVENT_DESTROYED;
 import static android.content.Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS;
@@ -34,10 +35,12 @@
 import android.annotation.EnforcePermission;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.annotation.RequiresPermission;
 import android.app.ActivityManagerInternal;
 import android.app.ActivityOptions.LaunchCookie;
 import android.app.AppOpsManager;
 import android.app.IProcessObserver;
+import android.app.KeyguardManager;
 import android.app.compat.CompatChanges;
 import android.compat.annotation.ChangeId;
 import android.compat.annotation.EnabledSince;
@@ -78,6 +81,7 @@
 import com.android.internal.util.ArrayUtils;
 import com.android.internal.util.DumpUtils;
 import com.android.server.LocalServices;
+import com.android.server.SystemConfig;
 import com.android.server.SystemService;
 import com.android.server.Watchdog;
 import com.android.server.wm.WindowManagerInternal;
@@ -132,6 +136,7 @@
     private final ActivityManagerInternal mActivityManagerInternal;
     private final PackageManager mPackageManager;
     private final WindowManagerInternal mWmInternal;
+    private final KeyguardManager mKeyguardManager;
 
     private final MediaRouter mMediaRouter;
     private final MediaRouterCallback mMediaRouterCallback;
@@ -147,7 +152,9 @@
         this(context, new Injector());
     }
 
-    @VisibleForTesting MediaProjectionManagerService(Context context, Injector injector) {
+    @RequiresPermission(Manifest.permission.SUBSCRIBE_TO_KEYGUARD_LOCKED_STATE)
+    @VisibleForTesting
+    MediaProjectionManagerService(Context context, Injector injector) {
         super(context);
         mContext = context;
         mInjector = injector;
@@ -163,9 +170,47 @@
         mMediaRouter = (MediaRouter) mContext.getSystemService(Context.MEDIA_ROUTER_SERVICE);
         mMediaRouterCallback = new MediaRouterCallback();
         mMediaProjectionMetricsLogger = injector.mediaProjectionMetricsLogger(context);
+        mKeyguardManager = (KeyguardManager) mContext.getSystemService(Context.KEYGUARD_SERVICE);
+        mKeyguardManager.addKeyguardLockedStateListener(
+                mContext.getMainExecutor(), this::onKeyguardLockedStateChanged);
         Watchdog.getInstance().addMonitor(this);
     }
 
+    /**
+     * In order to record the keyguard, the MediaProjection package must be either:
+     *   - a holder of RECORD_SENSITIVE_CONTENT permission, or
+     *   - be one of the bugreport whitelisted packages
+     */
+    private boolean canCaptureKeyguard() {
+        if (!android.companion.virtualdevice.flags.Flags.mediaProjectionKeyguardRestrictions()) {
+            return true;
+        }
+        synchronized (mLock) {
+            if (mProjectionGrant == null || mProjectionGrant.packageName == null) {
+                return false;
+            }
+            if (mPackageManager.checkPermission(RECORD_SENSITIVE_CONTENT,
+                    mProjectionGrant.packageName)
+                    == PackageManager.PERMISSION_GRANTED) {
+                return true;
+            }
+            return SystemConfig.getInstance().getBugreportWhitelistedPackages()
+                    .contains(mProjectionGrant.packageName);
+        }
+    }
+
+    @VisibleForTesting
+    void onKeyguardLockedStateChanged(boolean isKeyguardLocked) {
+        if (!isKeyguardLocked) return;
+        synchronized (mLock) {
+            if (mProjectionGrant != null && !canCaptureKeyguard()) {
+                Slog.d(TAG, "Content Recording: Stopped MediaProjection"
+                        + " due to keyguard lock");
+                mProjectionGrant.stop();
+            }
+        }
+    }
+
     /** Functional interface for providing time. */
     @VisibleForTesting
     interface Clock {
@@ -1252,6 +1297,11 @@
         @Override
         public void notifyVirtualDisplayCreated(int displayId) {
             notifyVirtualDisplayCreated_enforcePermission();
+            if (mKeyguardManager.isKeyguardLocked() && !canCaptureKeyguard()) {
+                Slog.w(TAG, "Content Recording: Keyguard locked, aborting MediaProjection");
+                stop();
+                return;
+            }
             synchronized (mLock) {
                 mVirtualDisplayId = displayId;
 
diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java
index b9a9d64f..2c233f8 100644
--- a/services/core/java/com/android/server/pm/UserManagerService.java
+++ b/services/core/java/com/android/server/pm/UserManagerService.java
@@ -1901,6 +1901,7 @@
                 Slog.i(LOG_TAG, "Quiet mode is already " + enableQuietMode);
                 return;
             }
+            UserManager.invalidateQuietModeEnabledCache();
             profile.flags ^= UserInfo.FLAG_QUIET_MODE;
             profileUserData = getUserDataLU(profile.id);
         }
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index 5e644d3..834c17e 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -232,6 +232,7 @@
 import static com.android.server.wm.ActivityTaskManagerService.RELAUNCH_REASON_NONE;
 import static com.android.server.wm.ActivityTaskManagerService.RELAUNCH_REASON_WINDOWING_MODE_RESIZE;
 import static com.android.server.wm.ActivityTaskManagerService.getInputDispatchingTimeoutMillisLocked;
+import static com.android.server.wm.DesktopModeLaunchParamsModifier.canEnterDesktopMode;
 import static com.android.server.wm.IdentifierProto.HASH_CODE;
 import static com.android.server.wm.IdentifierProto.TITLE;
 import static com.android.server.wm.IdentifierProto.USER_ID;
@@ -9283,18 +9284,24 @@
     }
 
     void updateSizeCompatScale(Rect resolvedAppBounds, Rect containerAppBounds) {
-        // Only allow to scale down.
         mSizeCompatScale = mAppCompatController.getTransparentPolicy()
                 .findOpaqueNotFinishingActivityBelow()
                 .map(activityRecord -> activityRecord.mSizeCompatScale)
-                .orElseGet(() -> {
-                    final int contentW = resolvedAppBounds.width();
-                    final int contentH = resolvedAppBounds.height();
-                    final int viewportW = containerAppBounds.width();
-                    final int viewportH = containerAppBounds.height();
-                    return (contentW <= viewportW && contentH <= viewportH) ? 1f : Math.min(
-                            (float) viewportW / contentW, (float) viewportH / contentH);
-                });
+                .orElseGet(() -> calculateSizeCompatScale(resolvedAppBounds, containerAppBounds));
+    }
+
+    private float calculateSizeCompatScale(Rect resolvedAppBounds, Rect containerAppBounds) {
+        final int contentW = resolvedAppBounds.width();
+        final int contentH = resolvedAppBounds.height();
+        final int viewportW = containerAppBounds.width();
+        final int viewportH = containerAppBounds.height();
+        // Allow an application to be up-scaled if its window is smaller than its
+        // original container or if it's a freeform window in desktop mode.
+        boolean shouldAllowUpscaling = !(contentW <= viewportW && contentH <= viewportH)
+                || (canEnterDesktopMode(mAtmService.mContext)
+                    && getWindowingMode() == WINDOWING_MODE_FREEFORM);
+        return shouldAllowUpscaling ? Math.min(
+                (float) viewportW / contentW, (float) viewportH / contentH) : 1f;
     }
 
     private boolean isInSizeCompatModeForBounds(final Rect appBounds, final Rect containerBounds) {
diff --git a/services/core/java/com/android/server/wm/Transition.java b/services/core/java/com/android/server/wm/Transition.java
index 47af6fc..2a3e945 100644
--- a/services/core/java/com/android/server/wm/Transition.java
+++ b/services/core/java/com/android/server/wm/Transition.java
@@ -2757,12 +2757,19 @@
             return out;
         }
 
+        // Get the animation theme from the top-most application window
+        // when Flags.customAnimationsBehindTranslucent() is false.
         final AnimationOptions animOptionsForActivityTransition =
                 calculateAnimationOptionsForActivityTransition(type, sortedTargets);
+
         if (!Flags.moveAnimationOptionsToChange() && animOptionsForActivityTransition != null) {
             out.setAnimationOptions(animOptionsForActivityTransition);
         }
 
+        // Store the animation options of the topmost non-translucent change
+        // (Used when Flags.customAnimationsBehindTranslucent() is true)
+        AnimationOptions activityAboveAnimationOptions = null;
+
         final ArraySet<WindowContainer> occludedAtEndContainers = new ArraySet<>();
         // Convert all the resolved ChangeInfos into TransactionInfo.Change objects in order.
         final int count = sortedTargets.size();
@@ -2881,9 +2888,26 @@
                 change.setBackgroundColor(ColorUtils.setAlphaComponent(backgroundColor, 255));
             }
 
-            AnimationOptions animOptions = null;
+            // Calculate the animation options for this change
             if (Flags.moveAnimationOptionsToChange()) {
-                if (activityRecord != null && animOptionsForActivityTransition != null) {
+                AnimationOptions animOptions = null;
+                if (Flags.customAnimationsBehindTranslucent() && activityRecord != null) {
+                    if (activityAboveAnimationOptions != null) {
+                        // Inherit the options from one of the changes on top of this
+                        animOptions = activityAboveAnimationOptions;
+                    } else {
+                        // Create the options based on this change's custom animations and layout
+                        // parameters
+                        animOptions = getOptions(activityRecord /* customAnimActivity */,
+                                                 activityRecord /* animLpActivity */);
+                        if (!change.hasFlags(FLAG_TRANSLUCENT)) {
+                            // If this change is not translucent, its options are going to be
+                            // inherited by the changes below
+                            activityAboveAnimationOptions = animOptions;
+                        }
+                    }
+                } else if (activityRecord != null && animOptionsForActivityTransition != null) {
+                    // Use the same options from the top activity for all the activities
                     animOptions = animOptionsForActivityTransition;
                 } else if (Flags.activityEmbeddingOverlayPresentationFlag()
                         && isEmbeddedTaskFragment) {
@@ -2931,25 +2955,42 @@
     @Nullable
     private static AnimationOptions calculateAnimationOptionsForActivityTransition(
             @TransitionType int type, @NonNull ArrayList<ChangeInfo> sortedTargets) {
-        TransitionInfo.AnimationOptions animOptions = null;
-
-        // Check if the top-most app is an activity (ie. activity->activity). If so, make sure
-        // to honor its custom transition options.
         WindowContainer<?> topApp = null;
         for (int i = 0; i < sortedTargets.size(); i++) {
-            if (isWallpaper(sortedTargets.get(i).mContainer)) continue;
-            topApp = sortedTargets.get(i).mContainer;
-            break;
+            if (!isWallpaper(sortedTargets.get(i).mContainer)) {
+                topApp = sortedTargets.get(i).mContainer;
+                break;
+            }
         }
-        if (topApp.asActivityRecord() != null) {
-            final ActivityRecord topActivity = topApp.asActivityRecord();
-            animOptions = addCustomActivityTransition(topActivity, true/* open */,
-                    null /* animOptions */);
-            animOptions = addCustomActivityTransition(topActivity, false/* open */,
+        ActivityRecord animLpActivity = findAnimLayoutParamsActivityRecord(type, sortedTargets);
+        return getOptions(topApp.asActivityRecord() /* customAnimActivity */,
+                animLpActivity /* animLpActivity */);
+    }
+
+    /**
+     * Updates and returns animOptions with the layout parameters of animLpActivity
+     * @param customAnimActivity the activity that drives the custom animation options
+     * @param animLpActivity the activity that drives the animation options with its layout
+     *                       parameters
+     * @return the options extracted from the provided activities
+     */
+    @Nullable
+    private static AnimationOptions getOptions(@Nullable ActivityRecord customAnimActivity,
+            @Nullable ActivityRecord animLpActivity) {
+        AnimationOptions animOptions = null;
+        // Custom
+        if (customAnimActivity != null) {
+            animOptions = addCustomActivityTransition(customAnimActivity, true /* open */,
+                    animOptions);
+            animOptions = addCustomActivityTransition(customAnimActivity, false /* open */,
                     animOptions);
         }
-        final WindowManager.LayoutParams animLp =
-                getLayoutParamsForAnimationsStyle(type, sortedTargets);
+
+        // Layout parameters
+        final WindowState mainWindow = animLpActivity != null
+                ? animLpActivity.findMainWindow() : null;
+        final WindowManager.LayoutParams animLp = mainWindow != null ? mainWindow.mAttrs : null;
+
         if (animLp != null && animLp.type != TYPE_APPLICATION_STARTING
                 && animLp.windowAnimations != 0) {
             // Don't send animation options if no windowAnimations have been set or if the we
@@ -3087,10 +3128,9 @@
         return ancestor;
     }
 
-    private static WindowManager.LayoutParams getLayoutParamsForAnimationsStyle(int type,
-            ArrayList<ChangeInfo> sortedTargets) {
-        // Find the layout params of the top-most application window that is part of the
-        // transition, which is what will control the animation theme.
+    @Nullable
+    private static ActivityRecord findAnimLayoutParamsActivityRecord(
+            @TransitionType int transit, @NonNull List<ChangeInfo> sortedTargets) {
         final ArraySet<Integer> activityTypes = new ArraySet<>();
         final int targetCount = sortedTargets.size();
         for (int i = 0; i < targetCount; ++i) {
@@ -3110,16 +3150,7 @@
             // activity through the layout parameter animation style.
             return null;
         }
-        final ActivityRecord animLpActivity =
-                findAnimLayoutParamsActivityRecord(sortedTargets, type, activityTypes);
-        final WindowState mainWindow = animLpActivity != null
-                ? animLpActivity.findMainWindow() : null;
-        return mainWindow != null ? mainWindow.mAttrs : null;
-    }
 
-    private static ActivityRecord findAnimLayoutParamsActivityRecord(
-            List<ChangeInfo> sortedTargets,
-            @TransitionType int transit, ArraySet<Integer> activityTypes) {
         // Remote animations always win, but fullscreen windows override non-fullscreen windows.
         ActivityRecord result = lookForTopWindowWithFilter(sortedTargets,
                 w -> w.getRemoteAnimationDefinition() != null
diff --git a/services/core/jni/com_android_server_input_InputManagerService.cpp b/services/core/jni/com_android_server_input_InputManagerService.cpp
index 1f0c827..eab7364 100644
--- a/services/core/jni/com_android_server_input_InputManagerService.cpp
+++ b/services/core/jni/com_android_server_input_InputManagerService.cpp
@@ -382,6 +382,7 @@
             PointerControllerInterface::ControllerType type) override;
     void notifyPointerDisplayIdChanged(ui::LogicalDisplayId displayId,
                                        const FloatPoint& position) override;
+    void notifyMouseCursorFadedOnTyping() override;
 
     /* --- InputFilterPolicyInterface implementation --- */
     void notifyStickyModifierStateChanged(uint32_t modifierState,
@@ -788,6 +789,10 @@
             InputReaderConfiguration::Change::DISPLAY_INFO);
 }
 
+void NativeInputManager::notifyMouseCursorFadedOnTyping() {
+    mInputManager->getReader().notifyMouseCursorFadedOnTyping();
+}
+
 void NativeInputManager::notifyStickyModifierStateChanged(uint32_t modifierState,
                                                           uint32_t lockedModifierState) {
     JNIEnv* env = jniEnv();
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/MouseKeysInterceptorTest.kt b/services/tests/servicestests/src/com/android/server/accessibility/MouseKeysInterceptorTest.kt
index dc8d239..0def516 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/MouseKeysInterceptorTest.kt
+++ b/services/tests/servicestests/src/com/android/server/accessibility/MouseKeysInterceptorTest.kt
@@ -16,6 +16,8 @@
 
 package com.android.server.accessibility
 
+import android.util.MathUtils.sqrt
+
 import android.companion.virtual.VirtualDeviceManager
 import android.companion.virtual.VirtualDeviceParams
 import android.content.Context
@@ -59,6 +61,7 @@
     companion object {
         const val DISPLAY_ID = 1
         const val DEVICE_ID = 123
+        const val MOUSE_POINTER_MOVEMENT_STEP = 1.8f
         // This delay is required for key events to be sent and handled correctly.
         // The handler only performs a move/scroll event if it receives the key event
         // at INTERVAL_MILLIS (which happens in practice). Hence, we need this delay in the tests.
@@ -113,8 +116,7 @@
         Mockito.`when`(iInputManager.inputDeviceIds).thenReturn(intArrayOf(DEVICE_ID))
         Mockito.`when`(mockAms.traceManager).thenReturn(mockTraceManager)
 
-        mouseKeysInterceptor = MouseKeysInterceptor(mockAms, mockInputManager,
-            testLooper.looper, DISPLAY_ID)
+        mouseKeysInterceptor = MouseKeysInterceptor(mockAms, testLooper.looper, DISPLAY_ID)
         // VirtualMouse is created on a separate thread.
         // Wait for VirtualMouse to be created before running tests
         TimeUnit.MILLISECONDS.sleep(20L)
@@ -145,7 +147,7 @@
     fun whenMouseDirectionalKeyIsPressed_relativeEventIsSent() {
         // There should be some delay between the downTime of the key event and calling onKeyEvent
         val downTime = clock.now() - KEYBOARD_POST_EVENT_DELAY_MILLIS
-        val keyCode = MouseKeysInterceptor.MouseKeyEvent.DOWN_MOVE.getKeyCodeValue()
+        val keyCode = MouseKeysInterceptor.MouseKeyEvent.DIAGONAL_DOWN_LEFT_MOVE.keyCodeValue
         val downEvent = KeyEvent(downTime, downTime, KeyEvent.ACTION_DOWN,
             keyCode, 0, 0, DEVICE_ID, 0)
 
@@ -153,14 +155,15 @@
         testLooper.dispatchAll()
 
         // Verify the sendRelativeEvent method is called once and capture the arguments
-        verifyRelativeEvents(arrayOf<Float>(0f), arrayOf<Float>(1.8f))
+        verifyRelativeEvents(arrayOf(-MOUSE_POINTER_MOVEMENT_STEP / sqrt(2.0f)),
+            arrayOf(MOUSE_POINTER_MOVEMENT_STEP / sqrt(2.0f)))
     }
 
     @Test
     fun whenClickKeyIsPressed_buttonEventIsSent() {
         // There should be some delay between the downTime of the key event and calling onKeyEvent
         val downTime = clock.now() - KEYBOARD_POST_EVENT_DELAY_MILLIS
-        val keyCode = MouseKeysInterceptor.MouseKeyEvent.LEFT_CLICK.getKeyCodeValue()
+        val keyCode = MouseKeysInterceptor.MouseKeyEvent.LEFT_CLICK.keyCodeValue
         val downEvent = KeyEvent(downTime, downTime, KeyEvent.ACTION_DOWN,
             keyCode, 0, 0, DEVICE_ID, 0)
         mouseKeysInterceptor.onKeyEvent(downEvent, 0)
@@ -179,7 +182,7 @@
     @Test
     fun whenHoldKeyIsPressed_buttonEventIsSent() {
         val downTime = clock.now() - KEYBOARD_POST_EVENT_DELAY_MILLIS
-        val keyCode = MouseKeysInterceptor.MouseKeyEvent.HOLD.getKeyCodeValue()
+        val keyCode = MouseKeysInterceptor.MouseKeyEvent.HOLD.keyCodeValue
         val downEvent = KeyEvent(downTime, downTime, KeyEvent.ACTION_DOWN,
             keyCode, 0, 0, DEVICE_ID, 0)
         mouseKeysInterceptor.onKeyEvent(downEvent, 0)
@@ -195,7 +198,7 @@
     @Test
     fun whenReleaseKeyIsPressed_buttonEventIsSent() {
         val downTime = clock.now() - KEYBOARD_POST_EVENT_DELAY_MILLIS
-        val keyCode = MouseKeysInterceptor.MouseKeyEvent.RELEASE.getKeyCodeValue()
+        val keyCode = MouseKeysInterceptor.MouseKeyEvent.RELEASE.keyCodeValue
         val downEvent = KeyEvent(downTime, downTime, KeyEvent.ACTION_DOWN,
             keyCode, 0, 0, DEVICE_ID, 0)
         mouseKeysInterceptor.onKeyEvent(downEvent, 0)
@@ -209,18 +212,38 @@
     }
 
     @Test
-    fun whenScrollUpKeyIsPressed_scrollEventIsSent() {
+    fun whenScrollToggleOn_ScrollUpKeyIsPressed_scrollEventIsSent() {
         // There should be some delay between the downTime of the key event and calling onKeyEvent
         val downTime = clock.now() - KEYBOARD_POST_EVENT_DELAY_MILLIS
-        val keyCode = MouseKeysInterceptor.MouseKeyEvent.SCROLL_UP.getKeyCodeValue()
+        val keyCodeScrollToggle = MouseKeysInterceptor.MouseKeyEvent.SCROLL_TOGGLE.keyCodeValue
+        val keyCodeScroll = MouseKeysInterceptor.MouseKeyEvent.UP_MOVE_OR_SCROLL.keyCodeValue
+
+        val scrollToggleDownEvent = KeyEvent(downTime, downTime, KeyEvent.ACTION_DOWN,
+            keyCodeScrollToggle, 0, 0, DEVICE_ID, 0)
+        val scrollDownEvent = KeyEvent(downTime, downTime, KeyEvent.ACTION_DOWN,
+            keyCodeScroll, 0, 0, DEVICE_ID, 0)
+
+        mouseKeysInterceptor.onKeyEvent(scrollToggleDownEvent, 0)
+        mouseKeysInterceptor.onKeyEvent(scrollDownEvent, 0)
+        testLooper.dispatchAll()
+
+        // Verify the sendScrollEvent method is called once and capture the arguments
+        verifyScrollEvents(arrayOf<Float>(0f), arrayOf<Float>(1.0f))
+    }
+
+    @Test
+    fun whenScrollToggleOff_DirectionalUpKeyIsPressed_RelativeEventIsSent() {
+        // There should be some delay between the downTime of the key event and calling onKeyEvent
+        val downTime = clock.now() - KEYBOARD_POST_EVENT_DELAY_MILLIS
+        val keyCode = MouseKeysInterceptor.MouseKeyEvent.UP_MOVE_OR_SCROLL.keyCodeValue
         val downEvent = KeyEvent(downTime, downTime, KeyEvent.ACTION_DOWN,
             keyCode, 0, 0, DEVICE_ID, 0)
 
         mouseKeysInterceptor.onKeyEvent(downEvent, 0)
         testLooper.dispatchAll()
 
-        // Verify the sendScrollEvent method is called once and capture the arguments
-        verifyScrollEvents(arrayOf<Float>(0f), arrayOf<Float>(1.0f))
+        // Verify the sendRelativeEvent method is called once and capture the arguments
+        verifyRelativeEvents(arrayOf<Float>(0f), arrayOf<Float>(-MOUSE_POINTER_MOVEMENT_STEP))
     }
 
     private fun verifyRelativeEvents(expectedX: Array<Float>, expectedY: Array<Float>) {
diff --git a/services/tests/servicestests/src/com/android/server/media/projection/MediaProjectionManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/media/projection/MediaProjectionManagerServiceTest.java
index e64397d..316b5fa 100644
--- a/services/tests/servicestests/src/com/android/server/media/projection/MediaProjectionManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/media/projection/MediaProjectionManagerServiceTest.java
@@ -17,6 +17,7 @@
 package com.android.server.media.projection;
 
 
+import static android.Manifest.permission.RECORD_SENSITIVE_CONTENT;
 import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
 import static android.content.pm.ApplicationInfo.PRIVATE_FLAG_PRIVILEGED;
 import static android.media.projection.MediaProjectionManager.TYPE_MIRRORING;
@@ -50,6 +51,7 @@
 
 import android.app.ActivityManagerInternal;
 import android.app.ActivityOptions.LaunchCookie;
+import android.app.KeyguardManager;
 import android.content.Context;
 import android.content.ContextWrapper;
 import android.content.pm.ApplicationInfo;
@@ -66,7 +68,9 @@
 import android.os.RemoteException;
 import android.os.UserHandle;
 import android.os.test.TestLooper;
+import android.platform.test.annotations.EnableFlags;
 import android.platform.test.annotations.Presubmit;
+import android.platform.test.flag.junit.SetFlagsRule;
 import android.view.ContentRecordingSession;
 import android.view.ContentRecordingSession.RecordContent;
 
@@ -81,6 +85,7 @@
 
 import org.junit.After;
 import org.junit.Before;
+import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.ArgumentCaptor;
@@ -151,6 +156,9 @@
     private ContentRecordingSession mWaitingDisplaySession =
             createDisplaySession(DEFAULT_DISPLAY);
 
+    @Rule
+    public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
+
     @Mock
     private ActivityManagerInternal mAmInternal;
     @Mock
@@ -158,6 +166,8 @@
     @Mock
     private PackageManager mPackageManager;
     @Mock
+    private KeyguardManager mKeyguardManager;
+    @Mock
     private IMediaProjectionWatcherCallback mWatcherCallback;
     @Mock
     private MediaProjectionMetricsLogger mMediaProjectionMetricsLogger;
@@ -177,6 +187,7 @@
         mContext = spy(new ContextWrapper(
                 InstrumentationRegistry.getInstrumentation().getTargetContext()));
         doReturn(mPackageManager).when(mContext).getPackageManager();
+        doReturn(mKeyguardManager).when(mContext).getSystemService(eq(Context.KEYGUARD_SERVICE));
 
         mClock = new OffsettableClock.Stopped();
         mWaitingDisplaySession.setWaitingForConsent(true);
@@ -246,6 +257,39 @@
         assertThat(stoppedCallback2).isFalse();
     }
 
+    @EnableFlags(android.companion.virtualdevice.flags
+            .Flags.FLAG_MEDIA_PROJECTION_KEYGUARD_RESTRICTIONS)
+    @Test
+    public void testCreateProjection_keyguardLocked() throws Exception {
+        MediaProjectionManagerService.MediaProjection projection = startProjectionPreconditions();
+
+        doReturn(true).when(mKeyguardManager).isKeyguardLocked();
+        doReturn(PackageManager.PERMISSION_DENIED).when(mPackageManager)
+                .checkPermission(RECORD_SENSITIVE_CONTENT, projection.packageName);
+        projection.start(mIMediaProjectionCallback);
+        projection.notifyVirtualDisplayCreated(10);
+
+        assertThat(mService.getActiveProjectionInfo()).isNull();
+        assertThat(mIMediaProjectionCallback.mLatch.await(5, TimeUnit.SECONDS)).isTrue();
+    }
+
+    @EnableFlags(android.companion.virtualdevice.flags
+            .Flags.FLAG_MEDIA_PROJECTION_KEYGUARD_RESTRICTIONS)
+    @Test
+    public void testCreateProjection_keyguardLocked_packageAllowlisted()
+            throws NameNotFoundException {
+        MediaProjectionManagerService.MediaProjection projection = startProjectionPreconditions();
+
+        doReturn(true).when(mKeyguardManager).isKeyguardLocked();
+        doReturn(PackageManager.PERMISSION_GRANTED).when(mPackageManager)
+                .checkPermission(RECORD_SENSITIVE_CONTENT, projection.packageName);
+        projection.start(mIMediaProjectionCallback);
+        projection.notifyVirtualDisplayCreated(10);
+
+        // The projection was started because it was allowed to capture the keyguard.
+        assertThat(mService.getActiveProjectionInfo()).isNotNull();
+    }
+
     @Test
     public void testCreateProjection_attemptReuse_noPriorProjectionGrant()
             throws NameNotFoundException {
@@ -317,6 +361,48 @@
         assertThat(secondProjection).isNotEqualTo(projection);
     }
 
+    @EnableFlags(android.companion.virtualdevice.flags
+            .Flags.FLAG_MEDIA_PROJECTION_KEYGUARD_RESTRICTIONS)
+    @Test
+    public void testKeyguardLocked_stopsActiveProjection() throws Exception {
+        MediaProjectionManagerService service =
+                new MediaProjectionManagerService(mContext, mMediaProjectionMetricsLoggerInjector);
+        MediaProjectionManagerService.MediaProjection projection =
+                startProjectionPreconditions(service);
+        projection.start(mIMediaProjectionCallback);
+
+        assertThat(service.getActiveProjectionInfo()).isNotNull();
+
+        doReturn(PackageManager.PERMISSION_DENIED).when(mPackageManager)
+                .checkPermission(RECORD_SENSITIVE_CONTENT, projection.packageName);
+        service.onKeyguardLockedStateChanged(true);
+
+        verify(mMediaProjectionMetricsLogger).logStopped(UID, TARGET_UID_UNKNOWN);
+        assertThat(service.getActiveProjectionInfo()).isNull();
+        assertThat(mIMediaProjectionCallback.mLatch.await(5, TimeUnit.SECONDS)).isTrue();
+    }
+
+    @EnableFlags(android.companion.virtualdevice.flags
+            .Flags.FLAG_MEDIA_PROJECTION_KEYGUARD_RESTRICTIONS)
+    @Test
+    public void testKeyguardLocked_packageAllowlisted_doesNotStopActiveProjection()
+            throws NameNotFoundException {
+        MediaProjectionManagerService service =
+                new MediaProjectionManagerService(mContext, mMediaProjectionMetricsLoggerInjector);
+        MediaProjectionManagerService.MediaProjection projection =
+                startProjectionPreconditions(service);
+        projection.start(mIMediaProjectionCallback);
+
+        assertThat(service.getActiveProjectionInfo()).isNotNull();
+
+        doReturn(PackageManager.PERMISSION_GRANTED).when(mPackageManager).checkPermission(
+                RECORD_SENSITIVE_CONTENT, projection.packageName);
+        service.onKeyguardLockedStateChanged(true);
+
+        verifyZeroInteractions(mMediaProjectionMetricsLogger);
+        assertThat(service.getActiveProjectionInfo()).isNotNull();
+    }
+
     @Test
     public void stop_noActiveProjections_doesNotLog() throws Exception {
         MediaProjectionManagerService service =
diff --git a/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java b/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java
index 9003ab6..d714db99 100644
--- a/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java
@@ -1862,6 +1862,25 @@
         assertThat(profilesExcludingHidden).asList().doesNotContain(profile.id);
     }
 
+    /**
+     * Test that UserManager.isQuietModeEnabled return false for unsupported
+     * arguments such as UserHandle.NULL, UserHandle.CURRENT or UserHandle.ALL.
+     **/
+    @MediumTest
+    @Test
+    public void testQuietModeEnabledForUnsupportedUserHandles() throws Exception {
+        assumeManagedUsersSupported();
+        final int mainUserId = mUserManager.getMainUser().getIdentifier();
+        UserInfo userInfo = createProfileForUser("Profile",
+                UserManager.USER_TYPE_PROFILE_MANAGED, mainUserId);
+        mUserManager.requestQuietModeEnabled(true, userInfo.getUserHandle());
+        assertThat(mUserManager.isQuietModeEnabled(userInfo.getUserHandle())).isTrue();
+        assertThat(mUserManager.isQuietModeEnabled(UserHandle.of(UserHandle.USER_NULL))).isFalse();
+        assertThat(mUserManager.isQuietModeEnabled(UserHandle.CURRENT)).isFalse();
+        assertThat(mUserManager.isQuietModeEnabled(UserHandle.CURRENT_OR_SELF)).isFalse();
+        assertThat(mUserManager.isQuietModeEnabled(UserHandle.ALL)).isFalse();
+    }
+
     private String generateLongString() {
         String partialString = "Test Name Test Name Test Name Test Name Test Name Test Name Test "
                 + "Name Test Name Test Name Test Name "; //String of length 100
diff --git a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
index 5fe8524..ae88b1b 100644
--- a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
@@ -104,6 +104,7 @@
 import android.os.Binder;
 import android.os.RemoteException;
 import android.os.UserHandle;
+import android.platform.test.annotations.DisableFlags;
 import android.platform.test.annotations.EnableFlags;
 import android.platform.test.annotations.Presubmit;
 import android.provider.DeviceConfig;
@@ -495,7 +496,7 @@
         // Activity is sandboxed; it is in size compat mode since it is not resizable and has a
         // max aspect ratio.
         assertActivityMaxBoundsSandboxed();
-        assertScaled();
+        assertDownScaled();
     }
 
     @Test
@@ -515,7 +516,7 @@
         // The bounds should be [100, 0 - 1100, 2500].
         assertEquals(origBounds.width(), currentBounds.width());
         assertEquals(origBounds.height(), currentBounds.height());
-        assertScaled();
+        assertDownScaled();
 
         // The scale is 2000/2500=0.8. The horizontal centered offset is (1000-(1000*0.8))/2=100.
         final float scale = (float) display.mBaseDisplayHeight / currentBounds.height();
@@ -555,7 +556,7 @@
         assertEquals(origBounds.width(), currentBounds.width());
         assertEquals(origBounds.height(), currentBounds.height());
         assertEquals(offsetX, mActivity.getBounds().left);
-        assertScaled();
+        assertDownScaled();
         // Activity is sandboxed due to size compat mode.
         assertActivityMaxBoundsSandboxed();
 
@@ -693,7 +694,7 @@
         // The configuration bounds [820, 0 - 1820, 2500] should keep the same.
         assertEquals(originalBounds.width(), currentBounds.width());
         assertEquals(originalBounds.height(), currentBounds.height());
-        assertScaled();
+        assertDownScaled();
         // Activity max bounds are sandboxed due to size compat mode on the new display.
         assertActivityMaxBoundsSandboxed();
 
@@ -752,7 +753,7 @@
         assertEquals(origAppBounds.width(), appBounds.width());
         assertEquals(origAppBounds.height(), appBounds.height());
         // The activity is 1000x1400 and the display is 2500x1000.
-        assertScaled();
+        assertDownScaled();
         final float scale = mActivity.getCompatScale();
         // The position in configuration should be in app coordinates.
         final Rect screenBounds = mActivity.getBounds();
@@ -849,7 +850,7 @@
         // Size compatibility mode is able to handle orientation change so the process shouldn't be
         // restarted and the override configuration won't be cleared.
         verify(mActivity, never()).restartProcessIfVisible();
-        assertScaled();
+        assertDownScaled();
         // Activity max bounds are sandboxed due to size compat mode, even if is not visible.
         assertActivityMaxBoundsSandboxed();
 
@@ -1624,6 +1625,85 @@
                 activity.getBounds().width(), 0.5);
     }
 
+
+    /**
+     * Test that a freeform unresizeable activity can be down-scaled to fill its smaller parent
+     * bounds.
+     */
+    @Test
+    public void testCompatScaling_freeformUnresizeableApp_largerThanParent_downScaled() {
+        final int dw = 600;
+        final int dh = 800;
+        final DisplayContent display = new TestDisplayContent.Builder(mAtm, dw, dh)
+                .setWindowingMode(WINDOWING_MODE_FREEFORM)
+                .build();
+        setUpApp(display);
+        prepareUnresizable(mActivity, /* maxAspect */ 0f, SCREEN_ORIENTATION_PORTRAIT);
+        mActivity.setWindowingMode(WINDOWING_MODE_FREEFORM);
+        assertFalse(mActivity.inSizeCompatMode());
+
+        // Resize app to make original app bounds larger than parent bounds.
+        mTask.getWindowConfiguration().setAppBounds(
+                new Rect(0, 0, dw - 300, dh - 400));
+        mActivity.onConfigurationChanged(mTask.getConfiguration());
+        // App should enter size compat mode and be down-scaled to fill new parent bounds.
+        assertDownScaled();
+    }
+
+    /**
+     * Test that when desktop mode is enabled, a freeform unresizeable activity can be up-scaled to
+     * fill its larger parent bounds.
+     */
+    @Test
+    @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODE)
+    public void testCompatScaling_freeformUnresizeableApp_smallerThanParent_upScaled() {
+        doReturn(true).when(() ->
+                DesktopModeLaunchParamsModifier.canEnterDesktopMode(any()));
+        final int dw = 600;
+        final int dh = 800;
+        final DisplayContent display = new TestDisplayContent.Builder(mAtm, dw, dh)
+                .setWindowingMode(WINDOWING_MODE_FREEFORM)
+                .build();
+        setUpApp(display);
+        prepareUnresizable(mActivity, /* maxAspect */ 0f, SCREEN_ORIENTATION_PORTRAIT);
+        mActivity.setWindowingMode(WINDOWING_MODE_FREEFORM);
+        assertFalse(mActivity.inSizeCompatMode());
+
+        // Resize app to make original app bounds smaller than parent bounds.
+        mTask.getWindowConfiguration().setAppBounds(
+                new Rect(0, 0, dw + 300, dh + 400));
+        mActivity.onConfigurationChanged(mTask.getConfiguration());
+        // App should enter size compat mode and be up-scaled to fill parent bounds.
+        assertUpScaled();
+    }
+
+    /**
+     * Test that when desktop mode is disabled, a freeform unresizeable activity cannot be up-scaled
+     * despite its larger parent bounds.
+     */
+    @Test
+    @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODE)
+    public void testSizeCompatScaling_freeformUnresizeableApp_smallerThanParent_notScaled() {
+        final int dw = 600;
+        final int dh = 800;
+        final DisplayContent display = new TestDisplayContent.Builder(mAtm, dw, dh)
+                .setWindowingMode(WINDOWING_MODE_FREEFORM)
+                .build();
+        setUpApp(display);
+        prepareUnresizable(mActivity, /* maxAspect */ 0f, SCREEN_ORIENTATION_PORTRAIT);
+        mActivity.setWindowingMode(WINDOWING_MODE_FREEFORM);
+        assertFalse(mActivity.inSizeCompatMode());
+        final Rect originalAppBounds = mActivity.getBounds();
+
+        // Resize app to make original app bounds smaller than parent bounds.
+        mTask.getWindowConfiguration().setAppBounds(
+                new Rect(0, 0, dw + 300, dh + 400));
+        mActivity.onConfigurationChanged(mTask.getConfiguration());
+        // App should enter size compat mode but remain its original size.
+        assertTrue(mActivity.inSizeCompatMode());
+        assertEquals(originalAppBounds, mActivity.getBounds());
+    }
+
     @Test
     public void testGetLetterboxInnerBounds_noScalingApplied() {
         // Set up a display in portrait and ignoring orientation request.
@@ -1659,7 +1739,7 @@
         // App should be in size compat.
         assertFalse(mActivity.mAppCompatController.getAppCompatAspectRatioPolicy()
                 .isLetterboxedForFixedOrientationAndAspectRatio());
-        assertScaled();
+        assertDownScaled();
         assertThat(mActivity.inSizeCompatMode()).isTrue();
         assertActivityMaxBoundsSandboxed();
 
@@ -2000,7 +2080,7 @@
 
         // After we rotate, the activity should go in the size-compat mode and report the same
         // configuration values.
-        assertScaled();
+        assertDownScaled();
         assertEquals(originalScreenWidthDp, mActivity.getConfiguration().smallestScreenWidthDp);
         assertEquals(originalScreenWidthDp, mActivity.getConfiguration().screenWidthDp);
         assertEquals(originalScreenHeighthDp, mActivity.getConfiguration().screenHeightDp);
@@ -2775,7 +2855,7 @@
         // App should be in size compat.
         assertFalse(mActivity.mAppCompatController.getAppCompatAspectRatioPolicy()
                 .isLetterboxedForFixedOrientationAndAspectRatio());
-        assertScaled();
+        assertDownScaled();
         assertEquals(activityBounds.width(), newActivityBounds.width());
         assertEquals(activityBounds.height(), newActivityBounds.height());
         assertActivityMaxBoundsSandboxed();
@@ -2807,7 +2887,7 @@
         // App should be in size compat.
         assertFalse(mActivity.mAppCompatController.getAppCompatAspectRatioPolicy()
                 .isLetterboxedForFixedOrientationAndAspectRatio());
-        assertScaled();
+        assertDownScaled();
         assertThat(mActivity.inSizeCompatMode()).isTrue();
         assertActivityMaxBoundsSandboxed();
 
@@ -2955,7 +3035,7 @@
         // App should be in size compat.
         assertFalse(mActivity.mAppCompatController.getAppCompatAspectRatioPolicy()
                 .isLetterboxedForFixedOrientationAndAspectRatio());
-        assertScaled();
+        assertDownScaled();
         assertThat(mActivity.inSizeCompatMode()).isTrue();
         // Activity max bounds are sandboxed due to size compat mode.
         assertActivityMaxBoundsSandboxed();
@@ -2967,7 +3047,7 @@
         verify(mActivity, never()).clearSizeCompatMode();
         assertFalse(mActivity.mAppCompatController.getAppCompatAspectRatioPolicy()
                 .isLetterboxedForFixedOrientationAndAspectRatio());
-        assertScaled();
+        assertDownScaled();
         assertEquals(activityBounds, mActivity.getBounds());
         // Activity max bounds are sandboxed due to size compat.
         assertActivityMaxBoundsSandboxed();
@@ -2995,7 +3075,7 @@
         // App should be in size compat.
         assertFalse(mActivity.mAppCompatController.getAppCompatAspectRatioPolicy()
                 .isLetterboxedForFixedOrientationAndAspectRatio());
-        assertScaled();
+        assertDownScaled();
         assertActivityMaxBoundsSandboxed();
 
         // Rotate display to landscape.
@@ -3032,7 +3112,7 @@
         // App should be in size compat.
         assertFalse(mActivity.mAppCompatController.getAppCompatAspectRatioPolicy()
                 .isLetterboxedForFixedOrientationAndAspectRatio());
-        assertScaled();
+        assertDownScaled();
         assertActivityMaxBoundsSandboxed();
 
         // Rotate display to portrait.
@@ -3224,7 +3304,7 @@
         assertEquals(WINDOWING_MODE_MULTI_WINDOW, mActivity.getWindowingMode());
 
         // Non-resizable activity in size compat mode
-        assertScaled();
+        assertDownScaled();
         final Rect newBounds = new Rect(mActivity.getWindowConfiguration().getBounds());
         assertEquals(originalBounds.width(), newBounds.width());
         assertEquals(originalBounds.height(), newBounds.height());
@@ -3288,7 +3368,7 @@
         assertEquals(WINDOWING_MODE_MULTI_WINDOW, mActivity.getWindowingMode());
 
         // Non-resizable activity in size compat mode
-        assertScaled();
+        assertDownScaled();
         final Rect newBounds = new Rect(mActivity.getWindowConfiguration().getBounds());
         assertEquals(originalBounds.width(), newBounds.width());
         assertEquals(originalBounds.height(), newBounds.height());
@@ -3329,7 +3409,7 @@
         organizer.mPrimary.setBounds(0, 0, 1000, 800);
 
         // Non-resizable activity should be in size compat mode.
-        assertScaled();
+        assertDownScaled();
         assertEquals(mActivity.getBounds(), new Rect(60, 0, 940, 800));
 
         recomputeNaturalConfigurationOfUnresizableActivity();
@@ -3906,7 +3986,7 @@
         // Force activity to scaled down for size compat mode.
         resizeDisplay(mTask.mDisplayContent, 700, 1400);
         assertTrue(mActivity.inSizeCompatMode());
-        assertScaled();
+        assertDownScaled();
         assertEquals(sizeCompatScaled, mActivity.getBounds());
     }
 
@@ -4406,7 +4486,7 @@
         resizeDisplay(mTask.mDisplayContent, 1400, 700);
 
         assertTrue(mActivity.inSizeCompatMode());
-        assertScaled();
+        assertDownScaled();
         assertEquals(sizeCompatScaled, mActivity.getBounds());
     }
 
@@ -4672,7 +4752,7 @@
         // Target min aspect ratio must be larger than parent aspect ratio to be applied.
         final float targetMinAspectRatio = 3.0f;
 
-        // Create fixed portait activity with min aspect ratio greater than parent aspect ratio.
+        // Create fixed portrait activity with min aspect ratio greater than parent aspect ratio.
         final ActivityRecord fixedOrientationActivity = new ActivityBuilder(mAtm)
                 .setTask(task).setScreenOrientation(SCREEN_ORIENTATION_PORTRAIT)
                 .setMinAspectRatio(targetMinAspectRatio).build();
@@ -4686,7 +4766,7 @@
         final Rect minAspectRatioAppBounds = new Rect(minAspectRatioActivity.getConfiguration()
                 .windowConfiguration.getAppBounds());
 
-        // Create unresizeable fixed portait activity with min aspect ratio greater than parent
+        // Create unresizeable fixed portrait activity with min aspect ratio greater than parent
         // aspect ratio.
         final ActivityRecord sizeCompatActivity = new ActivityBuilder(mAtm)
                 .setTask(task).setResizeMode(RESIZE_MODE_UNRESIZEABLE)
@@ -4719,7 +4799,7 @@
         // Activity should enter size compat with old density after display density change.
         display.setForcedDensity(newDensity, UserHandle.USER_CURRENT);
 
-        assertScaled();
+        assertDownScaled();
         assertEquals(origDensity, mActivity.getConfiguration().densityDpi);
 
         // Activity should exit size compat with new density.
@@ -4958,14 +5038,25 @@
         }
     }
 
-    private void assertScaled() {
-        assertScaled(mActivity);
+    private void assertUpScaled() {
+        assertScaled(mActivity, /* upScalingExpected */ true);
     }
 
-    /** Asserts that the size of activity is larger than its parent so it is scaling. */
-    private void assertScaled(ActivityRecord activity) {
+    private void assertDownScaled() {
+        assertScaled(mActivity, /* upScalingExpected */ false);
+    }
+
+    /**
+     * Asserts that the size of an activity differs from its parent and so it is scaling (either up
+     * or down).
+     */
+    private void assertScaled(ActivityRecord activity, boolean upScalingExpected) {
         assertTrue(activity.inSizeCompatMode());
-        assertNotEquals(1f, activity.getCompatScale(), 0.0001f /* delta */);
+        if (upScalingExpected) {
+            assertTrue(activity.getCompatScale() > 1f);
+        } else {
+            assertTrue(activity.getCompatScale() < 1f);
+        }
     }
 
     private void assertFitted() {
diff --git a/tests/FlickerTests/IME/Android.bp b/tests/FlickerTests/IME/Android.bp
index ccc3683..78d93e1 100644
--- a/tests/FlickerTests/IME/Android.bp
+++ b/tests/FlickerTests/IME/Android.bp
@@ -34,6 +34,11 @@
     srcs: ["src/**/Close*"],
 }
 
+filegroup {
+    name: "FlickerTestsIme2-src",
+    srcs: ["src/**/ShowImeOnAppStart*"],
+}
+
 android_test {
     name: "FlickerTestsIme",
     defaults: ["FlickerTestsDefault"],
@@ -77,9 +82,23 @@
     defaults: ["FlickerTestsDefault"],
     manifest: "AndroidManifest.xml",
     test_config_template: "AndroidTestTemplate.xml",
+    srcs: [":FlickerTestsIme2-src"],
+    static_libs: [
+        "FlickerTestsBase",
+        "FlickerTestsImeCommon",
+    ],
+    data: ["trace_config/*"],
+}
+
+android_test {
+    name: "FlickerTestsIme3",
+    defaults: ["FlickerTestsDefault"],
+    manifest: "AndroidManifest.xml",
+    test_config_template: "AndroidTestTemplate.xml",
     srcs: ["src/**/*"],
     exclude_srcs: [
         ":FlickerTestsIme1-src",
+        ":FlickerTestsIme2-src",
         ":FlickerTestsImeCommon-src",
     ],
     static_libs: [