Transitions - Add dozing information

Dozing is separate state, apart from AOD, where low power sensors are
enabled but the screen is not active. Begin accounting for this state
during transitions.

Also, use the class name for logging.

Also, recover dreaming state after a crash.

Test: atest KeyguardRepositoryImplTest
KeyguardTransitionRepositoryTest
Bug: 195430376
Fixes: 259782960

Change-Id: Id887c886c4a1df4d4c466b5ea9f4c4507bfcd50e
This commit is contained in:
Matt Pietal
2022-11-22 19:15:04 +00:00
parent 73c694161c
commit 2a314ee828
16 changed files with 228 additions and 208 deletions

View File

@@ -116,6 +116,7 @@ class KeyguardTransitionRepositoryImpl @Inject constructor() : KeyguardTransitio
KeyguardState.LOCKSCREEN,
0f,
TransitionState.STARTED,
KeyguardTransitionRepositoryImpl::class.simpleName!!,
)
)
emitTransition(
@@ -124,6 +125,7 @@ class KeyguardTransitionRepositoryImpl @Inject constructor() : KeyguardTransitio
KeyguardState.LOCKSCREEN,
1f,
TransitionState.FINISHED,
KeyguardTransitionRepositoryImpl::class.simpleName!!,
)
)
}

View File

@@ -41,17 +41,14 @@ constructor(
override fun start() {
listenForTransitionToAodFromLockscreen()
listenForTransitionToLockscreenFromAod()
listenForTransitionToLockscreenFromDozeStates()
}
private fun listenForTransitionToAodFromLockscreen() {
scope.launch {
keyguardInteractor
.dozeTransitionTo(DozeStateModel.DOZE_AOD)
.sample(
keyguardTransitionInteractor.startedKeyguardTransitionStep,
{ a, b -> Pair(a, b) }
)
.sample(keyguardTransitionInteractor.startedKeyguardTransitionStep, ::Pair)
.collect { pair ->
val (dozeToAod, lastStartedStep) = pair
if (lastStartedStep.to == KeyguardState.LOCKSCREEN) {
@@ -68,21 +65,19 @@ constructor(
}
}
private fun listenForTransitionToLockscreenFromAod() {
private fun listenForTransitionToLockscreenFromDozeStates() {
val canGoToLockscreen = setOf(KeyguardState.AOD, KeyguardState.DOZING)
scope.launch {
keyguardInteractor
.dozeTransitionTo(DozeStateModel.FINISH)
.sample(
keyguardTransitionInteractor.startedKeyguardTransitionStep,
{ a, b -> Pair(a, b) }
)
.sample(keyguardTransitionInteractor.startedKeyguardTransitionStep, ::Pair)
.collect { pair ->
val (dozeToAod, lastStartedStep) = pair
if (lastStartedStep.to == KeyguardState.AOD) {
if (canGoToLockscreen.contains(lastStartedStep.to)) {
keyguardTransitionRepository.startTransition(
TransitionInfo(
name,
KeyguardState.AOD,
lastStartedStep.to,
KeyguardState.LOCKSCREEN,
getAnimator(),
)

View File

@@ -21,9 +21,7 @@ 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.BiometricUnlockModel.WAKE_AND_UNLOCK
import com.android.systemui.keyguard.shared.model.BiometricUnlockModel.WAKE_AND_UNLOCK_FROM_DREAM
import com.android.systemui.keyguard.shared.model.BiometricUnlockModel.WAKE_AND_UNLOCK_PULSING
import com.android.systemui.keyguard.shared.model.BiometricUnlockModel.Companion.isWakeAndUnlock
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.keyguard.shared.model.TransitionInfo
import com.android.systemui.util.kotlin.sample
@@ -42,9 +40,6 @@ constructor(
private val keyguardTransitionInteractor: KeyguardTransitionInteractor,
) : TransitionInteractor(AodToGoneTransitionInteractor::class.simpleName!!) {
private val wakeAndUnlockModes =
setOf(WAKE_AND_UNLOCK, WAKE_AND_UNLOCK_FROM_DREAM, WAKE_AND_UNLOCK_PULSING)
override fun start() {
scope.launch {
keyguardInteractor.biometricUnlockState
@@ -52,8 +47,7 @@ constructor(
.collect { pair ->
val (biometricUnlockState, keyguardState) = pair
if (
keyguardState == KeyguardState.AOD &&
wakeAndUnlockModes.contains(biometricUnlockState)
keyguardState == KeyguardState.AOD && isWakeAndUnlock(biometricUnlockState)
) {
keyguardTransitionRepository.startTransition(
TransitionInfo(

View File

@@ -1,94 +0,0 @@
/*
* 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.DozeStateModel
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.flow.combine
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(DreamingLockscreenTransitionInteractor::class.simpleName!!) {
override fun start() {
scope.launch {
keyguardInteractor.isDreaming
.sample(
combine(
keyguardInteractor.dozeTransitionModel,
keyguardTransitionInteractor.finishedKeyguardState
) { a, b -> Pair(a, b) },
{ a, bc -> Triple(a, bc.first, bc.second) }
)
.collect { triple ->
val (isDreaming, dozeTransitionModel, keyguardState) = triple
// Dozing/AOD and dreaming have overlapping events. If the state remains in
// FINISH, it means that doze mode is not running and DREAMING is ok to
// commence.
if (dozeTransitionModel.to == DozeStateModel.FINISH) {
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

@@ -1,76 +0,0 @@
/*
* 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.wakefulnessModel
.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

@@ -0,0 +1,180 @@
/*
* 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.BiometricUnlockModel.Companion.isWakeAndUnlock
import com.android.systemui.keyguard.shared.model.DozeStateModel
import com.android.systemui.keyguard.shared.model.DozeStateModel.Companion.isDozeOff
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.flow.combine
import kotlinx.coroutines.launch
@SysUISingleton
class DreamingTransitionInteractor
@Inject
constructor(
@Application private val scope: CoroutineScope,
private val keyguardInteractor: KeyguardInteractor,
private val keyguardTransitionRepository: KeyguardTransitionRepository,
private val keyguardTransitionInteractor: KeyguardTransitionInteractor,
) : TransitionInteractor(DreamingTransitionInteractor::class.simpleName!!) {
private val canDreamFrom =
setOf(KeyguardState.LOCKSCREEN, KeyguardState.GONE, KeyguardState.DOZING)
override fun start() {
listenForEntryToDreaming()
listenForDreamingToLockscreen()
listenForDreamingToGone()
listenForDreamingToDozing()
}
private fun listenForEntryToDreaming() {
scope.launch {
keyguardInteractor.isDreaming
.sample(
combine(
keyguardInteractor.dozeTransitionModel,
keyguardTransitionInteractor.finishedKeyguardState,
::Pair
),
::toTriple
)
.collect { triple ->
val (isDreaming, dozeTransitionModel, keyguardState) = triple
// Dozing/AOD and dreaming have overlapping events. If the state remains in
// FINISH, it means that doze mode is not running and DREAMING is ok to
// commence.
if (
isDozeOff(dozeTransitionModel.to) &&
isDreaming &&
canDreamFrom.contains(keyguardState)
) {
keyguardTransitionRepository.startTransition(
TransitionInfo(
name,
keyguardState,
KeyguardState.DREAMING,
getAnimator(),
)
)
}
}
}
}
private fun listenForDreamingToLockscreen() {
scope.launch {
keyguardInteractor.isDreaming
.sample(
combine(
keyguardInteractor.dozeTransitionModel,
keyguardTransitionInteractor.startedKeyguardTransitionStep,
::Pair,
),
::toTriple
)
.collect { triple ->
val (isDreaming, dozeTransitionModel, lastStartedTransition) = triple
if (
isDozeOff(dozeTransitionModel.to) &&
!isDreaming &&
lastStartedTransition.to == KeyguardState.DREAMING
) {
keyguardTransitionRepository.startTransition(
TransitionInfo(
name,
KeyguardState.DREAMING,
KeyguardState.LOCKSCREEN,
getAnimator(),
)
)
}
}
}
}
private fun listenForDreamingToGone() {
scope.launch {
keyguardInteractor.biometricUnlockState
.sample(keyguardTransitionInteractor.finishedKeyguardState, ::Pair)
.collect { pair ->
val (biometricUnlockState, keyguardState) = pair
if (
keyguardState == KeyguardState.DREAMING &&
isWakeAndUnlock(biometricUnlockState)
) {
keyguardTransitionRepository.startTransition(
TransitionInfo(
name,
KeyguardState.DREAMING,
KeyguardState.GONE,
getAnimator(),
)
)
}
}
}
}
private fun listenForDreamingToDozing() {
scope.launch {
combine(
keyguardInteractor.dozeTransitionModel,
keyguardTransitionInteractor.finishedKeyguardState,
::Pair
)
.collect { pair ->
val (dozeTransitionModel, keyguardState) = pair
if (
dozeTransitionModel.to == DozeStateModel.DOZE &&
keyguardState == KeyguardState.DREAMING
) {
keyguardTransitionRepository.startTransition(
TransitionInfo(
name,
KeyguardState.DREAMING,
KeyguardState.DOZING,
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

@@ -37,7 +37,7 @@ constructor(
private val keyguardInteractor: KeyguardInteractor,
private val keyguardTransitionRepository: KeyguardTransitionRepository,
private val keyguardTransitionInteractor: KeyguardTransitionInteractor,
) : TransitionInteractor("GONE->AOD") {
) : TransitionInteractor(GoneAodTransitionInteractor::class.simpleName!!) {
override fun start() {
scope.launch {

View File

@@ -46,6 +46,8 @@ constructor(
scope.launch { keyguardInteractor.isDozing.collect { logger.v("isDozing", it) } }
scope.launch { keyguardInteractor.isDreaming.collect { logger.v("isDreaming", it) } }
scope.launch {
interactor.finishedKeyguardTransitionStep.collect {
logger.i("Finished transition", it)
@@ -61,5 +63,9 @@ constructor(
scope.launch {
interactor.startedKeyguardTransitionStep.collect { logger.i("Started transition", it) }
}
scope.launch {
keyguardInteractor.dozeTransitionModel.collect { logger.i("Doze transition", it) }
}
}
}

View File

@@ -43,8 +43,7 @@ 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")
is DreamingTransitionInteractor -> Log.d(TAG, "Started $it")
}
it.start()
}

View File

@@ -37,15 +37,15 @@ constructor(
private val keyguardInteractor: KeyguardInteractor,
private val keyguardTransitionInteractor: KeyguardTransitionInteractor,
private val keyguardTransitionRepository: KeyguardTransitionRepository,
) : TransitionInteractor("LOCKSCREEN->GONE") {
) : TransitionInteractor(LockscreenGoneTransitionInteractor::class.simpleName!!) {
override fun start() {
scope.launch {
keyguardInteractor.isKeyguardGoingAway
.sample(keyguardTransitionInteractor.finishedKeyguardState, { a, b -> Pair(a, b) })
.sample(keyguardTransitionInteractor.startedKeyguardTransitionStep, ::Pair)
.collect { pair ->
val (isKeyguardGoingAway, keyguardState) = pair
if (!isKeyguardGoingAway && keyguardState == KeyguardState.LOCKSCREEN) {
val (isKeyguardGoingAway, lastStartedStep) = pair
if (isKeyguardGoingAway && lastStartedStep.to == KeyguardState.LOCKSCREEN) {
keyguardTransitionRepository.startTransition(
TransitionInfo(
name,

View File

@@ -52,13 +52,5 @@ abstract class StartKeyguardTransitionModule {
@IntoSet
abstract fun lockscreenGone(impl: LockscreenGoneTransitionInteractor): TransitionInteractor
@Binds
@IntoSet
abstract fun dreamingLockscreen(
impl: DreamingLockscreenTransitionInteractor
): TransitionInteractor
@Binds
@IntoSet
abstract fun dreamingToAod(impl: DreamingToAodTransitionInteractor): TransitionInteractor
@Binds @IntoSet abstract fun dreaming(impl: DreamingTransitionInteractor): TransitionInteractor
}

View File

@@ -29,4 +29,6 @@ package com.android.systemui.keyguard.domain.interactor
sealed class TransitionInteractor(val name: String) {
abstract fun start()
fun <A, B, C> toTriple(a: A, bc: Pair<B, C>) = Triple(a, bc.first, bc.second)
}

View File

@@ -46,5 +46,14 @@ enum class BiometricUnlockModel {
/** When bouncer is visible and will be dismissed. */
DISMISS_BOUNCER,
/** Mode in which fingerprint wakes and unlocks the device from a dream. */
WAKE_AND_UNLOCK_FROM_DREAM,
WAKE_AND_UNLOCK_FROM_DREAM;
companion object {
private val wakeAndUnlockModes =
setOf(WAKE_AND_UNLOCK, WAKE_AND_UNLOCK_FROM_DREAM, WAKE_AND_UNLOCK_PULSING)
fun isWakeAndUnlock(model: BiometricUnlockModel): Boolean {
return wakeAndUnlockModes.contains(model)
}
}
}

View File

@@ -42,5 +42,11 @@ enum class DozeStateModel {
/** AOD, prox is near, transitions to DOZE_AOD_PAUSED after a timeout. */
DOZE_AOD_PAUSING,
/** Always-on doze. Device is awake, showing docking UI and listening for pulse triggers. */
DOZE_AOD_DOCKED
DOZE_AOD_DOCKED;
companion object {
fun isDozeOff(model: DozeStateModel): Boolean {
return model == UNINITIALIZED || model == FINISH
}
}
}

View File

@@ -22,6 +22,7 @@ import android.util.Log
import android.util.Log.TerribleFailure
import android.util.Log.TerribleFailureHandler
import android.view.Choreographer.FrameCallback
import androidx.test.filters.FlakyTest
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.animation.Interpolators
@@ -97,6 +98,7 @@ class KeyguardTransitionRepositoryTest : SysuiTestCase() {
}
@Test
@FlakyTest(bugId = 260213291)
fun `starting second transition will cancel the first transition`() {
runBlocking(IMMEDIATE) {
val (animator, provider) = setupAnimator(this)

View File

@@ -202,6 +202,9 @@ public class KeyguardServiceDelegate {
if (!mKeyguardState.enabled) {
mKeyguardService.setKeyguardEnabled(mKeyguardState.enabled);
}
if (mKeyguardState.dreaming) {
mKeyguardService.onDreamingStarted();
}
}
@Override