Merge changes I645115ab,I73b51987,Id1b79253 into tm-qpr-dev am: 2c34290e87 am: c52dcfa7d5

Original change: https://googleplex-android-review.googlesource.com/c/platform/frameworks/base/+/20427727

Change-Id: I0b6eb3cad7f3de3614f303969f937c29c3044671
Signed-off-by: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
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 ca25282..9d5d8bb 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
@@ -17,6 +17,7 @@
 package com.android.systemui.keyguard.data.repository
 
 import com.android.keyguard.KeyguardUpdateMonitor
+import com.android.keyguard.KeyguardUpdateMonitorCallback
 import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging
 import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
 import com.android.systemui.common.shared.model.Position
@@ -69,6 +70,9 @@
      */
     val isKeyguardShowing: Flow<Boolean>
 
+    /** Observable for the signal that keyguard is about to go away. */
+    val isKeyguardGoingAway: Flow<Boolean>
+
     /** Observable for whether the bouncer is showing. */
     val isBouncerShowing: Flow<Boolean>
 
@@ -85,6 +89,14 @@
     val isDozing: Flow<Boolean>
 
     /**
+     * Observable for whether the device is dreaming.
+     *
+     * Dozing/AOD is a specific type of dream, but it is also possible for other non-systemui dreams
+     * to be active, such as screensavers.
+     */
+    val isDreaming: Flow<Boolean>
+
+    /**
      * Observable for the amount of doze we are currently in.
      *
      * While in doze state, this amount can change - driving a cycle of animations designed to avoid
@@ -136,12 +148,12 @@
 class KeyguardRepositoryImpl
 @Inject
 constructor(
-        statusBarStateController: StatusBarStateController,
-        dozeHost: DozeHost,
-        wakefulnessLifecycle: WakefulnessLifecycle,
-        biometricUnlockController: BiometricUnlockController,
-        private val keyguardStateController: KeyguardStateController,
-        private val keyguardUpdateMonitor: KeyguardUpdateMonitor,
+    statusBarStateController: StatusBarStateController,
+    dozeHost: DozeHost,
+    wakefulnessLifecycle: WakefulnessLifecycle,
+    biometricUnlockController: BiometricUnlockController,
+    private val keyguardStateController: KeyguardStateController,
+    private val keyguardUpdateMonitor: KeyguardUpdateMonitor,
 ) : KeyguardRepository {
     private val _animateBottomAreaDozingTransitions = MutableStateFlow(false)
     override val animateBottomAreaDozingTransitions =
@@ -176,6 +188,29 @@
         awaitClose { keyguardStateController.removeCallback(callback) }
     }
 
+    override val isKeyguardGoingAway: Flow<Boolean> = conflatedCallbackFlow {
+        val callback =
+            object : KeyguardStateController.Callback {
+                override fun onKeyguardGoingAwayChanged() {
+                    trySendWithFailureLogging(
+                        keyguardStateController.isKeyguardGoingAway,
+                        TAG,
+                        "updated isKeyguardGoingAway"
+                    )
+                }
+            }
+
+        keyguardStateController.addCallback(callback)
+        // Adding the callback does not send an initial update.
+        trySendWithFailureLogging(
+            keyguardStateController.isKeyguardGoingAway,
+            TAG,
+            "initial isKeyguardGoingAway"
+        )
+
+        awaitClose { keyguardStateController.removeCallback(callback) }
+    }
+
     override val isBouncerShowing: Flow<Boolean> = conflatedCallbackFlow {
         val callback =
             object : KeyguardStateController.Callback {
@@ -218,6 +253,25 @@
             }
             .distinctUntilChanged()
 
+    override val isDreaming: Flow<Boolean> =
+        conflatedCallbackFlow {
+                val callback =
+                    object : KeyguardUpdateMonitorCallback() {
+                        override fun onDreamingStateChanged(isDreaming: Boolean) {
+                            trySendWithFailureLogging(isDreaming, TAG, "updated isDreaming")
+                        }
+                    }
+                keyguardUpdateMonitor.registerCallback(callback)
+                trySendWithFailureLogging(
+                    keyguardUpdateMonitor.isDreaming,
+                    TAG,
+                    "initial isDreaming",
+                )
+
+                awaitClose { keyguardUpdateMonitor.removeCallback(callback) }
+            }
+            .distinctUntilChanged()
+
     override val dozeAmount: Flow<Float> = conflatedCallbackFlow {
         val callback =
             object : StatusBarStateController.StateListener {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepository.kt
index e3d1a27..bce7d92 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepository.kt
@@ -94,11 +94,13 @@
      */
     private val _transitions =
         MutableSharedFlow<TransitionStep>(
+            replay = 2,
             extraBufferCapacity = 10,
-            onBufferOverflow = BufferOverflow.DROP_OLDEST
+            onBufferOverflow = BufferOverflow.DROP_OLDEST,
         )
     override val transitions = _transitions.asSharedFlow().distinctUntilChanged()
     private var lastStep: TransitionStep = TransitionStep()
+    private var lastAnimator: ValueAnimator? = null
 
     /*
      * When manual control of the transition is requested, a unique [UUID] is used as the handle
@@ -106,19 +108,39 @@
      */
     private var updateTransitionId: UUID? = null
 
+    init {
+        // Seed with transitions signaling a boot into lockscreen state
+        emitTransition(
+            TransitionStep(
+                KeyguardState.OFF,
+                KeyguardState.LOCKSCREEN,
+                0f,
+                TransitionState.STARTED,
+            )
+        )
+        emitTransition(
+            TransitionStep(
+                KeyguardState.OFF,
+                KeyguardState.LOCKSCREEN,
+                1f,
+                TransitionState.FINISHED,
+            )
+        )
+    }
+
     override fun startTransition(info: TransitionInfo): UUID? {
         if (lastStep.transitionState != TransitionState.FINISHED) {
-            // Open questions:
-            // * Queue of transitions? buffer of 1?
-            // * Are transitions cancellable if a new one is triggered?
-            // * What validation does this need to do?
-            Log.wtf(TAG, "Transition still active: $lastStep")
-            return null
+            Log.i(TAG, "Transition still active: $lastStep, canceling")
         }
 
+        val startingValue = 1f - lastStep.value
+        lastAnimator?.cancel()
+        lastAnimator = info.animator
+
         info.animator?.let { animator ->
             // An animator was provided, so use it to run the transition
-            animator.setFloatValues(0f, 1f)
+            animator.setFloatValues(startingValue, 1f)
+            animator.duration = ((1f - startingValue) * animator.duration).toLong()
             val updateListener =
                 object : AnimatorUpdateListener {
                     override fun onAnimationUpdate(animation: ValueAnimator) {
@@ -134,15 +156,24 @@
             val adapter =
                 object : AnimatorListenerAdapter() {
                     override fun onAnimationStart(animation: Animator) {
-                        emitTransition(TransitionStep(info, 0f, TransitionState.STARTED))
+                        emitTransition(TransitionStep(info, startingValue, TransitionState.STARTED))
                     }
                     override fun onAnimationCancel(animation: Animator) {
-                        Log.i(TAG, "Cancelling transition: $info")
+                        endAnimation(animation, lastStep.value, TransitionState.CANCELED)
                     }
                     override fun onAnimationEnd(animation: Animator) {
-                        emitTransition(TransitionStep(info, 1f, TransitionState.FINISHED))
+                        endAnimation(animation, 1f, TransitionState.FINISHED)
+                    }
+
+                    private fun endAnimation(
+                        animation: Animator,
+                        value: Float,
+                        state: TransitionState
+                    ) {
+                        emitTransition(TransitionStep(info, value, state))
                         animator.removeListener(this)
                         animator.removeUpdateListener(updateListener)
+                        lastAnimator = null
                     }
                 }
             animator.addListener(adapter)
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/AodLockscreenTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/AodLockscreenTransitionInteractor.kt
index 0aeff7f..e5521c7 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/AodLockscreenTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/AodLockscreenTransitionInteractor.kt
@@ -20,10 +20,11 @@
 import com.android.systemui.animation.Interpolators
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
-import com.android.systemui.keyguard.data.repository.KeyguardRepository
 import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository
 import com.android.systemui.keyguard.shared.model.KeyguardState
 import com.android.systemui.keyguard.shared.model.TransitionInfo
+import com.android.systemui.keyguard.shared.model.WakefulnessModel.Companion.isSleepingOrStartingToSleep
+import com.android.systemui.keyguard.shared.model.WakefulnessModel.Companion.isWakingOrStartingToWake
 import com.android.systemui.util.kotlin.sample
 import javax.inject.Inject
 import kotlinx.coroutines.CoroutineScope
@@ -35,18 +36,30 @@
 @Inject
 constructor(
     @Application private val scope: CoroutineScope,
-    private val keyguardRepository: KeyguardRepository,
+    private val keyguardInteractor: KeyguardInteractor,
     private val keyguardTransitionRepository: KeyguardTransitionRepository,
     private val keyguardTransitionInteractor: KeyguardTransitionInteractor,
 ) : TransitionInteractor("AOD<->LOCKSCREEN") {
 
     override fun start() {
         scope.launch {
-            keyguardRepository.isDozing
-                .sample(keyguardTransitionInteractor.finishedKeyguardState, { a, b -> Pair(a, b) })
+            /*
+             * Listening to the startedKeyguardTransitionStep (last started step) allows this code
+             * to interrupt an active transition, as long as they were either going to LOCKSCREEN or
+             * AOD state. One example is when the user presses the power button in the middle of an
+             * active transition.
+             */
+            keyguardInteractor.wakefulnessState
+                .sample(
+                    keyguardTransitionInteractor.startedKeyguardTransitionStep,
+                    { a, b -> Pair(a, b) }
+                )
                 .collect { pair ->
-                    val (isDozing, keyguardState) = pair
-                    if (isDozing && keyguardState == KeyguardState.LOCKSCREEN) {
+                    val (wakefulnessState, lastStartedStep) = pair
+                    if (
+                        isSleepingOrStartingToSleep(wakefulnessState) &&
+                            lastStartedStep.to == KeyguardState.LOCKSCREEN
+                    ) {
                         keyguardTransitionRepository.startTransition(
                             TransitionInfo(
                                 name,
@@ -55,7 +68,10 @@
                                 getAnimator(),
                             )
                         )
-                    } else if (!isDozing && keyguardState == KeyguardState.AOD) {
+                    } else if (
+                        isWakingOrStartingToWake(wakefulnessState) &&
+                            lastStartedStep.to == KeyguardState.AOD
+                    ) {
                         keyguardTransitionRepository.startTransition(
                             TransitionInfo(
                                 name,
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/BouncerToGoneTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/BouncerToGoneTransitionInteractor.kt
new file mode 100644
index 0000000..dd29673
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/BouncerToGoneTransitionInteractor.kt
@@ -0,0 +1,81 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.systemui.keyguard.domain.interactor
+
+import android.animation.ValueAnimator
+import com.android.systemui.animation.Interpolators
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository
+import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.keyguard.shared.model.TransitionInfo
+import com.android.systemui.shade.data.repository.ShadeRepository
+import com.android.systemui.util.kotlin.sample
+import java.util.UUID
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.collect
+import kotlinx.coroutines.launch
+
+@SysUISingleton
+class BouncerToGoneTransitionInteractor
+@Inject
+constructor(
+    @Application private val scope: CoroutineScope,
+    private val keyguardInteractor: KeyguardInteractor,
+    private val shadeRepository: ShadeRepository,
+    private val keyguardTransitionRepository: KeyguardTransitionRepository,
+    private val keyguardTransitionInteractor: KeyguardTransitionInteractor
+) : TransitionInteractor("BOUNCER->GONE") {
+
+    private var transitionId: UUID? = null
+
+    override fun start() {
+        listenForKeyguardGoingAway()
+    }
+
+    private fun listenForKeyguardGoingAway() {
+        scope.launch {
+            keyguardInteractor.isKeyguardGoingAway
+                .sample(keyguardTransitionInteractor.finishedKeyguardState, { a, b -> Pair(a, b) })
+                .collect { pair ->
+                    val (isKeyguardGoingAway, keyguardState) = pair
+                    if (isKeyguardGoingAway && keyguardState == KeyguardState.BOUNCER) {
+                        keyguardTransitionRepository.startTransition(
+                            TransitionInfo(
+                                ownerName = name,
+                                from = KeyguardState.BOUNCER,
+                                to = KeyguardState.GONE,
+                                animator = getAnimator(),
+                            )
+                        )
+                    }
+                }
+        }
+    }
+
+    private fun getAnimator(): ValueAnimator {
+        return ValueAnimator().apply {
+            setInterpolator(Interpolators.LINEAR)
+            setDuration(TRANSITION_DURATION_MS)
+        }
+    }
+
+    companion object {
+        private const val TRANSITION_DURATION_MS = 300L
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/DreamingLockscreenTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/DreamingLockscreenTransitionInteractor.kt
new file mode 100644
index 0000000..c44cda4
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/DreamingLockscreenTransitionInteractor.kt
@@ -0,0 +1,81 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.systemui.keyguard.domain.interactor
+
+import android.animation.ValueAnimator
+import com.android.systemui.animation.Interpolators
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository
+import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.keyguard.shared.model.TransitionInfo
+import com.android.systemui.util.kotlin.sample
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.collect
+import kotlinx.coroutines.launch
+
+@SysUISingleton
+class DreamingLockscreenTransitionInteractor
+@Inject
+constructor(
+    @Application private val scope: CoroutineScope,
+    private val keyguardInteractor: KeyguardInteractor,
+    private val keyguardTransitionRepository: KeyguardTransitionRepository,
+    private val keyguardTransitionInteractor: KeyguardTransitionInteractor,
+) : TransitionInteractor("DREAMING<->LOCKSCREEN") {
+
+    override fun start() {
+        scope.launch {
+            keyguardInteractor.isDreaming
+                .sample(keyguardTransitionInteractor.finishedKeyguardState, { a, b -> Pair(a, b) })
+                .collect { pair ->
+                    val (isDreaming, keyguardState) = pair
+                    if (isDreaming && keyguardState == KeyguardState.LOCKSCREEN) {
+                        keyguardTransitionRepository.startTransition(
+                            TransitionInfo(
+                                name,
+                                KeyguardState.LOCKSCREEN,
+                                KeyguardState.DREAMING,
+                                getAnimator(),
+                            )
+                        )
+                    } else if (!isDreaming && keyguardState == KeyguardState.DREAMING) {
+                        keyguardTransitionRepository.startTransition(
+                            TransitionInfo(
+                                name,
+                                KeyguardState.DREAMING,
+                                KeyguardState.LOCKSCREEN,
+                                getAnimator(),
+                            )
+                        )
+                    }
+                }
+        }
+    }
+
+    private fun getAnimator(): ValueAnimator {
+        return ValueAnimator().apply {
+            setInterpolator(Interpolators.LINEAR)
+            setDuration(TRANSITION_DURATION_MS)
+        }
+    }
+
+    companion object {
+        private const val TRANSITION_DURATION_MS = 500L
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/DreamingToAodTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/DreamingToAodTransitionInteractor.kt
new file mode 100644
index 0000000..9e2b724
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/DreamingToAodTransitionInteractor.kt
@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.systemui.keyguard.domain.interactor
+
+import android.animation.ValueAnimator
+import com.android.systemui.animation.Interpolators
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository
+import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.keyguard.shared.model.TransitionInfo
+import com.android.systemui.keyguard.shared.model.WakefulnessModel.Companion.isSleepingOrStartingToSleep
+import com.android.systemui.util.kotlin.sample
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.collect
+import kotlinx.coroutines.launch
+
+@SysUISingleton
+class DreamingToAodTransitionInteractor
+@Inject
+constructor(
+    @Application private val scope: CoroutineScope,
+    private val keyguardInteractor: KeyguardInteractor,
+    private val keyguardTransitionRepository: KeyguardTransitionRepository,
+    private val keyguardTransitionInteractor: KeyguardTransitionInteractor,
+) : TransitionInteractor("DREAMING->AOD") {
+
+    override fun start() {
+        scope.launch {
+            keyguardInteractor.wakefulnessState
+                .sample(keyguardTransitionInteractor.finishedKeyguardState, { a, b -> Pair(a, b) })
+                .collect { pair ->
+                    val (wakefulnessState, keyguardState) = pair
+                    if (
+                        isSleepingOrStartingToSleep(wakefulnessState) &&
+                            keyguardState == KeyguardState.DREAMING
+                    ) {
+                        keyguardTransitionRepository.startTransition(
+                            TransitionInfo(
+                                name,
+                                KeyguardState.DREAMING,
+                                KeyguardState.AOD,
+                                getAnimator(),
+                            )
+                        )
+                    }
+                }
+        }
+    }
+
+    private fun getAnimator(): ValueAnimator {
+        return ValueAnimator().apply {
+            setInterpolator(Interpolators.LINEAR)
+            setDuration(TRANSITION_DURATION_MS)
+        }
+    }
+
+    companion object {
+        private const val TRANSITION_DURATION_MS = 300L
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt
index 614ff8d..5a1c702 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt
@@ -41,8 +41,15 @@
     val dozeAmount: Flow<Float> = repository.dozeAmount
     /** Whether the system is in doze mode. */
     val isDozing: Flow<Boolean> = repository.isDozing
+    /**
+     * Whether the system is dreaming. [isDreaming] will be always be true when [isDozing] is true,
+     * but not vice-versa.
+     */
+    val isDreaming: Flow<Boolean> = repository.isDreaming
     /** Whether the keyguard is showing or not. */
     val isKeyguardShowing: Flow<Boolean> = repository.isKeyguardShowing
+    /** Whether the keyguard is going away. */
+    val isKeyguardGoingAway: Flow<Boolean> = repository.isKeyguardGoingAway
     /** Whether the bouncer is showing or not. */
     val isBouncerShowing: Flow<Boolean> = repository.isBouncerShowing
     /** The device wake/sleep state */
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionAuditLogger.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionAuditLogger.kt
index 57fb4a1..58a8093 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionAuditLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionAuditLogger.kt
@@ -41,12 +41,24 @@
         }
 
         scope.launch {
+            keyguardInteractor.isBouncerShowing.collect { logger.v("Bouncer showing", it) }
+        }
+
+        scope.launch { keyguardInteractor.isDozing.collect { logger.v("isDozing", it) } }
+
+        scope.launch {
             interactor.finishedKeyguardTransitionStep.collect {
                 logger.i("Finished transition", it)
             }
         }
 
         scope.launch {
+            interactor.canceledKeyguardTransitionStep.collect {
+                logger.i("Canceled transition", it)
+            }
+        }
+
+        scope.launch {
             interactor.startedKeyguardTransitionStep.collect { logger.i("Started transition", it) }
         }
     }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionCoreStartable.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionCoreStartable.kt
index a7c6d44..43dd358e 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionCoreStartable.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionCoreStartable.kt
@@ -42,6 +42,9 @@
                     is GoneAodTransitionInteractor -> Log.d(TAG, "Started $it")
                     is LockscreenGoneTransitionInteractor -> Log.d(TAG, "Started $it")
                     is AodToGoneTransitionInteractor -> Log.d(TAG, "Started $it")
+                    is BouncerToGoneTransitionInteractor -> Log.d(TAG, "Started $it")
+                    is DreamingLockscreenTransitionInteractor -> Log.d(TAG, "Started $it")
+                    is DreamingToAodTransitionInteractor -> Log.d(TAG, "Started $it")
                 }
             it.start()
         }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt
index 749183e..54a4f49 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt
@@ -57,6 +57,14 @@
             lockscreenToAodTransition,
         )
 
+    /* The last [TransitionStep] with a [TransitionState] of STARTED */
+    val startedKeyguardTransitionStep: Flow<TransitionStep> =
+        repository.transitions.filter { step -> step.transitionState == TransitionState.STARTED }
+
+    /* The last [TransitionStep] with a [TransitionState] of CANCELED */
+    val canceledKeyguardTransitionStep: Flow<TransitionStep> =
+        repository.transitions.filter { step -> step.transitionState == TransitionState.CANCELED }
+
     /* The last [TransitionStep] with a [TransitionState] of FINISHED */
     val finishedKeyguardTransitionStep: Flow<TransitionStep> =
         repository.transitions.filter { step -> step.transitionState == TransitionState.FINISHED }
@@ -64,8 +72,4 @@
     /* The last completed [KeyguardState] transition */
     val finishedKeyguardState: Flow<KeyguardState> =
         finishedKeyguardTransitionStep.map { step -> step.to }
-
-    /* The last [TransitionStep] with a [TransitionState] of STARTED */
-    val startedKeyguardTransitionStep: Flow<TransitionStep> =
-        repository.transitions.filter { step -> step.transitionState == TransitionState.STARTED }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/LockscreenBouncerTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/LockscreenBouncerTransitionInteractor.kt
index fd4814d..cca2d56 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/LockscreenBouncerTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/LockscreenBouncerTransitionInteractor.kt
@@ -56,10 +56,20 @@
     private fun listenForBouncerHiding() {
         scope.launch {
             keyguardInteractor.isBouncerShowing
-                .sample(keyguardInteractor.wakefulnessState, { a, b -> Pair(a, b) })
-                .collect { pair ->
-                    val (isBouncerShowing, wakefulnessState) = pair
-                    if (!isBouncerShowing) {
+                .sample(
+                    combine(
+                        keyguardInteractor.wakefulnessState,
+                        keyguardTransitionInteractor.startedKeyguardTransitionStep,
+                    ) { a, b ->
+                        Pair(a, b)
+                    },
+                    { a, bc -> Triple(a, bc.first, bc.second) }
+                )
+                .collect { triple ->
+                    val (isBouncerShowing, wakefulnessState, lastStartedTransitionStep) = triple
+                    if (
+                        !isBouncerShowing && lastStartedTransitionStep.to == KeyguardState.BOUNCER
+                    ) {
                         val to =
                             if (
                                 wakefulnessState == WakefulnessModel.STARTING_TO_SLEEP ||
@@ -90,10 +100,10 @@
                     combine(
                         keyguardTransitionInteractor.finishedKeyguardState,
                         keyguardInteractor.statusBarState,
-                    ) { keyguardState, statusBarState ->
-                        Pair(keyguardState, statusBarState)
+                    ) { a, b ->
+                        Pair(a, b)
                     },
-                    { shadeModel, pair -> Triple(shadeModel, pair.first, pair.second) }
+                    { a, bc -> Triple(a, bc.first, bc.second) }
                 )
                 .collect { triple ->
                     val (shadeModel, keyguardState, statusBarState) = triple
@@ -116,8 +126,7 @@
                         )
                     } else {
                         // TODO (b/251849525): Remove statusbarstate check when that state is
-                        // integrated
-                        // into KeyguardTransitionRepository
+                        // integrated into KeyguardTransitionRepository
                         if (
                             keyguardState == KeyguardState.LOCKSCREEN &&
                                 shadeModel.isUserDragging &&
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/LockscreenGoneTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/LockscreenGoneTransitionInteractor.kt
index 6c1adbd..4100f7a 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/LockscreenGoneTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/LockscreenGoneTransitionInteractor.kt
@@ -23,6 +23,7 @@
 import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository
 import com.android.systemui.keyguard.shared.model.KeyguardState
 import com.android.systemui.keyguard.shared.model.TransitionInfo
+import com.android.systemui.util.kotlin.sample
 import javax.inject.Inject
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.flow.collect
@@ -34,23 +35,27 @@
 constructor(
     @Application private val scope: CoroutineScope,
     private val keyguardInteractor: KeyguardInteractor,
+    private val keyguardTransitionInteractor: KeyguardTransitionInteractor,
     private val keyguardTransitionRepository: KeyguardTransitionRepository,
 ) : TransitionInteractor("LOCKSCREEN->GONE") {
 
     override fun start() {
         scope.launch {
-            keyguardInteractor.isKeyguardShowing.collect { isShowing ->
-                if (!isShowing) {
-                    keyguardTransitionRepository.startTransition(
-                        TransitionInfo(
-                            name,
-                            KeyguardState.LOCKSCREEN,
-                            KeyguardState.GONE,
-                            getAnimator(),
+            keyguardInteractor.isKeyguardGoingAway
+                .sample(keyguardTransitionInteractor.finishedKeyguardState, { a, b -> Pair(a, b) })
+                .collect { pair ->
+                    val (isKeyguardGoingAway, keyguardState) = pair
+                    if (!isKeyguardGoingAway && keyguardState == KeyguardState.LOCKSCREEN) {
+                        keyguardTransitionRepository.startTransition(
+                            TransitionInfo(
+                                name,
+                                KeyguardState.LOCKSCREEN,
+                                KeyguardState.GONE,
+                                getAnimator(),
+                            )
                         )
-                    )
+                    }
                 }
-            }
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerInteractor.kt
index c22f4e7a..fcd653b 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerInteractor.kt
@@ -210,9 +210,12 @@
             expansion == KeyguardBouncer.EXPANSION_HIDDEN &&
                 oldExpansion != KeyguardBouncer.EXPANSION_HIDDEN
         ) {
-            repository.setPrimaryVisible(false)
-            repository.setPrimaryShow(null)
-            falsingCollector.onBouncerHidden()
+            /*
+             * There are cases where #hide() was not invoked, such as when
+             * NotificationPanelViewController controls the hide animation. Make sure the state gets
+             * updated by calling #hide() directly.
+             */
+            hide()
             DejankUtils.postAfterTraversal { primaryBouncerCallbackInteractor.dispatchReset() }
             primaryBouncerCallbackInteractor.dispatchFullyHidden()
         } else if (
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/StartKeyguardTransitionModule.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/StartKeyguardTransitionModule.kt
index 37f33af..dbffeab 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/StartKeyguardTransitionModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/StartKeyguardTransitionModule.kt
@@ -46,5 +46,19 @@
 
     @Binds
     @IntoSet
+    abstract fun bouncerGone(impl: BouncerToGoneTransitionInteractor): TransitionInteractor
+
+    @Binds
+    @IntoSet
     abstract fun lockscreenGone(impl: LockscreenGoneTransitionInteractor): TransitionInteractor
+
+    @Binds
+    @IntoSet
+    abstract fun dreamingLockscreen(
+        impl: DreamingLockscreenTransitionInteractor
+    ): TransitionInteractor
+
+    @Binds
+    @IntoSet
+    abstract fun dreamingToAod(impl: DreamingToAodTransitionInteractor): TransitionInteractor
 }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/KeyguardState.kt b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/KeyguardState.kt
index 7958033..dd908c4 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/KeyguardState.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/KeyguardState.kt
@@ -17,12 +17,29 @@
 
 /** List of all possible states to transition to/from */
 enum class KeyguardState {
-    /**
-     * For initialization as well as when the security method is set to NONE, indicating that
-     * the keyguard should never be shown.
+    /*
+     * The display is completely off, as well as any sensors that would trigger the device to wake
+     * up.
      */
-    NONE,
-    /* Always-on Display. The device is in a low-power mode with a minimal UI visible */
+    OFF,
+    /**
+     * The device has entered a special low-power mode within SystemUI. Doze is technically a
+     * special dream service implementation. No UI is visible. In this state, a least some
+     * low-powered sensors such as lift to wake or tap to wake are enabled, or wake screen for
+     * notifications is enabled, allowing the device to quickly wake up.
+     */
+    DOZING,
+    /*
+     * A device state after the device times out, which can be from both LOCKSCREEN or GONE states.
+     * DOZING is an example of special version of this state. Dreams may be implemented by third
+     * parties to present their own UI over keyguard, like a screensaver.
+     */
+    DREAMING,
+    /**
+     * The device has entered a special low-power mode within SystemUI, also called the Always-on
+     * Display (AOD). A minimal UI is presented to show critical information. If the device is in
+     * low-power mode without a UI, then it is DOZING.
+     */
     AOD,
     /*
      * The security screen prompt UI, containing PIN, Password, Pattern, and all FPS
@@ -34,7 +51,6 @@
      * unlocked if SWIPE security method is used, or if face lockscreen bypass is false.
      */
     LOCKSCREEN,
-
     /*
      * Keyguard is no longer visible. In most cases the user has just authenticated and keyguard
      * is being removed, but there are other cases where the user is swiping away keyguard, such as
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/TransitionState.kt b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/TransitionState.kt
index 0e0465b..38a93b5 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/TransitionState.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/TransitionState.kt
@@ -17,7 +17,12 @@
 
 /** Possible states for a running transition between [State] */
 enum class TransitionState {
+    /* Transition has begun. */
     STARTED,
+    /* Transition is actively running. */
     RUNNING,
-    FINISHED
+    /* Transition has completed successfully. */
+    FINISHED,
+    /* Transition has been interrupted, and not completed successfully. */
+    CANCELED,
 }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/TransitionStep.kt b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/TransitionStep.kt
index 732a6f7..767fd58 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/TransitionStep.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/TransitionStep.kt
@@ -17,8 +17,8 @@
 
 /** This information will flow from the [KeyguardTransitionRepository] to control the UI layer */
 data class TransitionStep(
-    val from: KeyguardState = KeyguardState.NONE,
-    val to: KeyguardState = KeyguardState.NONE,
+    val from: KeyguardState = KeyguardState.OFF,
+    val to: KeyguardState = KeyguardState.OFF,
     val value: Float = 0f, // constrained [0.0, 1.0]
     val transitionState: TransitionState = TransitionState.FINISHED,
     val ownerName: String = "",
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/WakefulnessModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/WakefulnessModel.kt
index 64f834d..92040f4 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/WakefulnessModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/WakefulnessModel.kt
@@ -24,5 +24,15 @@
     /** Device is now fully awake and interactive. */
     AWAKE,
     /** Signal that the device is now going to sleep. */
-    STARTING_TO_SLEEP,
+    STARTING_TO_SLEEP;
+
+    companion object {
+        fun isSleepingOrStartingToSleep(model: WakefulnessModel): Boolean {
+            return model == ASLEEP || model == STARTING_TO_SLEEP
+        }
+
+        fun isWakingOrStartingToWake(model: WakefulnessModel): Boolean {
+            return model == AWAKE || model == STARTING_TO_WAKE
+        }
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java
index d328472..aa0757e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java
@@ -279,10 +279,7 @@
      * @see #onFullyShown()
      */
     private void onFullyHidden() {
-        cancelShowRunnable();
-        setVisibility(View.INVISIBLE);
-        mFalsingCollector.onBouncerHidden();
-        DejankUtils.postAfterTraversal(mResetRunnable);
+
     }
 
     private void setVisibility(@View.Visibility int visibility) {
@@ -459,7 +456,13 @@
             onFullyShown();
             dispatchFullyShown();
         } else if (fraction == EXPANSION_HIDDEN && oldExpansion != EXPANSION_HIDDEN) {
-            onFullyHidden();
+            DejankUtils.postAfterTraversal(mResetRunnable);
+            /*
+             * There are cases where #hide() was not invoked, such as when
+             * NotificationPanelViewController controls the hide animation. Make sure the state gets
+             * updated by calling #hide() directly.
+             */
+            hide(false /* destroyView */);
             dispatchFullyHidden();
         } else if (fraction != EXPANSION_VISIBLE && oldExpansion == EXPANSION_VISIBLE) {
             dispatchStartingToHide();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
index 86e27ab..d54a863 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
@@ -895,7 +895,7 @@
 
         float stateBehind = mClipsQsScrim ? state.getNotifAlpha() : state.getBehindAlpha();
         float behindAlpha;
-        int behindTint;
+        int behindTint = state.getBehindTint();
         if (mDarkenWhileDragging) {
             behindAlpha = MathUtils.lerp(mDefaultScrimAlpha, stateBehind,
                     interpolatedFract);
@@ -903,12 +903,14 @@
             behindAlpha = MathUtils.lerp(0 /* start */, stateBehind,
                     interpolatedFract);
         }
-        if (mClipsQsScrim) {
-            behindTint = ColorUtils.blendARGB(ScrimState.BOUNCER.getNotifTint(),
+        if (mStatusBarKeyguardViewManager.isPrimaryBouncerInTransit()) {
+            if (mClipsQsScrim) {
+                behindTint = ColorUtils.blendARGB(ScrimState.BOUNCER.getNotifTint(),
                     state.getNotifTint(), interpolatedFract);
-        } else {
-            behindTint = ColorUtils.blendARGB(ScrimState.BOUNCER.getBehindTint(),
+            } else {
+                behindTint = ColorUtils.blendARGB(ScrimState.BOUNCER.getBehindTint(),
                     state.getBehindTint(), interpolatedFract);
+            }
         }
         if (mQsExpansion > 0) {
             behindAlpha = MathUtils.lerp(behindAlpha, mDefaultScrimAlpha, mQsExpansion);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
index e9d4e55..681d21b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
@@ -165,6 +165,7 @@
             @Override
             public void onFullyHidden() {
                 mPrimaryBouncerAnimating = false;
+                updateStates();
             }
 
             @Override
@@ -1183,12 +1184,16 @@
             updateNavigationBarVisibility(navBarVisible);
         }
 
-        if (primaryBouncerShowing != mLastPrimaryBouncerShowing || mFirstUpdate) {
+        boolean isPrimaryBouncerShowingChanged =
+            primaryBouncerShowing != mLastPrimaryBouncerShowing;
+        mLastPrimaryBouncerShowing = primaryBouncerShowing;
+
+        if (isPrimaryBouncerShowingChanged || mFirstUpdate) {
             mNotificationShadeWindowController.setBouncerShowing(primaryBouncerShowing);
             mCentralSurfaces.setBouncerShowing(primaryBouncerShowing);
         }
         if (primaryBouncerIsOrWillBeShowing != mLastPrimaryBouncerIsOrWillBeShowing || mFirstUpdate
-                || primaryBouncerShowing != mLastPrimaryBouncerShowing) {
+                || isPrimaryBouncerShowingChanged) {
             mKeyguardUpdateManager.sendPrimaryBouncerChanged(primaryBouncerIsOrWillBeShowing,
                     primaryBouncerShowing);
         }
@@ -1197,7 +1202,6 @@
         mLastShowing = showing;
         mLastGlobalActionsVisible = mGlobalActionsVisible;
         mLastOccluded = occluded;
-        mLastPrimaryBouncerShowing = primaryBouncerShowing;
         mLastPrimaryBouncerIsOrWillBeShowing = primaryBouncerIsOrWillBeShowing;
         mLastBouncerDismissible = primaryBouncerDismissible;
         mLastRemoteInputActive = remoteInputActive;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt
index ade83cf..6ba0634 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt
@@ -18,6 +18,7 @@
 
 import androidx.test.filters.SmallTest
 import com.android.keyguard.KeyguardUpdateMonitor
+import com.android.keyguard.KeyguardUpdateMonitorCallback
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.common.shared.model.Position
 import com.android.systemui.doze.DozeHost
@@ -60,12 +61,12 @@
 
         underTest =
             KeyguardRepositoryImpl(
-                    statusBarStateController,
-                    dozeHost,
-                    wakefulnessLifecycle,
-                    biometricUnlockController,
-                    keyguardStateController,
-                    keyguardUpdateMonitor,
+                statusBarStateController,
+                dozeHost,
+                wakefulnessLifecycle,
+                biometricUnlockController,
+                keyguardStateController,
+                keyguardUpdateMonitor,
             )
     }
 
@@ -257,6 +258,48 @@
     }
 
     @Test
+    fun isKeyguardGoingAway() = runBlockingTest {
+        whenever(keyguardStateController.isKeyguardGoingAway).thenReturn(false)
+        var latest: Boolean? = null
+        val job = underTest.isKeyguardGoingAway.onEach { latest = it }.launchIn(this)
+
+        assertThat(latest).isFalse()
+
+        val captor = argumentCaptor<KeyguardStateController.Callback>()
+        verify(keyguardStateController).addCallback(captor.capture())
+
+        whenever(keyguardStateController.isKeyguardGoingAway).thenReturn(true)
+        captor.value.onKeyguardGoingAwayChanged()
+        assertThat(latest).isTrue()
+
+        whenever(keyguardStateController.isKeyguardGoingAway).thenReturn(false)
+        captor.value.onKeyguardGoingAwayChanged()
+        assertThat(latest).isFalse()
+
+        job.cancel()
+    }
+
+    @Test
+    fun isDreaming() = runBlockingTest {
+        whenever(keyguardUpdateMonitor.isDreaming()).thenReturn(false)
+        var latest: Boolean? = null
+        val job = underTest.isDreaming.onEach { latest = it }.launchIn(this)
+
+        assertThat(latest).isFalse()
+
+        val captor = argumentCaptor<KeyguardUpdateMonitorCallback>()
+        verify(keyguardUpdateMonitor).registerCallback(captor.capture())
+
+        captor.value.onDreamingStateChanged(true)
+        assertThat(latest).isTrue()
+
+        captor.value.onDreamingStateChanged(false)
+        assertThat(latest).isFalse()
+
+        job.cancel()
+    }
+
+    @Test
     fun biometricUnlockState() = runBlockingTest {
         val values = mutableListOf<BiometricUnlockModel>()
         val job = underTest.biometricUnlockState.onEach(values::add).launchIn(this)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepositoryTest.kt
index 27d5d0a..2b03722 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepositoryTest.kt
@@ -25,8 +25,8 @@
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.animation.Interpolators
+import com.android.systemui.keyguard.shared.model.KeyguardState
 import com.android.systemui.keyguard.shared.model.KeyguardState.AOD
-import com.android.systemui.keyguard.shared.model.KeyguardState.BOUNCER
 import com.android.systemui.keyguard.shared.model.KeyguardState.LOCKSCREEN
 import com.android.systemui.keyguard.shared.model.TransitionInfo
 import com.android.systemui.keyguard.shared.model.TransitionState
@@ -38,7 +38,6 @@
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.Dispatchers
 import kotlinx.coroutines.Job
-import kotlinx.coroutines.delay
 import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.collect
 import kotlinx.coroutines.flow.launchIn
@@ -91,18 +90,51 @@
                 }
             }
 
-            assertSteps(steps, listWithStep(BigDecimal(.1)))
+            assertSteps(steps, listWithStep(BigDecimal(.1)), AOD, LOCKSCREEN)
 
             job.cancel()
             provider.stop()
         }
 
     @Test
-    fun `startTransition called during another transition fails`() {
-        underTest.startTransition(TransitionInfo(OWNER_NAME, AOD, LOCKSCREEN, null))
-        underTest.startTransition(TransitionInfo(OWNER_NAME, LOCKSCREEN, BOUNCER, null))
+    fun `starting second transition will cancel the first transition`() {
+        runBlocking(IMMEDIATE) {
+            val (animator, provider) = setupAnimator(this)
 
-        assertThat(wtfHandler.failed).isTrue()
+            val steps = mutableListOf<TransitionStep>()
+            val job = underTest.transition(AOD, LOCKSCREEN).onEach { steps.add(it) }.launchIn(this)
+
+            underTest.startTransition(TransitionInfo(OWNER_NAME, AOD, LOCKSCREEN, animator))
+            // 3 yields(), alternating with the animator, results in a value 0.1, which can be
+            // canceled and tested against
+            yield()
+            yield()
+            yield()
+
+            // Now start 2nd transition, which will interrupt the first
+            val job2 = underTest.transition(LOCKSCREEN, AOD).onEach { steps.add(it) }.launchIn(this)
+            val (animator2, provider2) = setupAnimator(this)
+            underTest.startTransition(TransitionInfo(OWNER_NAME, LOCKSCREEN, AOD, animator2))
+
+            val startTime = System.currentTimeMillis()
+            while (animator2.isRunning()) {
+                yield()
+                if (System.currentTimeMillis() - startTime > MAX_TEST_DURATION) {
+                    fail("Failed test due to excessive runtime of: $MAX_TEST_DURATION")
+                }
+            }
+
+            val firstTransitionSteps = listWithStep(step = BigDecimal(.1), stop = BigDecimal(.1))
+            assertSteps(steps.subList(0, 4), firstTransitionSteps, AOD, LOCKSCREEN)
+
+            val secondTransitionSteps = listWithStep(step = BigDecimal(.1), start = BigDecimal(.9))
+            assertSteps(steps.subList(4, steps.size), secondTransitionSteps, LOCKSCREEN, AOD)
+
+            job.cancel()
+            job2.cancel()
+            provider.stop()
+            provider2.stop()
+        }
     }
 
     @Test
@@ -165,11 +197,15 @@
         assertThat(wtfHandler.failed).isTrue()
     }
 
-    private fun listWithStep(step: BigDecimal): List<BigDecimal> {
+    private fun listWithStep(
+        step: BigDecimal,
+        start: BigDecimal = BigDecimal.ZERO,
+        stop: BigDecimal = BigDecimal.ONE,
+    ): List<BigDecimal> {
         val steps = mutableListOf<BigDecimal>()
 
-        var i = BigDecimal.ZERO
-        while (i.compareTo(BigDecimal.ONE) <= 0) {
+        var i = start
+        while (i.compareTo(stop) <= 0) {
             steps.add(i)
             i = (i + step).setScale(2, RoundingMode.HALF_UP)
         }
@@ -177,23 +213,43 @@
         return steps
     }
 
-    private fun assertSteps(steps: List<TransitionStep>, fractions: List<BigDecimal>) {
+    private fun assertSteps(
+        steps: List<TransitionStep>,
+        fractions: List<BigDecimal>,
+        from: KeyguardState,
+        to: KeyguardState,
+    ) {
         assertThat(steps[0])
-            .isEqualTo(TransitionStep(AOD, LOCKSCREEN, 0f, TransitionState.STARTED, OWNER_NAME))
+            .isEqualTo(
+                TransitionStep(
+                    from,
+                    to,
+                    fractions[0].toFloat(),
+                    TransitionState.STARTED,
+                    OWNER_NAME
+                )
+            )
         fractions.forEachIndexed { index, fraction ->
             assertThat(steps[index + 1])
                 .isEqualTo(
                     TransitionStep(
-                        AOD,
-                        LOCKSCREEN,
+                        from,
+                        to,
                         fraction.toFloat(),
                         TransitionState.RUNNING,
                         OWNER_NAME
                     )
                 )
         }
+        val lastValue = fractions[fractions.size - 1].toFloat()
+        val status =
+            if (lastValue < 1f) {
+                TransitionState.CANCELED
+            } else {
+                TransitionState.FINISHED
+            }
         assertThat(steps[steps.size - 1])
-            .isEqualTo(TransitionStep(AOD, LOCKSCREEN, 1f, TransitionState.FINISHED, OWNER_NAME))
+            .isEqualTo(TransitionStep(from, to, lastValue, status, OWNER_NAME))
 
         assertThat(wtfHandler.failed).isFalse()
     }
@@ -230,7 +286,7 @@
                 scope.launch {
                     frames.collect {
                         // Delay is required for AnimationHandler to properly register a callback
-                        delay(1)
+                        yield()
                         val (frameNumber, callback) = it
                         callback?.doFrame(frameNumber)
                     }
@@ -243,7 +299,7 @@
         }
 
         override fun postFrameCallback(cb: FrameCallback) {
-            frames.value = Pair(++frameCount, cb)
+            frames.value = Pair(frameCount++, cb)
         }
         override fun postCommitCallback(runnable: Runnable) {}
         override fun getFrameTime() = frameCount
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerInteractorTest.kt
index c85f7b9..21e5068 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerInteractorTest.kt
@@ -155,6 +155,7 @@
         mPrimaryBouncerInteractor.setPanelExpansion(EXPANSION_HIDDEN)
         verify(repository).setPrimaryVisible(false)
         verify(repository).setPrimaryShow(null)
+        verify(repository).setPrimaryHide(true)
         verify(falsingCollector).onBouncerHidden()
         verify(mPrimaryBouncerCallbackInteractor).dispatchReset()
         verify(mPrimaryBouncerCallbackInteractor).dispatchFullyHidden()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java
index df48e1d..808abc8 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java
@@ -1437,6 +1437,17 @@
     }
 
     @Test
+    public void behindTint_inKeyguardState_bouncerNotActive_usesKeyguardBehindTint() {
+        when(mStatusBarKeyguardViewManager.isPrimaryBouncerInTransit()).thenReturn(false);
+        mScrimController.setClipsQsScrim(false);
+
+        mScrimController.transitionTo(ScrimState.KEYGUARD);
+        finishAnimationsImmediately();
+        assertThat(mScrimBehind.getTint())
+                .isEqualTo(ScrimState.KEYGUARD.getBehindTint());
+    }
+
+    @Test
     public void testNotificationTransparency_followsTransitionToFullShade() {
         mScrimController.transitionTo(SHADE_LOCKED);
         mScrimController.setRawPanelExpansionFraction(1.0f);
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 6f70f0e..a798f40 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
@@ -44,6 +44,9 @@
     private val _isDozing = MutableStateFlow(false)
     override val isDozing: Flow<Boolean> = _isDozing
 
+    private val _isDreaming = MutableStateFlow(false)
+    override val isDreaming: Flow<Boolean> = _isDreaming
+
     private val _dozeAmount = MutableStateFlow(0f)
     override val dozeAmount: Flow<Float> = _dozeAmount
 
@@ -54,10 +57,13 @@
     override val wakefulnessState: Flow<WakefulnessModel> = _wakefulnessState
 
     private val _isUdfpsSupported = MutableStateFlow(false)
-    
+
     private val _isBouncerShowing = MutableStateFlow(false)
     override val isBouncerShowing: Flow<Boolean> = _isBouncerShowing
 
+    private val _isKeyguardGoingAway = MutableStateFlow(false)
+    override val isKeyguardGoingAway: Flow<Boolean> = _isKeyguardGoingAway
+
     private val _biometricUnlockState = MutableStateFlow(BiometricUnlockModel.NONE)
     override val biometricUnlockState: Flow<BiometricUnlockModel> = _biometricUnlockState