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,