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:
@@ -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
|
||||
|
||||
@@ -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. */
|
||||
|
||||
@@ -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 */
|
||||
|
||||
@@ -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 }
|
||||
)
|
||||
}
|
||||
|
||||
@@ -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 }
|
||||
)
|
||||
}
|
||||
|
||||
@@ -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> {
|
||||
|
||||
@@ -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"
|
||||
)
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user