Merge changes I33b84f05,I3f746a2c into main

* changes:
  Add transitions between glanceable hub and dozing
  Add helper methods to CommunalInteractor
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt
index 7769223..2119060 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt
@@ -557,6 +557,43 @@
         }
 
     @Test
+    fun isIdleOnCommunal() =
+        testScope.runTest {
+            val transitionState =
+                MutableStateFlow<ObservableCommunalTransitionState>(
+                    ObservableCommunalTransitionState.Idle(CommunalSceneKey.Blank)
+                )
+            communalRepository.setTransitionState(transitionState)
+
+            // isIdleOnCommunal is false when not on communal.
+            val isIdleOnCommunal by collectLastValue(underTest.isIdleOnCommunal)
+            runCurrent()
+            assertThat(isIdleOnCommunal).isEqualTo(false)
+
+            // Transition to communal.
+            transitionState.value =
+                ObservableCommunalTransitionState.Idle(CommunalSceneKey.Communal)
+            runCurrent()
+
+            // isIdleOnCommunal is now true since we're on communal.
+            assertThat(isIdleOnCommunal).isEqualTo(true)
+
+            // Start transition away from communal.
+            transitionState.value =
+                ObservableCommunalTransitionState.Transition(
+                    fromScene = CommunalSceneKey.Communal,
+                    toScene = CommunalSceneKey.Blank,
+                    progress = flowOf(0f),
+                    isInitiatedByUserInput = false,
+                    isUserInputOngoing = flowOf(false),
+                )
+            runCurrent()
+
+            // isIdleOnCommunal turns false as soon as transition away starts.
+            assertThat(isIdleOnCommunal).isEqualTo(false)
+        }
+
+    @Test
     fun testShowWidgetEditorStartsActivity() =
         testScope.runTest {
             underTest.showWidgetEditor()
diff --git a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt
index b6180cb..92d01db 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt
@@ -56,7 +56,7 @@
 class CommunalInteractor
 @Inject
 constructor(
-    @Application private val applicationScope: CoroutineScope,
+    @Application applicationScope: CoroutineScope,
     private val communalRepository: CommunalRepository,
     private val widgetRepository: CommunalWidgetRepository,
     private val communalPrefsRepository: CommunalPrefsRepository,
@@ -134,6 +134,17 @@
     val isCommunalShowing: Flow<Boolean> =
         communalRepository.desiredScene.map { it == CommunalSceneKey.Communal }
 
+    /**
+     * Flow that emits a boolean if the communal UI is fully visible and not in transition.
+     *
+     * This will not be true while transitioning to the hub and will turn false immediately when a
+     * swipe to exit the hub starts.
+     */
+    val isIdleOnCommunal: Flow<Boolean> =
+        communalRepository.transitionState.map {
+            it is ObservableCommunalTransitionState.Idle && it.scene == CommunalSceneKey.Communal
+        }
+
     val isKeyguardVisible: Flow<Boolean> = keyguardInteractor.isKeyguardVisible
 
     /** Callback received whenever the [SceneTransitionLayout] finishes a scene transition. */
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 fcb7698..e2a8b6c 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
@@ -18,6 +18,7 @@
 
 import android.animation.ValueAnimator
 import com.android.app.animation.Interpolators
+import com.android.systemui.communal.domain.interactor.CommunalInteractor
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Background
 import com.android.systemui.dagger.qualifiers.Main
@@ -25,7 +26,7 @@
 import com.android.systemui.keyguard.shared.model.BiometricUnlockModel.Companion.isWakeAndUnlock
 import com.android.systemui.keyguard.shared.model.KeyguardState
 import com.android.systemui.power.domain.interactor.PowerInteractor
-import com.android.systemui.util.kotlin.Utils.Companion.toTriple
+import com.android.systemui.util.kotlin.Utils.Companion.toQuad
 import com.android.systemui.util.kotlin.sample
 import javax.inject.Inject
 import kotlin.time.Duration.Companion.milliseconds
@@ -45,6 +46,7 @@
     @Main mainDispatcher: CoroutineDispatcher,
     private val keyguardInteractor: KeyguardInteractor,
     private val powerInteractor: PowerInteractor,
+    private val communalInteractor: CommunalInteractor,
 ) :
     TransitionInteractor(
         fromState = KeyguardState.DOZING,
@@ -54,26 +56,33 @@
     ) {
 
     override fun start() {
-        listenForDozingToLockscreenOrOccluded()
+        listenForDozingToLockscreenHubOrOccluded()
         listenForDozingToGone()
         listenForTransitionToCamera(scope, keyguardInteractor)
     }
 
-    private fun listenForDozingToLockscreenOrOccluded() {
+    private fun listenForDozingToLockscreenHubOrOccluded() {
         scope.launch {
             powerInteractor.isAwake
                 .sample(
                     combine(
                         startedKeyguardTransitionStep,
                         keyguardInteractor.isKeyguardOccluded,
-                        ::Pair
+                        communalInteractor.isIdleOnCommunal,
+                        ::Triple
                     ),
-                    ::toTriple
+                    ::toQuad
                 )
-                .collect { (isAwake, lastStartedTransition, occluded) ->
+                .collect { (isAwake, lastStartedTransition, occluded, isIdleOnCommunal) ->
                     if (isAwake && lastStartedTransition.to == KeyguardState.DOZING) {
                         startTransitionTo(
-                            if (occluded) KeyguardState.OCCLUDED else KeyguardState.LOCKSCREEN
+                            if (occluded) {
+                                KeyguardState.OCCLUDED
+                            } else if (isIdleOnCommunal) {
+                                KeyguardState.GLANCEABLE_HUB
+                            } else {
+                                KeyguardState.LOCKSCREEN
+                            }
                         )
                     }
                 }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGlanceableHubTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGlanceableHubTransitionInteractor.kt
index 48b3d9a..3292ea8 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGlanceableHubTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGlanceableHubTransitionInteractor.kt
@@ -25,17 +25,24 @@
 import com.android.systemui.dagger.qualifiers.Main
 import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository
 import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.keyguard.shared.model.TransitionModeOnCanceled
+import com.android.systemui.power.domain.interactor.PowerInteractor
+import com.android.systemui.util.kotlin.sample
 import javax.inject.Inject
 import kotlin.time.Duration.Companion.milliseconds
 import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.launch
 
 @SysUISingleton
 class FromGlanceableHubTransitionInteractor
 @Inject
 constructor(
+    @Background private val scope: CoroutineScope,
     private val glanceableHubTransitions: GlanceableHubTransitions,
     override val transitionRepository: KeyguardTransitionRepository,
     transitionInteractor: KeyguardTransitionInteractor,
+    private val powerInteractor: PowerInteractor,
     @Main mainDispatcher: CoroutineDispatcher,
     @Background bgDispatcher: CoroutineDispatcher,
 ) :
@@ -50,6 +57,7 @@
             return
         }
         listenForHubToLockscreen()
+        listenForHubToDozing()
     }
 
     override fun getDefaultAnimatorForTransitionsToState(toState: KeyguardState): ValueAnimator {
@@ -71,6 +79,20 @@
         )
     }
 
+    private fun listenForHubToDozing() {
+        scope.launch {
+            powerInteractor.isAsleep.sample(startedKeyguardTransitionStep, ::Pair).collect {
+                (isAsleep, lastStartedStep) ->
+                if (lastStartedStep.to == fromState && isAsleep) {
+                    startTransitionTo(
+                        toState = KeyguardState.DOZING,
+                        modeOnCanceled = TransitionModeOnCanceled.LAST_VALUE,
+                    )
+                }
+            }
+        }
+    }
+
     companion object {
         const val TAG = "FromGlanceableHubTransitionInteractor"
         val DEFAULT_DURATION = 500.milliseconds
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt
index 8b6611f..90eaa5a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt
@@ -250,6 +250,7 @@
                     transitionRepository = transitionRepository,
                     transitionInteractor = transitionInteractor,
                     powerInteractor = powerInteractor,
+                    communalInteractor = communalInteractor,
                 )
                 .apply { start() }
 
@@ -279,11 +280,13 @@
 
         fromGlanceableHubTransitionInteractor =
             FromGlanceableHubTransitionInteractor(
+                    scope = testScope,
                     bgDispatcher = testDispatcher,
                     mainDispatcher = testDispatcher,
                     glanceableHubTransitions = glanceableHubTransitions,
                     transitionRepository = transitionRepository,
                     transitionInteractor = transitionInteractor,
+                    powerInteractor = powerInteractor,
                 )
                 .apply { start() }
     }
@@ -725,6 +728,38 @@
         }
 
     @Test
+    fun dozingToGlanceableHub() =
+        testScope.runTest {
+            // GIVEN a prior transition has run to DOZING
+            runTransitionAndSetWakefulness(KeyguardState.GLANCEABLE_HUB, KeyguardState.DOZING)
+            runCurrent()
+
+            // GIVEN the device is idle on the glanceable hub
+            val idleTransitionState =
+                MutableStateFlow<ObservableCommunalTransitionState>(
+                    ObservableCommunalTransitionState.Idle(CommunalSceneKey.Communal)
+                )
+            communalInteractor.setTransitionState(idleTransitionState)
+            runCurrent()
+
+            // WHEN the device begins to wake
+            powerInteractor.setAwakeForTest()
+            runCurrent()
+
+            val info =
+                withArgCaptor<TransitionInfo> {
+                    verify(transitionRepository).startTransition(capture())
+                }
+            // THEN a transition to DOZING should occur
+            assertThat(info.ownerName).isEqualTo(FromDozingTransitionInteractor::class.simpleName)
+            assertThat(info.from).isEqualTo(KeyguardState.DOZING)
+            assertThat(info.to).isEqualTo(KeyguardState.GLANCEABLE_HUB)
+            assertThat(info.animator).isNotNull()
+
+            coroutineContext.cancelChildren()
+        }
+
+    @Test
     fun goneToDozing() =
         testScope.runTest {
             // GIVEN a device with AOD not available
@@ -1577,6 +1612,30 @@
             coroutineContext.cancelChildren()
         }
 
+    @Test
+    fun glanceableHubToDozing() =
+        testScope.runTest {
+            // GIVEN a prior transition has run to GLANCEABLE_HUB
+            runTransitionAndSetWakefulness(KeyguardState.LOCKSCREEN, KeyguardState.GLANCEABLE_HUB)
+
+            // WHEN the device begins to sleep
+            powerInteractor.setAsleepForTest()
+            runCurrent()
+
+            val info =
+                withArgCaptor<TransitionInfo> {
+                    verify(transitionRepository).startTransition(capture())
+                }
+            // THEN a transition to DOZING should occur
+            assertThat(info.ownerName)
+                .isEqualTo(FromGlanceableHubTransitionInteractor::class.simpleName)
+            assertThat(info.from).isEqualTo(KeyguardState.GLANCEABLE_HUB)
+            assertThat(info.to).isEqualTo(KeyguardState.DOZING)
+            assertThat(info.animator).isNotNull()
+
+            coroutineContext.cancelChildren()
+        }
+
     private fun createKeyguardInteractor(): KeyguardInteractor {
         return KeyguardInteractorFactory.create(
                 featureFlags = featureFlags,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/CommunalInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/CommunalInteractorKosmos.kt
index d8f0cec..92193fd 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/CommunalInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/CommunalInteractorKosmos.kt
@@ -24,14 +24,14 @@
 import com.android.systemui.communal.widgets.CommunalAppWidgetHost
 import com.android.systemui.communal.widgets.EditWidgetsActivityStarter
 import com.android.systemui.kosmos.Kosmos
-import com.android.systemui.kosmos.testScope
+import com.android.systemui.kosmos.applicationCoroutineScope
 import com.android.systemui.smartspace.data.repository.smartspaceRepository
 import org.mockito.Mockito.mock
 
 val Kosmos.communalInteractor by
     Kosmos.Fixture {
         CommunalInteractor(
-            applicationScope = testScope.backgroundScope,
+            applicationScope = applicationCoroutineScope,
             communalRepository = communalRepository,
             widgetRepository = communalWidgetRepository,
             mediaRepository = communalMediaRepository,