Merge "Implemented Shortcut retrieval for current apps" into main
diff --git a/api/Android.bp b/api/Android.bp
index cd1997c..6a04f0d 100644
--- a/api/Android.bp
+++ b/api/Android.bp
@@ -368,8 +368,6 @@
         "--hide CallbackInterface",
         // Disable HiddenSuperclass, as Metalava handles this fine (it should be hidden by default)
         "--hide HiddenSuperclass",
-        "--hide-package android.audio.policy.configuration.V7_0",
-        "--hide-package com.android.server",
         "--manifest $(location :frameworks-base-core-AndroidManifest.xml)",
     ],
     filter_packages: packages_to_document,
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/audio/policy/configuration/V7_0/package-info.java b/core/java/android/audio/policy/configuration/V7_0/package-info.java
new file mode 100644
index 0000000..8f7425f
--- /dev/null
+++ b/core/java/android/audio/policy/configuration/V7_0/package-info.java
@@ -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.
+ */
+
+/**
+ * Hide the android.audio.policy.configuration.V7_0 API as that is managed
+ * separately.
+ *
+ * @hide
+ */
+package android.audio.policy.configuration.V7_0;
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/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/scene/src/com/android/compose/animation/scene/MultiPointerDraggable.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/MultiPointerDraggable.kt
index cdcfc84..615d393 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/MultiPointerDraggable.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/MultiPointerDraggable.kt
@@ -241,43 +241,50 @@
                 }
             }
 
-            awaitPointerEventScope {
-                while (isActive) {
-                    try {
-                        detectDragGestures(
-                            orientation = orientation,
-                            startDragImmediately = startDragImmediately,
-                            onDragStart = { startedPosition, overSlop, pointersDown ->
-                                velocityTracker.resetTracking()
-                                onDragStarted(startedPosition, overSlop, pointersDown)
-                            },
-                            onDrag = { controller, change, amount ->
-                                velocityTracker.addPointerInputChange(change)
-                                controller.onDrag(amount)
-                            },
-                            onDragEnd = { controller ->
-                                val viewConfiguration = currentValueOf(LocalViewConfiguration)
-                                val maxVelocity =
-                                    viewConfiguration.maximumFlingVelocity.let { Velocity(it, it) }
-                                val velocity = velocityTracker.calculateVelocity(maxVelocity)
-                                controller.onStop(
-                                    velocity =
-                                        when (orientation) {
-                                            Orientation.Horizontal -> velocity.x
-                                            Orientation.Vertical -> velocity.y
-                                        },
-                                    canChangeScene = true,
-                                )
-                            },
-                            onDragCancel = { controller ->
-                                controller.onStop(velocity = 0f, canChangeScene = true)
-                            },
-                            swipeDetector = swipeDetector
-                        )
-                    } catch (exception: CancellationException) {
-                        // If the coroutine scope is active, we can just restart the drag cycle.
-                        if (!isActive) {
-                            throw exception
+            // The order is important here: we want to make sure that the previous PointerEventScope
+            // is initialized first. This ensures that the following PointerEventScope doesn't
+            // receive more events than the first one.
+            launch {
+                awaitPointerEventScope {
+                    while (isActive) {
+                        try {
+                            detectDragGestures(
+                                orientation = orientation,
+                                startDragImmediately = startDragImmediately,
+                                onDragStart = { startedPosition, overSlop, pointersDown ->
+                                    velocityTracker.resetTracking()
+                                    onDragStarted(startedPosition, overSlop, pointersDown)
+                                },
+                                onDrag = { controller, change, amount ->
+                                    velocityTracker.addPointerInputChange(change)
+                                    controller.onDrag(amount)
+                                },
+                                onDragEnd = { controller ->
+                                    val viewConfiguration = currentValueOf(LocalViewConfiguration)
+                                    val maxVelocity =
+                                        viewConfiguration.maximumFlingVelocity.let {
+                                            Velocity(it, it)
+                                        }
+                                    val velocity = velocityTracker.calculateVelocity(maxVelocity)
+                                    controller.onStop(
+                                        velocity =
+                                            when (orientation) {
+                                                Orientation.Horizontal -> velocity.x
+                                                Orientation.Vertical -> velocity.y
+                                            },
+                                        canChangeScene = true,
+                                    )
+                                },
+                                onDragCancel = { controller ->
+                                    controller.onStop(velocity = 0f, canChangeScene = true)
+                                },
+                                swipeDetector = swipeDetector
+                            )
+                        } catch (exception: CancellationException) {
+                            // If the coroutine scope is active, we can just restart the drag cycle.
+                            if (!isActive) {
+                                throw exception
+                            }
                         }
                     }
                 }
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/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/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/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/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/core/java/com/android/server/Watchdog.java b/services/core/java/com/android/server/Watchdog.java
index 21947ba..95dbaae 100644
--- a/services/core/java/com/android/server/Watchdog.java
+++ b/services/core/java/com/android/server/Watchdog.java
@@ -1016,6 +1016,7 @@
             // Trigger the kernel to dump all blocked threads, and backtraces on all CPUs to the
             // kernel log
             doSysRq('w');
+            doSysRq('m');
             doSysRq('l');
         }
 
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/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/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/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() {