Transitions - Update for canceled state

When transitions are canceled a new one begins, existing animations
could be left in an incorrect state (position, alpha), so make sure to
end them where they should be. Also, if a transition is started
halfway, make sure to emit events to inform the ViewModel where it
should be at that point in time.

Also, prevent oscillating events for dreaming by adding in a slight
delay.

Bug: 260637747
Test: atest KeyguardScenariosTest

Change-Id: I03c9f25175f745e5eca93ab1ba74cd7f7005ee28
This commit is contained in:
Matt Pietal
2023-01-09 15:17:03 +00:00
parent d707fcf727
commit caedd59764
8 changed files with 70 additions and 44 deletions

View File

@@ -21,7 +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.Companion.isWakeAndUnlock
import com.android.systemui.keyguard.shared.model.BiometricUnlockModel
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
@@ -56,7 +56,7 @@ constructor(
scope.launch {
// Using isDreamingWithOverlay provides an optimized path to LOCKSCREEN state, which
// otherwise would have gone through OCCLUDED first
keyguardInteractor.isDreamingWithOverlay
keyguardInteractor.isAbleToDream
.sample(
combine(
keyguardInteractor.dozeTransitionModel,
@@ -65,8 +65,7 @@ constructor(
),
::toTriple
)
.collect { triple ->
val (isDreaming, dozeTransitionModel, lastStartedTransition) = triple
.collect { (isDreaming, dozeTransitionModel, lastStartedTransition) ->
if (
!isDreaming &&
isDozeOff(dozeTransitionModel.to) &&
@@ -96,8 +95,7 @@ constructor(
),
::toTriple
)
.collect { triple ->
val (isDreaming, isOccluded, lastStartedTransition) = triple
.collect { (isDreaming, isOccluded, lastStartedTransition) ->
if (
isOccluded &&
!isDreaming &&
@@ -123,24 +121,18 @@ constructor(
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(),
)
keyguardInteractor.biometricUnlockState.collect { biometricUnlockState ->
if (biometricUnlockState == BiometricUnlockModel.WAKE_AND_UNLOCK_FROM_DREAM) {
keyguardTransitionRepository.startTransition(
TransitionInfo(
name,
KeyguardState.DREAMING,
KeyguardState.GONE,
getAnimator(),
)
}
)
}
}
}
}
@@ -151,8 +143,7 @@ constructor(
keyguardTransitionInteractor.finishedKeyguardState,
::Pair
)
.collect { pair ->
val (dozeTransitionModel, keyguardState) = pair
.collect { (dozeTransitionModel, keyguardState) ->
if (
dozeTransitionModel.to == DozeStateModel.DOZE &&
keyguardState == KeyguardState.DREAMING

View File

@@ -32,12 +32,15 @@ import com.android.systemui.keyguard.shared.model.StatusBarState
import com.android.systemui.keyguard.shared.model.WakefulnessModel
import com.android.systemui.statusbar.CommandQueue
import com.android.systemui.statusbar.CommandQueue.Callbacks
import com.android.systemui.util.kotlin.sample
import javax.inject.Inject
import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.flow.merge
/**
@@ -89,15 +92,23 @@ constructor(
/**
* Dozing and dreaming have overlapping events. If the doze state remains in FINISH, it means
* that doze mode is not running and DREAMING is ok to commence.
*
* Allow a brief moment to prevent rapidly oscillating between true/false signals.
*/
val isAbleToDream: Flow<Boolean> =
merge(isDreaming, isDreamingWithOverlay)
.sample(
.combine(
dozeTransitionModel,
{ isDreaming, dozeTransitionModel ->
isDreaming && isDozeOff(dozeTransitionModel.to)
}
)
.flatMapLatest { isAbleToDream ->
flow {
delay(50)
emit(isAbleToDream)
}
}
.distinctUntilChanged()
/** Whether the keyguard is showing or not. */

View File

@@ -22,10 +22,14 @@ import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.keyguard.domain.interactor.FromDreamingTransitionInteractor.Companion.TO_LOCKSCREEN_DURATION
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
import com.android.systemui.keyguard.shared.model.AnimationParams
import com.android.systemui.keyguard.shared.model.TransitionState.CANCELED
import com.android.systemui.keyguard.shared.model.TransitionState.FINISHED
import javax.inject.Inject
import kotlin.time.Duration.Companion.milliseconds
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.merge
/**
* Breaks down DREAMING->LOCKSCREEN transition into discrete steps for corresponding views to
@@ -49,9 +53,15 @@ constructor(
/** Lockscreen views y-translation */
fun lockscreenTranslationY(translatePx: Int): Flow<Float> {
return flowForAnimation(LOCKSCREEN_TRANSLATION_Y).map { value ->
-translatePx + (EMPHASIZED_DECELERATE.getInterpolation(value) * translatePx)
}
return merge(
flowForAnimation(LOCKSCREEN_TRANSLATION_Y).map { value ->
-translatePx + (EMPHASIZED_DECELERATE.getInterpolation(value) * translatePx)
},
// On end, reset the translation to 0
interactor.dreamingToLockscreenTransition
.filter { it.transitionState == FINISHED || it.transitionState == CANCELED }
.map { 0f }
)
}
/** Lockscreen views alpha */

View File

@@ -21,7 +21,8 @@ import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.keyguard.domain.interactor.FromGoneTransitionInteractor.Companion.TO_DREAMING_DURATION
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
import com.android.systemui.keyguard.shared.model.AnimationParams
import com.android.systemui.keyguard.shared.model.TransitionState
import com.android.systemui.keyguard.shared.model.TransitionState.CANCELED
import com.android.systemui.keyguard.shared.model.TransitionState.FINISHED
import javax.inject.Inject
import kotlin.time.Duration.Companion.milliseconds
import kotlinx.coroutines.flow.Flow
@@ -45,7 +46,7 @@ constructor(
},
// On end, reset the translation to 0
interactor.goneToDreamingTransition
.filter { step -> step.transitionState == TransitionState.FINISHED }
.filter { it.transitionState == FINISHED || it.transitionState == CANCELED }
.map { 0f }
)
}

View File

@@ -21,7 +21,8 @@ import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.keyguard.domain.interactor.FromLockscreenTransitionInteractor.Companion.TO_DREAMING_DURATION
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
import com.android.systemui.keyguard.shared.model.AnimationParams
import com.android.systemui.keyguard.shared.model.TransitionState
import com.android.systemui.keyguard.shared.model.TransitionState.CANCELED
import com.android.systemui.keyguard.shared.model.TransitionState.FINISHED
import javax.inject.Inject
import kotlin.time.Duration.Companion.milliseconds
import kotlinx.coroutines.flow.Flow
@@ -48,7 +49,7 @@ constructor(
},
// On end, reset the translation to 0
interactor.lockscreenToDreamingTransition
.filter { step -> step.transitionState == TransitionState.FINISHED }
.filter { it.transitionState == FINISHED || it.transitionState == CANCELED }
.map { 0f }
)
}

View File

@@ -38,6 +38,7 @@ import com.android.systemui.util.mockito.withArgCaptor
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.cancelChildren
import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.advanceUntilIdle
import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
import org.junit.Before
@@ -177,7 +178,7 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() {
keyguardRepository.setDreamingWithOverlay(false)
// AND occluded has stopped
keyguardRepository.setKeyguardOccluded(false)
runCurrent()
advanceUntilIdle()
val info =
withArgCaptor<TransitionInfo> {
@@ -539,12 +540,11 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() {
},
)
)
runCurrent()
reset(mockTransitionRepository)
// WHEN the device begins to dream
keyguardRepository.setDreamingWithOverlay(true)
runCurrent()
advanceUntilIdle()
val info =
withArgCaptor<TransitionInfo> {

View File

@@ -92,8 +92,10 @@ class GoneToDreamingTransitionViewModelTest : SysuiTestCase() {
repository.sendTransitionStep(step(0.5f))
// ...up to here
repository.sendTransitionStep(step(1f))
// And a final reset event on CANCEL
repository.sendTransitionStep(step(0.8f, TransitionState.CANCELED))
assertThat(values.size).isEqualTo(3)
assertThat(values.size).isEqualTo(4)
assertThat(values[0])
.isEqualTo(
EMPHASIZED_ACCELERATE.getInterpolation(
@@ -112,6 +114,7 @@ class GoneToDreamingTransitionViewModelTest : SysuiTestCase() {
animValue(0.5f, LOCKSCREEN_TRANSLATION_Y)
) * pixels
)
assertThat(values[3]).isEqualTo(0f)
job.cancel()
}
@@ -123,12 +126,15 @@ class GoneToDreamingTransitionViewModelTest : SysuiTestCase() {
return (stepValue - startValue) * multiplier
}
private fun step(value: Float): TransitionStep {
private fun step(
value: Float,
state: TransitionState = TransitionState.RUNNING
): TransitionStep {
return TransitionStep(
from = KeyguardState.GONE,
to = KeyguardState.DREAMING,
value = value,
transitionState = TransitionState.RUNNING,
transitionState = state,
ownerName = "GoneToDreamingTransitionViewModelTest"
)
}

View File

@@ -67,8 +67,7 @@ class LockscreenToDreamingTransitionViewModelTest : SysuiTestCase() {
repository.sendTransitionStep(step(1f))
// Only three values should be present, since the dream overlay runs for a small
// fraction
// of the overall animation time
// fraction of the overall animation time
assertThat(values.size).isEqualTo(3)
assertThat(values[0]).isEqualTo(1f - animValue(0f, LOCKSCREEN_ALPHA))
assertThat(values[1]).isEqualTo(1f - animValue(0.1f, LOCKSCREEN_ALPHA))
@@ -92,8 +91,10 @@ class LockscreenToDreamingTransitionViewModelTest : SysuiTestCase() {
repository.sendTransitionStep(step(0.5f))
// ...up to here
repository.sendTransitionStep(step(1f))
// And a final reset event on FINISHED
repository.sendTransitionStep(step(1f, TransitionState.FINISHED))
assertThat(values.size).isEqualTo(3)
assertThat(values.size).isEqualTo(4)
assertThat(values[0])
.isEqualTo(
EMPHASIZED_ACCELERATE.getInterpolation(
@@ -112,6 +113,8 @@ class LockscreenToDreamingTransitionViewModelTest : SysuiTestCase() {
animValue(0.5f, LOCKSCREEN_TRANSLATION_Y)
) * pixels
)
assertThat(values[3]).isEqualTo(0f)
job.cancel()
}
@@ -123,12 +126,15 @@ class LockscreenToDreamingTransitionViewModelTest : SysuiTestCase() {
return (stepValue - startValue) * multiplier
}
private fun step(value: Float): TransitionStep {
private fun step(
value: Float,
state: TransitionState = TransitionState.RUNNING
): TransitionStep {
return TransitionStep(
from = KeyguardState.LOCKSCREEN,
to = KeyguardState.DREAMING,
value = value,
transitionState = TransitionState.RUNNING,
transitionState = state,
ownerName = "LockscreenToDreamingTransitionViewModelTest"
)
}