Merge "Add WakeDirectlyToGoneInteractor." into main
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 868c462..1167cc4 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) {
                             startTransitionTo(
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..6a1b7cf
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardWakeDirectlyToGoneInteractor.kt
@@ -0,0 +1,367 @@
+/*
+ * 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.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() {
+        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 3355ffd..0985e69 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
@@ -203,9 +214,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 =
@@ -213,16 +228,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/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..22181f8
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardWakeDirectlyToGoneInteractorTest.kt
@@ -0,0 +1,370 @@
+/*
+ * 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.provider.Settings
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.internal.widget.lockPatternUtils
+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
+    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
+    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
+    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
+    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
+    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
+    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/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,
         )
     }