Transitions - Add dreaming state

Add support for states in and out of dreaming. At the moment, AOD is
equivalent to DOZING but these will be separated out in the future.

Test: atest KeyguardTransitionRepositoryTest
KeyguardRepositoryImplTest
Bug: 195430376

Change-Id: I73b51987f01540cc3d321f23286a3344d0847f9c
This commit is contained in:
Matt Pietal
2022-11-10 13:13:41 +00:00
parent d3783f7bac
commit a9094c9563
11 changed files with 265 additions and 23 deletions

View File

@@ -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
@@ -87,6 +88,14 @@ interface KeyguardRepository {
*/
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.
*
@@ -139,12 +148,12 @@ interface KeyguardRepository {
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 =
@@ -244,6 +253,25 @@ constructor(
}
.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 {

View File

@@ -112,7 +112,7 @@ class KeyguardTransitionRepositoryImpl @Inject constructor() : KeyguardTransitio
// Seed with transitions signaling a boot into lockscreen state
emitTransition(
TransitionStep(
KeyguardState.NONE,
KeyguardState.OFF,
KeyguardState.LOCKSCREEN,
0f,
TransitionState.STARTED,
@@ -120,7 +120,7 @@ class KeyguardTransitionRepositoryImpl @Inject constructor() : KeyguardTransitio
)
emitTransition(
TransitionStep(
KeyguardState.NONE,
KeyguardState.OFF,
KeyguardState.LOCKSCREEN,
1f,
TransitionState.FINISHED,

View File

@@ -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
}
}

View File

@@ -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
}
}

View File

@@ -41,6 +41,11 @@ constructor(
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. */

View File

@@ -43,6 +43,8 @@ constructor(
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()
}

View File

@@ -51,4 +51,14 @@ abstract class StartKeyguardTransitionModule {
@Binds
@IntoSet
abstract fun lockscreenGone(impl: LockscreenGoneTransitionInteractor): TransitionInteractor
@Binds
@IntoSet
abstract fun dreamingLockscreen(
impl: DreamingLockscreenTransitionInteractor
): TransitionInteractor
@Binds
@IntoSet
abstract fun dreamingToAod(impl: DreamingToAodTransitionInteractor): TransitionInteractor
}

View File

@@ -17,12 +17,29 @@ package com.android.systemui.keyguard.shared.model
/** 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.
*/
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.
*/
NONE,
/* Always-on Display. The device is in a low-power mode with a minimal UI visible */
AOD,
/*
* The security screen prompt UI, containing PIN, Password, Pattern, and all FPS
@@ -34,7 +51,6 @@ enum class KeyguardState {
* 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

View File

@@ -17,8 +17,8 @@ package com.android.systemui.keyguard.shared.model
/** 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 = "",

View File

@@ -18,6 +18,7 @@ package com.android.systemui.keyguard.data.repository
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 @@ class KeyguardRepositoryImplTest : SysuiTestCase() {
underTest =
KeyguardRepositoryImpl(
statusBarStateController,
dozeHost,
wakefulnessLifecycle,
biometricUnlockController,
keyguardStateController,
keyguardUpdateMonitor,
statusBarStateController,
dozeHost,
wakefulnessLifecycle,
biometricUnlockController,
keyguardStateController,
keyguardUpdateMonitor,
)
}
@@ -278,6 +279,26 @@ class KeyguardRepositoryImplTest : SysuiTestCase() {
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>()

View File

@@ -44,6 +44,9 @@ class FakeKeyguardRepository : KeyguardRepository {
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,7 +57,7 @@ class FakeKeyguardRepository : KeyguardRepository {
override val wakefulnessState: Flow<WakefulnessModel> = _wakefulnessState
private val _isUdfpsSupported = MutableStateFlow(false)
private val _isBouncerShowing = MutableStateFlow(false)
override val isBouncerShowing: Flow<Boolean> = _isBouncerShowing