diff options
16 files changed, 537 insertions, 450 deletions
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 ce61f2fec92f..86f65dde4031 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 @@ -21,6 +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.KeyguardState import com.android.systemui.keyguard.shared.model.TransitionInfo import com.android.systemui.keyguard.shared.model.WakefulnessModel.Companion.isWakingOrStartingToWake @@ -44,6 +45,7 @@ constructor( override fun start() { listenForDozingToLockscreen() + listenForDozingToGone() } private fun listenForDozingToLockscreen() { @@ -68,6 +70,28 @@ constructor( } } + private fun listenForDozingToGone() { + scope.launch { + keyguardInteractor.biometricUnlockState + .sample(keyguardTransitionInteractor.startedKeyguardTransitionStep, ::Pair) + .collect { (biometricUnlockState, lastStartedTransition) -> + if ( + lastStartedTransition.to == KeyguardState.DOZING && + isWakeAndUnlock(biometricUnlockState) + ) { + keyguardTransitionRepository.startTransition( + TransitionInfo( + name, + KeyguardState.DOZING, + KeyguardState.GONE, + getAnimator(), + ) + ) + } + } + } + } + private fun getAnimator(duration: Duration = DEFAULT_DURATION): ValueAnimator { return ValueAnimator().apply { setInterpolator(Interpolators.LINEAR) diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt index ad6dbea7ae43..53c80f65e44f 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt @@ -19,7 +19,6 @@ package com.android.systemui.keyguard.domain.interactor import com.android.systemui.dagger.SysUISingleton import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository -import com.android.systemui.keyguard.shared.model.AnimationParams import com.android.systemui.keyguard.shared.model.KeyguardState import com.android.systemui.keyguard.shared.model.KeyguardState.AOD import com.android.systemui.keyguard.shared.model.KeyguardState.BOUNCER @@ -31,9 +30,6 @@ import com.android.systemui.keyguard.shared.model.TransitionState import com.android.systemui.keyguard.shared.model.TransitionState.STARTED import com.android.systemui.keyguard.shared.model.TransitionStep import javax.inject.Inject -import kotlin.math.max -import kotlin.math.min -import kotlin.time.Duration import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.filter import kotlinx.coroutines.flow.map @@ -104,38 +100,4 @@ constructor( /* The last completed [KeyguardState] transition */ val finishedKeyguardState: Flow<KeyguardState> = finishedKeyguardTransitionStep.map { step -> step.to } - - /** - * Transitions will occur over a [totalDuration] with [TransitionStep]s being emitted in the - * range of [0, 1]. View animations should begin and end within a subset of this range. This - * function maps the [startTime] and [duration] into [0, 1], when this subset is valid. - */ - fun transitionStepAnimation( - flow: Flow<TransitionStep>, - params: AnimationParams, - totalDuration: Duration, - ): Flow<Float> { - val start = (params.startTime / totalDuration).toFloat() - val chunks = (totalDuration / params.duration).toFloat() - var isRunning = false - return flow - .map { step -> - val value = (step.value - start) * chunks - if (step.transitionState == STARTED) { - // When starting, make sure to always emit. If a transition is started from the - // middle, it is possible this animation is being skipped but we need to inform - // the ViewModels of the last update - isRunning = true - max(0f, min(1f, value)) - } else if (isRunning && value >= 1f) { - // Always send a final value of 1. Because of rounding, [value] may never be - // exactly 1. - isRunning = false - 1f - } else { - value - } - } - .filter { value -> value >= 0f && value <= 1f } - } } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/AnimationParams.kt b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/AnimationParams.kt deleted file mode 100644 index 67733e905268..000000000000 --- a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/AnimationParams.kt +++ /dev/null @@ -1,25 +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.shared.model - -import kotlin.time.Duration -import kotlin.time.Duration.Companion.milliseconds - -/** Animation parameters */ -data class AnimationParams( - val startTime: Duration = 0.milliseconds, - val duration: Duration, -) diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/KeyguardTransitionAnimationFlow.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/KeyguardTransitionAnimationFlow.kt new file mode 100644 index 000000000000..ca1e27c9d19c --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/KeyguardTransitionAnimationFlow.kt @@ -0,0 +1,106 @@ +/* + * Copyright (C) 2023 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.ui + +import android.view.animation.Interpolator +import com.android.systemui.animation.Interpolators.LINEAR +import com.android.systemui.keyguard.shared.model.TransitionState.CANCELED +import com.android.systemui.keyguard.shared.model.TransitionState.FINISHED +import com.android.systemui.keyguard.shared.model.TransitionState.RUNNING +import com.android.systemui.keyguard.shared.model.TransitionState.STARTED +import com.android.systemui.keyguard.shared.model.TransitionStep +import kotlin.math.max +import kotlin.math.min +import kotlin.time.Duration +import kotlin.time.Duration.Companion.milliseconds +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.filterNotNull +import kotlinx.coroutines.flow.map + +/** + * For the given transition params, construct a flow using [createFlow] for the specified portion of + * the overall transition. + */ +class KeyguardTransitionAnimationFlow( + private val transitionDuration: Duration, + private val transitionFlow: Flow<TransitionStep>, +) { + /** + * Transitions will occur over a [transitionDuration] with [TransitionStep]s being emitted in + * the range of [0, 1]. View animations should begin and end within a subset of this range. This + * function maps the [startTime] and [duration] into [0, 1], when this subset is valid. + */ + fun createFlow( + duration: Duration, + onStep: (Float) -> Float, + startTime: Duration = 0.milliseconds, + onCancel: (() -> Float)? = null, + onFinish: (() -> Float)? = null, + interpolator: Interpolator = LINEAR, + ): Flow<Float> { + if (!duration.isPositive()) { + throw IllegalArgumentException("duration must be a positive number: $duration") + } + if ((startTime + duration).compareTo(transitionDuration) > 0) { + throw IllegalArgumentException( + "startTime($startTime) + duration($duration) must be" + + " <= transitionDuration($transitionDuration)" + ) + } + + val start = (startTime / transitionDuration).toFloat() + val chunks = (transitionDuration / duration).toFloat() + var isComplete = true + + fun stepToValue(step: TransitionStep): Float? { + val value = (step.value - start) * chunks + return when (step.transitionState) { + // When starting, make sure to always emit. If a transition is started from the + // middle, it is possible this animation is being skipped but we need to inform + // the ViewModels of the last update + STARTED -> { + isComplete = false + max(0f, min(1f, value)) + } + // Always send a final value of 1. Because of rounding, [value] may never be + // exactly 1. + RUNNING -> + if (isComplete) { + null + } else if (value >= 1f) { + isComplete = true + 1f + } else if (value >= 0f) { + value + } else { + null + } + else -> null + }?.let { onStep(interpolator.getInterpolation(it)) } + } + + return transitionFlow + .map { step -> + when (step.transitionState) { + STARTED -> stepToValue(step) + RUNNING -> stepToValue(step) + CANCELED -> onCancel?.invoke() + FINISHED -> onFinish?.invoke() + } + } + .filterNotNull() + } +} diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToLockscreenTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToLockscreenTransitionViewModel.kt index 6627865ecc79..8d6545a49a8a 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToLockscreenTransitionViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToLockscreenTransitionViewModel.kt @@ -21,15 +21,10 @@ import com.android.systemui.animation.Interpolators.EMPHASIZED_DECELERATE 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 com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow 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 @@ -41,39 +36,46 @@ class DreamingToLockscreenTransitionViewModel constructor( private val interactor: KeyguardTransitionInteractor, ) { + private val transitionAnimation = + KeyguardTransitionAnimationFlow( + transitionDuration = TO_LOCKSCREEN_DURATION, + transitionFlow = interactor.dreamingToLockscreenTransition, + ) /** Dream overlay y-translation on exit */ fun dreamOverlayTranslationY(translatePx: Int): Flow<Float> { - return flowForAnimation(DREAM_OVERLAY_TRANSLATION_Y).map { value -> - EMPHASIZED_ACCELERATE.getInterpolation(value) * translatePx - } + return transitionAnimation.createFlow( + duration = 600.milliseconds, + onStep = { it * translatePx }, + interpolator = EMPHASIZED_ACCELERATE, + ) } /** Dream overlay views alpha - fade out */ - val dreamOverlayAlpha: Flow<Float> = flowForAnimation(DREAM_OVERLAY_ALPHA).map { 1f - it } + val dreamOverlayAlpha: Flow<Float> = + transitionAnimation.createFlow( + duration = 250.milliseconds, + onStep = { 1f - it }, + ) /** Lockscreen views y-translation */ fun lockscreenTranslationY(translatePx: Int): Flow<Float> { - 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 } + return transitionAnimation.createFlow( + duration = TO_LOCKSCREEN_DURATION, + onStep = { value -> -translatePx + value * translatePx }, + // Reset on cancel or finish + onFinish = { 0f }, + onCancel = { 0f }, + interpolator = EMPHASIZED_DECELERATE, ) } /** Lockscreen views alpha */ - val lockscreenAlpha: Flow<Float> = flowForAnimation(LOCKSCREEN_ALPHA) - - private fun flowForAnimation(params: AnimationParams): Flow<Float> { - return interactor.transitionStepAnimation( - interactor.dreamingToLockscreenTransition, - params, - totalDuration = TO_LOCKSCREEN_DURATION + val lockscreenAlpha: Flow<Float> = + transitionAnimation.createFlow( + startTime = 233.milliseconds, + duration = 250.milliseconds, + onStep = { it }, ) - } companion object { /* Length of time before ending the dream activity, in order to start unoccluding */ @@ -81,11 +83,5 @@ constructor( @JvmField val LOCKSCREEN_ANIMATION_DURATION_MS = (TO_LOCKSCREEN_DURATION - DREAM_ANIMATION_DURATION).inWholeMilliseconds - - val DREAM_OVERLAY_TRANSLATION_Y = AnimationParams(duration = 600.milliseconds) - val DREAM_OVERLAY_ALPHA = AnimationParams(duration = 250.milliseconds) - val LOCKSCREEN_TRANSLATION_Y = AnimationParams(duration = TO_LOCKSCREEN_DURATION) - val LOCKSCREEN_ALPHA = - AnimationParams(startTime = 233.milliseconds, duration = 250.milliseconds) } } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GoneToDreamingTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GoneToDreamingTransitionViewModel.kt index 5a4796096eeb..f16827d4a54a 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GoneToDreamingTransitionViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GoneToDreamingTransitionViewModel.kt @@ -20,15 +20,10 @@ import com.android.systemui.animation.Interpolators.EMPHASIZED_ACCELERATE 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.CANCELED -import com.android.systemui.keyguard.shared.model.TransitionState.FINISHED +import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow 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 GONE->DREAMING transition into discrete steps for corresponding views to consume. */ @SysUISingleton @@ -38,32 +33,28 @@ constructor( private val interactor: KeyguardTransitionInteractor, ) { + private val transitionAnimation = + KeyguardTransitionAnimationFlow( + transitionDuration = TO_DREAMING_DURATION, + transitionFlow = interactor.goneToDreamingTransition, + ) + /** Lockscreen views y-translation */ fun lockscreenTranslationY(translatePx: Int): Flow<Float> { - return merge( - flowForAnimation(LOCKSCREEN_TRANSLATION_Y).map { value -> - (EMPHASIZED_ACCELERATE.getInterpolation(value) * translatePx) - }, - // On end, reset the translation to 0 - interactor.goneToDreamingTransition - .filter { it.transitionState == FINISHED || it.transitionState == CANCELED } - .map { 0f } + return transitionAnimation.createFlow( + duration = 500.milliseconds, + onStep = { it * translatePx }, + // Reset on cancel or finish + onFinish = { 0f }, + onCancel = { 0f }, + interpolator = EMPHASIZED_ACCELERATE, ) } /** Lockscreen views alpha */ - val lockscreenAlpha: Flow<Float> = flowForAnimation(LOCKSCREEN_ALPHA).map { 1f - it } - - private fun flowForAnimation(params: AnimationParams): Flow<Float> { - return interactor.transitionStepAnimation( - interactor.goneToDreamingTransition, - params, - totalDuration = TO_DREAMING_DURATION + val lockscreenAlpha: Flow<Float> = + transitionAnimation.createFlow( + duration = 250.milliseconds, + onStep = { 1f - it }, ) - } - - companion object { - val LOCKSCREEN_TRANSLATION_Y = AnimationParams(duration = 500.milliseconds) - val LOCKSCREEN_ALPHA = AnimationParams(duration = 250.milliseconds) - } } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToDreamingTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToDreamingTransitionViewModel.kt index e05adbdab583..bc9dc4f69102 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToDreamingTransitionViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToDreamingTransitionViewModel.kt @@ -20,15 +20,10 @@ import com.android.systemui.animation.Interpolators.EMPHASIZED_ACCELERATE 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.CANCELED -import com.android.systemui.keyguard.shared.model.TransitionState.FINISHED +import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow 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 LOCKSCREEN->DREAMING transition into discrete steps for corresponding views to @@ -40,35 +35,32 @@ class LockscreenToDreamingTransitionViewModel constructor( private val interactor: KeyguardTransitionInteractor, ) { + private val transitionAnimation = + KeyguardTransitionAnimationFlow( + transitionDuration = TO_DREAMING_DURATION, + transitionFlow = interactor.lockscreenToDreamingTransition, + ) /** Lockscreen views y-translation */ fun lockscreenTranslationY(translatePx: Int): Flow<Float> { - return merge( - flowForAnimation(LOCKSCREEN_TRANSLATION_Y).map { value -> - (EMPHASIZED_ACCELERATE.getInterpolation(value) * translatePx) - }, - // On end, reset the translation to 0 - interactor.lockscreenToDreamingTransition - .filter { it.transitionState == FINISHED || it.transitionState == CANCELED } - .map { 0f } + return transitionAnimation.createFlow( + duration = 500.milliseconds, + onStep = { it * translatePx }, + // Reset on cancel or finish + onFinish = { 0f }, + onCancel = { 0f }, + interpolator = EMPHASIZED_ACCELERATE, ) } /** Lockscreen views alpha */ - val lockscreenAlpha: Flow<Float> = flowForAnimation(LOCKSCREEN_ALPHA).map { 1f - it } - - private fun flowForAnimation(params: AnimationParams): Flow<Float> { - return interactor.transitionStepAnimation( - interactor.lockscreenToDreamingTransition, - params, - totalDuration = TO_DREAMING_DURATION + val lockscreenAlpha: Flow<Float> = + transitionAnimation.createFlow( + duration = 250.milliseconds, + onStep = { 1f - it }, ) - } companion object { @JvmField val DREAMING_ANIMATION_DURATION_MS = TO_DREAMING_DURATION.inWholeMilliseconds - - val LOCKSCREEN_TRANSLATION_Y = AnimationParams(duration = 500.milliseconds) - val LOCKSCREEN_ALPHA = AnimationParams(duration = 250.milliseconds) } } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToOccludedTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToOccludedTransitionViewModel.kt index 22d292e92856..a60665a81f0e 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToOccludedTransitionViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToOccludedTransitionViewModel.kt @@ -20,14 +20,10 @@ import com.android.systemui.animation.Interpolators.EMPHASIZED_ACCELERATE import com.android.systemui.dagger.SysUISingleton import com.android.systemui.keyguard.domain.interactor.FromLockscreenTransitionInteractor.Companion.TO_OCCLUDED_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.ui.KeyguardTransitionAnimationFlow 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 LOCKSCREEN->OCCLUDED transition into discrete steps for corresponding views to @@ -39,33 +35,28 @@ class LockscreenToOccludedTransitionViewModel constructor( private val interactor: KeyguardTransitionInteractor, ) { - - /** Lockscreen views y-translation */ - fun lockscreenTranslationY(translatePx: Int): Flow<Float> { - return merge( - flowForAnimation(LOCKSCREEN_TRANSLATION_Y).map { value -> - (EMPHASIZED_ACCELERATE.getInterpolation(value) * translatePx) - }, - // On end, reset the translation to 0 - interactor.lockscreenToOccludedTransition - .filter { step -> step.transitionState == TransitionState.FINISHED } - .map { 0f } + private val transitionAnimation = + KeyguardTransitionAnimationFlow( + transitionDuration = TO_OCCLUDED_DURATION, + transitionFlow = interactor.lockscreenToOccludedTransition, ) - } /** Lockscreen views alpha */ - val lockscreenAlpha: Flow<Float> = flowForAnimation(LOCKSCREEN_ALPHA).map { 1f - it } - - private fun flowForAnimation(params: AnimationParams): Flow<Float> { - return interactor.transitionStepAnimation( - interactor.lockscreenToOccludedTransition, - params, - totalDuration = TO_OCCLUDED_DURATION + val lockscreenAlpha: Flow<Float> = + transitionAnimation.createFlow( + duration = 250.milliseconds, + onStep = { 1f - it }, ) - } - companion object { - val LOCKSCREEN_TRANSLATION_Y = AnimationParams(duration = TO_OCCLUDED_DURATION) - val LOCKSCREEN_ALPHA = AnimationParams(duration = 250.milliseconds) + /** Lockscreen views y-translation */ + fun lockscreenTranslationY(translatePx: Int): Flow<Float> { + return transitionAnimation.createFlow( + duration = TO_OCCLUDED_DURATION, + onStep = { value -> value * translatePx }, + // Reset on cancel or finish + onFinish = { 0f }, + onCancel = { 0f }, + interpolator = EMPHASIZED_ACCELERATE, + ) } } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToLockscreenTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToLockscreenTransitionViewModel.kt index e804562bc85f..5770f3ee8876 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToLockscreenTransitionViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToLockscreenTransitionViewModel.kt @@ -20,11 +20,10 @@ import com.android.systemui.animation.Interpolators.EMPHASIZED_DECELERATE import com.android.systemui.dagger.SysUISingleton import com.android.systemui.keyguard.domain.interactor.FromOccludedTransitionInteractor.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.ui.KeyguardTransitionAnimationFlow import javax.inject.Inject import kotlin.time.Duration.Companion.milliseconds import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.map /** * Breaks down OCCLUDED->LOCKSCREEN transition into discrete steps for corresponding views to @@ -36,28 +35,26 @@ class OccludedToLockscreenTransitionViewModel constructor( private val interactor: KeyguardTransitionInteractor, ) { + private val transitionAnimation = + KeyguardTransitionAnimationFlow( + transitionDuration = TO_LOCKSCREEN_DURATION, + transitionFlow = interactor.occludedToLockscreenTransition, + ) + /** Lockscreen views y-translation */ fun lockscreenTranslationY(translatePx: Int): Flow<Float> { - return flowForAnimation(LOCKSCREEN_TRANSLATION_Y).map { value -> - -translatePx + (EMPHASIZED_DECELERATE.getInterpolation(value) * translatePx) - } + return transitionAnimation.createFlow( + duration = TO_LOCKSCREEN_DURATION, + onStep = { value -> -translatePx + value * translatePx }, + interpolator = EMPHASIZED_DECELERATE, + ) } /** Lockscreen views alpha */ - val lockscreenAlpha: Flow<Float> = flowForAnimation(LOCKSCREEN_ALPHA) - - private fun flowForAnimation(params: AnimationParams): Flow<Float> { - return interactor.transitionStepAnimation( - interactor.occludedToLockscreenTransition, - params, - totalDuration = TO_LOCKSCREEN_DURATION + val lockscreenAlpha: Flow<Float> = + transitionAnimation.createFlow( + startTime = 233.milliseconds, + duration = 250.milliseconds, + onStep = { it }, ) - } - - companion object { - @JvmField val LOCKSCREEN_ANIMATION_DURATION_MS = TO_LOCKSCREEN_DURATION.inWholeMilliseconds - val LOCKSCREEN_TRANSLATION_Y = AnimationParams(duration = TO_LOCKSCREEN_DURATION) - val LOCKSCREEN_ALPHA = - AnimationParams(startTime = 233.milliseconds, duration = 250.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 3a871b4de8bc..702f37635092 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 @@ -24,6 +24,7 @@ import com.android.systemui.animation.Interpolators import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepositoryImpl +import com.android.systemui.keyguard.shared.model.BiometricUnlockModel import com.android.systemui.keyguard.shared.model.DozeStateModel import com.android.systemui.keyguard.shared.model.DozeTransitionModel import com.android.systemui.keyguard.shared.model.KeyguardState @@ -437,6 +438,43 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() { } @Test + fun `DOZING to GONE`() = + testScope.runTest { + // GIVEN a prior transition has run to DOZING + runner.startTransition( + testScope, + TransitionInfo( + ownerName = "", + from = KeyguardState.LOCKSCREEN, + to = KeyguardState.DOZING, + animator = + ValueAnimator().apply { + duration = 10 + interpolator = Interpolators.LINEAR + }, + ) + ) + runCurrent() + reset(mockTransitionRepository) + + // WHEN biometrics succeeds with wake and unlock mode + keyguardRepository.setBiometricUnlockState(BiometricUnlockModel.WAKE_AND_UNLOCK) + runCurrent() + + val info = + withArgCaptor<TransitionInfo> { + verify(mockTransitionRepository).startTransition(capture()) + } + // THEN a transition to DOZING should occur + assertThat(info.ownerName).isEqualTo("FromDozingTransitionInteractor") + assertThat(info.from).isEqualTo(KeyguardState.DOZING) + assertThat(info.to).isEqualTo(KeyguardState.GONE) + assertThat(info.animator).isNotNull() + + coroutineContext.cancelChildren() + } + + @Test fun `GONE to DOZING`() = testScope.runTest { // GIVEN a device with AOD not available diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/KeyguardTransitionAnimationFlowTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/KeyguardTransitionAnimationFlowTest.kt new file mode 100644 index 000000000000..a5b78b74fcdf --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/KeyguardTransitionAnimationFlowTest.kt @@ -0,0 +1,174 @@ +/* + * Copyright (C) 2023 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.ui + +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.animation.Interpolators.EMPHASIZED_ACCELERATE +import com.android.systemui.coroutines.collectLastValue +import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository +import com.android.systemui.keyguard.shared.model.KeyguardState +import com.android.systemui.keyguard.shared.model.TransitionState +import com.android.systemui.keyguard.shared.model.TransitionStep +import com.google.common.truth.Truth.assertThat +import kotlin.time.Duration.Companion.milliseconds +import kotlinx.coroutines.test.runTest +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.junit.runners.JUnit4 + +@SmallTest +@RunWith(JUnit4::class) +class KeyguardTransitionAnimationFlowTest : SysuiTestCase() { + private lateinit var underTest: KeyguardTransitionAnimationFlow + private lateinit var repository: FakeKeyguardTransitionRepository + + @Before + fun setUp() { + repository = FakeKeyguardTransitionRepository() + underTest = + KeyguardTransitionAnimationFlow( + 1000.milliseconds, + repository.transitions, + ) + } + + @Test(expected = IllegalArgumentException::class) + fun zeroDurationThrowsException() = runTest { + val flow = underTest.createFlow(duration = 0.milliseconds, onStep = { it }) + } + + @Test(expected = IllegalArgumentException::class) + fun startTimePlusDurationGreaterThanTransitionDurationThrowsException() = runTest { + val flow = + underTest.createFlow( + startTime = 300.milliseconds, + duration = 800.milliseconds, + onStep = { it } + ) + } + + @Test + fun onFinishRunsWhenSpecified() = runTest { + val flow = + underTest.createFlow( + duration = 100.milliseconds, + onStep = { it }, + onFinish = { 10f }, + ) + var animationValues = collectLastValue(flow) + repository.sendTransitionStep(step(1f, TransitionState.FINISHED)) + assertThat(animationValues()).isEqualTo(10f) + } + + @Test + fun onCancelRunsWhenSpecified() = runTest { + val flow = + underTest.createFlow( + duration = 100.milliseconds, + onStep = { it }, + onCancel = { 100f }, + ) + var animationValues = collectLastValue(flow) + repository.sendTransitionStep(step(0.5f, TransitionState.CANCELED)) + assertThat(animationValues()).isEqualTo(100f) + } + + @Test + fun usesStartTime() = runTest { + val flow = + underTest.createFlow( + startTime = 500.milliseconds, + duration = 500.milliseconds, + onStep = { it }, + ) + var animationValues = collectLastValue(flow) + repository.sendTransitionStep(step(0f, TransitionState.STARTED)) + assertThat(animationValues()).isEqualTo(0f) + + // Should not emit a value + repository.sendTransitionStep(step(0.1f, TransitionState.RUNNING)) + + repository.sendTransitionStep(step(0.5f, TransitionState.RUNNING)) + assertFloat(animationValues(), 0f) + repository.sendTransitionStep(step(0.6f, TransitionState.RUNNING)) + assertFloat(animationValues(), 0.2f) + repository.sendTransitionStep(step(0.8f, TransitionState.RUNNING)) + assertFloat(animationValues(), 0.6f) + repository.sendTransitionStep(step(1f, TransitionState.RUNNING)) + assertFloat(animationValues(), 1f) + } + + @Test + fun usesInterpolator() = runTest { + val flow = + underTest.createFlow( + duration = 1000.milliseconds, + interpolator = EMPHASIZED_ACCELERATE, + onStep = { it }, + ) + var animationValues = collectLastValue(flow) + repository.sendTransitionStep(step(0f, TransitionState.STARTED)) + assertFloat(animationValues(), EMPHASIZED_ACCELERATE.getInterpolation(0f)) + repository.sendTransitionStep(step(0.5f, TransitionState.RUNNING)) + assertFloat(animationValues(), EMPHASIZED_ACCELERATE.getInterpolation(0.5f)) + repository.sendTransitionStep(step(0.6f, TransitionState.RUNNING)) + assertFloat(animationValues(), EMPHASIZED_ACCELERATE.getInterpolation(0.6f)) + repository.sendTransitionStep(step(0.8f, TransitionState.RUNNING)) + assertFloat(animationValues(), EMPHASIZED_ACCELERATE.getInterpolation(0.8f)) + repository.sendTransitionStep(step(1f, TransitionState.RUNNING)) + assertFloat(animationValues(), EMPHASIZED_ACCELERATE.getInterpolation(1f)) + } + + @Test + fun usesOnStepToDoubleValue() = runTest { + val flow = + underTest.createFlow( + duration = 1000.milliseconds, + onStep = { it * 2 }, + ) + var animationValues = collectLastValue(flow) + repository.sendTransitionStep(step(0f, TransitionState.STARTED)) + assertFloat(animationValues(), 0f) + repository.sendTransitionStep(step(0.3f, TransitionState.RUNNING)) + assertFloat(animationValues(), 0.6f) + repository.sendTransitionStep(step(0.6f, TransitionState.RUNNING)) + assertFloat(animationValues(), 1.2f) + repository.sendTransitionStep(step(0.8f, TransitionState.RUNNING)) + assertFloat(animationValues(), 1.6f) + repository.sendTransitionStep(step(1f, TransitionState.RUNNING)) + assertFloat(animationValues(), 2f) + } + + private fun assertFloat(actual: Float?, expected: Float) { + assertThat(actual!!).isWithin(0.01f).of(expected) + } + + private fun step( + value: Float, + state: TransitionState = TransitionState.RUNNING + ): TransitionStep { + return TransitionStep( + from = KeyguardState.GONE, + to = KeyguardState.DREAMING, + value = value, + transitionState = state, + ownerName = "GoneToDreamingTransitionViewModelTest" + ) + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToLockscreenTransitionViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToLockscreenTransitionViewModelTest.kt index 557166301d64..06e397d8fb8f 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToLockscreenTransitionViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToLockscreenTransitionViewModelTest.kt @@ -18,19 +18,13 @@ package com.android.systemui.keyguard.ui.viewmodel import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase -import com.android.systemui.animation.Interpolators.EMPHASIZED_ACCELERATE -import com.android.systemui.animation.Interpolators.EMPHASIZED_DECELERATE import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository -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.KeyguardState import com.android.systemui.keyguard.shared.model.TransitionState import com.android.systemui.keyguard.shared.model.TransitionStep -import com.android.systemui.keyguard.ui.viewmodel.DreamingToLockscreenTransitionViewModel.Companion.DREAM_OVERLAY_ALPHA -import com.android.systemui.keyguard.ui.viewmodel.DreamingToLockscreenTransitionViewModel.Companion.DREAM_OVERLAY_TRANSLATION_Y -import com.android.systemui.keyguard.ui.viewmodel.DreamingToLockscreenTransitionViewModel.Companion.LOCKSCREEN_ALPHA -import com.android.systemui.keyguard.ui.viewmodel.DreamingToLockscreenTransitionViewModel.Companion.LOCKSCREEN_TRANSLATION_Y +import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow +import com.google.common.collect.Range import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach @@ -46,6 +40,7 @@ import org.junit.runners.JUnit4 class DreamingToLockscreenTransitionViewModelTest : SysuiTestCase() { private lateinit var underTest: DreamingToLockscreenTransitionViewModel private lateinit var repository: FakeKeyguardTransitionRepository + private lateinit var transitionAnimation: KeyguardTransitionAnimationFlow @Before fun setUp() { @@ -63,32 +58,18 @@ class DreamingToLockscreenTransitionViewModelTest : SysuiTestCase() { val job = underTest.dreamOverlayTranslationY(pixels).onEach { values.add(it) }.launchIn(this) + // Should start running here... + repository.sendTransitionStep(step(0f, TransitionState.STARTED)) repository.sendTransitionStep(step(0f)) repository.sendTransitionStep(step(0.3f)) repository.sendTransitionStep(step(0.5f)) + repository.sendTransitionStep(step(0.6f)) + // ...up to here + repository.sendTransitionStep(step(0.8f)) repository.sendTransitionStep(step(1f)) - // Only 3 values should be present, since the dream overlay runs for a small fraction - // of the overall animation time - assertThat(values.size).isEqualTo(3) - assertThat(values[0]) - .isEqualTo( - EMPHASIZED_ACCELERATE.getInterpolation( - animValue(0f, DREAM_OVERLAY_TRANSLATION_Y) - ) * pixels - ) - assertThat(values[1]) - .isEqualTo( - EMPHASIZED_ACCELERATE.getInterpolation( - animValue(0.3f, DREAM_OVERLAY_TRANSLATION_Y) - ) * pixels - ) - assertThat(values[2]) - .isEqualTo( - EMPHASIZED_ACCELERATE.getInterpolation( - animValue(0.5f, DREAM_OVERLAY_TRANSLATION_Y) - ) * pixels - ) + assertThat(values.size).isEqualTo(5) + values.forEach { assertThat(it).isIn(Range.closed(0f, 100f)) } job.cancel() } @@ -100,16 +81,18 @@ class DreamingToLockscreenTransitionViewModelTest : SysuiTestCase() { val job = underTest.dreamOverlayAlpha.onEach { values.add(it) }.launchIn(this) + // Should start running here... + repository.sendTransitionStep(step(0f, TransitionState.STARTED)) repository.sendTransitionStep(step(0f)) repository.sendTransitionStep(step(0.1f)) repository.sendTransitionStep(step(0.5f)) + // ...up to here repository.sendTransitionStep(step(1f)) // Only two values should be present, since the dream overlay runs for a small fraction // of the overall animation time - assertThat(values.size).isEqualTo(2) - assertThat(values[0]).isEqualTo(1f - animValue(0f, DREAM_OVERLAY_ALPHA)) - assertThat(values[1]).isEqualTo(1f - animValue(0.1f, DREAM_OVERLAY_ALPHA)) + assertThat(values.size).isEqualTo(4) + values.forEach { assertThat(it).isIn(Range.closed(0f, 1f)) } job.cancel() } @@ -121,19 +104,15 @@ class DreamingToLockscreenTransitionViewModelTest : SysuiTestCase() { val job = underTest.lockscreenAlpha.onEach { values.add(it) }.launchIn(this) + repository.sendTransitionStep(step(0f, TransitionState.STARTED)) repository.sendTransitionStep(step(0f)) repository.sendTransitionStep(step(0.1f)) - // Should start running here... repository.sendTransitionStep(step(0.2f)) repository.sendTransitionStep(step(0.3f)) - // ...up to here repository.sendTransitionStep(step(1f)) - // Only two values should be present, since the dream overlay runs for a small fraction - // of the overall animation time - assertThat(values.size).isEqualTo(2) - assertThat(values[0]).isEqualTo(animValue(0.2f, LOCKSCREEN_ALPHA)) - assertThat(values[1]).isEqualTo(animValue(0.3f, LOCKSCREEN_ALPHA)) + assertThat(values.size).isEqualTo(4) + values.forEach { assertThat(it).isIn(Range.closed(0f, 1f)) } job.cancel() } @@ -147,58 +126,27 @@ class DreamingToLockscreenTransitionViewModelTest : SysuiTestCase() { val job = underTest.lockscreenTranslationY(pixels).onEach { values.add(it) }.launchIn(this) + repository.sendTransitionStep(step(0f, TransitionState.STARTED)) repository.sendTransitionStep(step(0f)) repository.sendTransitionStep(step(0.3f)) repository.sendTransitionStep(step(0.5f)) repository.sendTransitionStep(step(1f)) - assertThat(values.size).isEqualTo(4) - assertThat(values[0]) - .isEqualTo( - -pixels + - EMPHASIZED_DECELERATE.getInterpolation( - animValue(0f, LOCKSCREEN_TRANSLATION_Y) - ) * pixels - ) - assertThat(values[1]) - .isEqualTo( - -pixels + - EMPHASIZED_DECELERATE.getInterpolation( - animValue(0.3f, LOCKSCREEN_TRANSLATION_Y) - ) * pixels - ) - assertThat(values[2]) - .isEqualTo( - -pixels + - EMPHASIZED_DECELERATE.getInterpolation( - animValue(0.5f, LOCKSCREEN_TRANSLATION_Y) - ) * pixels - ) - assertThat(values[3]) - .isEqualTo( - -pixels + - EMPHASIZED_DECELERATE.getInterpolation( - animValue(1f, LOCKSCREEN_TRANSLATION_Y) - ) * pixels - ) + assertThat(values.size).isEqualTo(5) + values.forEach { assertThat(it).isIn(Range.closed(-100f, 0f)) } job.cancel() } - private fun animValue(stepValue: Float, params: AnimationParams): Float { - val totalDuration = TO_LOCKSCREEN_DURATION - val startValue = (params.startTime / totalDuration).toFloat() - - val multiplier = (totalDuration / params.duration).toFloat() - return (stepValue - startValue) * multiplier - } - - private fun step(value: Float): TransitionStep { + private fun step( + value: Float, + state: TransitionState = TransitionState.RUNNING + ): TransitionStep { return TransitionStep( from = KeyguardState.DREAMING, to = KeyguardState.LOCKSCREEN, value = value, - transitionState = TransitionState.RUNNING, + transitionState = state, ownerName = "DreamingToLockscreenTransitionViewModelTest" ) } diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/GoneToDreamingTransitionViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/GoneToDreamingTransitionViewModelTest.kt index 7fa204bb980b..14c3b504d0ae 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/GoneToDreamingTransitionViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/GoneToDreamingTransitionViewModelTest.kt @@ -18,16 +18,12 @@ package com.android.systemui.keyguard.ui.viewmodel import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase -import com.android.systemui.animation.Interpolators.EMPHASIZED_ACCELERATE import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository -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.KeyguardState import com.android.systemui.keyguard.shared.model.TransitionState import com.android.systemui.keyguard.shared.model.TransitionStep -import com.android.systemui.keyguard.ui.viewmodel.GoneToDreamingTransitionViewModel.Companion.LOCKSCREEN_ALPHA -import com.android.systemui.keyguard.ui.viewmodel.GoneToDreamingTransitionViewModel.Companion.LOCKSCREEN_TRANSLATION_Y +import com.google.common.collect.Range import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach @@ -59,20 +55,18 @@ class GoneToDreamingTransitionViewModelTest : SysuiTestCase() { val job = underTest.lockscreenAlpha.onEach { values.add(it) }.launchIn(this) // Should start running here... + repository.sendTransitionStep(step(0f, TransitionState.STARTED)) repository.sendTransitionStep(step(0f)) repository.sendTransitionStep(step(0.1f)) repository.sendTransitionStep(step(0.2f)) - // ...up to here repository.sendTransitionStep(step(0.3f)) + // ...up to here repository.sendTransitionStep(step(1f)) // Only three values should be present, since the dream overlay runs for a small - // 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)) - assertThat(values[2]).isEqualTo(1f - animValue(0.2f, LOCKSCREEN_ALPHA)) + // fraction of the overall animation time + assertThat(values.size).isEqualTo(5) + values.forEach { assertThat(it).isIn(Range.closed(0f, 1f)) } job.cancel() } @@ -87,45 +81,19 @@ class GoneToDreamingTransitionViewModelTest : SysuiTestCase() { underTest.lockscreenTranslationY(pixels).onEach { values.add(it) }.launchIn(this) // Should start running here... + repository.sendTransitionStep(step(0f, TransitionState.STARTED)) repository.sendTransitionStep(step(0f)) repository.sendTransitionStep(step(0.3f)) 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(4) - assertThat(values[0]) - .isEqualTo( - EMPHASIZED_ACCELERATE.getInterpolation( - animValue(0f, LOCKSCREEN_TRANSLATION_Y) - ) * pixels - ) - assertThat(values[1]) - .isEqualTo( - EMPHASIZED_ACCELERATE.getInterpolation( - animValue(0.3f, LOCKSCREEN_TRANSLATION_Y) - ) * pixels - ) - assertThat(values[2]) - .isEqualTo( - EMPHASIZED_ACCELERATE.getInterpolation( - animValue(0.5f, LOCKSCREEN_TRANSLATION_Y) - ) * pixels - ) - assertThat(values[3]).isEqualTo(0f) + assertThat(values.size).isEqualTo(5) + values.forEach { assertThat(it).isIn(Range.closed(0f, 100f)) } + job.cancel() } - private fun animValue(stepValue: Float, params: AnimationParams): Float { - val totalDuration = TO_DREAMING_DURATION - val startValue = (params.startTime / totalDuration).toFloat() - - val multiplier = (totalDuration / params.duration).toFloat() - return (stepValue - startValue) * multiplier - } - private fun step( value: Float, state: TransitionState = TransitionState.RUNNING diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToDreamingTransitionViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToDreamingTransitionViewModelTest.kt index 539fc2c1548e..ed31dc39dd90 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToDreamingTransitionViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToDreamingTransitionViewModelTest.kt @@ -18,16 +18,12 @@ package com.android.systemui.keyguard.ui.viewmodel import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase -import com.android.systemui.animation.Interpolators.EMPHASIZED_ACCELERATE import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository -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.KeyguardState import com.android.systemui.keyguard.shared.model.TransitionState import com.android.systemui.keyguard.shared.model.TransitionStep -import com.android.systemui.keyguard.ui.viewmodel.LockscreenToDreamingTransitionViewModel.Companion.LOCKSCREEN_ALPHA -import com.android.systemui.keyguard.ui.viewmodel.LockscreenToDreamingTransitionViewModel.Companion.LOCKSCREEN_TRANSLATION_Y +import com.google.common.collect.Range import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach @@ -59,19 +55,18 @@ class LockscreenToDreamingTransitionViewModelTest : SysuiTestCase() { val job = underTest.lockscreenAlpha.onEach { values.add(it) }.launchIn(this) // Should start running here... + repository.sendTransitionStep(step(0f, TransitionState.STARTED)) repository.sendTransitionStep(step(0f)) repository.sendTransitionStep(step(0.1f)) repository.sendTransitionStep(step(0.2f)) - // ...up to here repository.sendTransitionStep(step(0.3f)) + // ...up to here repository.sendTransitionStep(step(1f)) // Only three values should be present, since the dream overlay runs for a small // 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)) - assertThat(values[2]).isEqualTo(1f - animValue(0.2f, LOCKSCREEN_ALPHA)) + assertThat(values.size).isEqualTo(5) + values.forEach { assertThat(it).isIn(Range.closed(0f, 1f)) } job.cancel() } @@ -85,47 +80,22 @@ class LockscreenToDreamingTransitionViewModelTest : SysuiTestCase() { val job = underTest.lockscreenTranslationY(pixels).onEach { values.add(it) }.launchIn(this) - // Should start running here... + repository.sendTransitionStep(step(0f, TransitionState.STARTED)) repository.sendTransitionStep(step(0f)) repository.sendTransitionStep(step(0.3f)) 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(4) - assertThat(values[0]) - .isEqualTo( - EMPHASIZED_ACCELERATE.getInterpolation( - animValue(0f, LOCKSCREEN_TRANSLATION_Y) - ) * pixels - ) - assertThat(values[1]) - .isEqualTo( - EMPHASIZED_ACCELERATE.getInterpolation( - animValue(0.3f, LOCKSCREEN_TRANSLATION_Y) - ) * pixels - ) - assertThat(values[2]) - .isEqualTo( - EMPHASIZED_ACCELERATE.getInterpolation( - animValue(0.5f, LOCKSCREEN_TRANSLATION_Y) - ) * pixels - ) - assertThat(values[3]).isEqualTo(0f) + assertThat(values.size).isEqualTo(6) + values.forEach { assertThat(it).isIn(Range.closed(0f, 100f)) } + // Validate finished value + assertThat(values[5]).isEqualTo(0f) job.cancel() } - private fun animValue(stepValue: Float, params: AnimationParams): Float { - val totalDuration = TO_DREAMING_DURATION - val startValue = (params.startTime / totalDuration).toFloat() - - val multiplier = (totalDuration / params.duration).toFloat() - return (stepValue - startValue) * multiplier - } - private fun step( value: Float, state: TransitionState = TransitionState.RUNNING diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToOccludedTransitionViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToOccludedTransitionViewModelTest.kt index 759345f51c59..458b31519500 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToOccludedTransitionViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToOccludedTransitionViewModelTest.kt @@ -18,16 +18,12 @@ package com.android.systemui.keyguard.ui.viewmodel import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase -import com.android.systemui.animation.Interpolators.EMPHASIZED_ACCELERATE import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository -import com.android.systemui.keyguard.domain.interactor.FromLockscreenTransitionInteractor.Companion.TO_OCCLUDED_DURATION import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor -import com.android.systemui.keyguard.shared.model.AnimationParams import com.android.systemui.keyguard.shared.model.KeyguardState import com.android.systemui.keyguard.shared.model.TransitionState import com.android.systemui.keyguard.shared.model.TransitionStep -import com.android.systemui.keyguard.ui.viewmodel.LockscreenToOccludedTransitionViewModel.Companion.LOCKSCREEN_ALPHA -import com.android.systemui.keyguard.ui.viewmodel.LockscreenToOccludedTransitionViewModel.Companion.LOCKSCREEN_TRANSLATION_Y +import com.google.common.collect.Range import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach @@ -59,19 +55,18 @@ class LockscreenToOccludedTransitionViewModelTest : SysuiTestCase() { val job = underTest.lockscreenAlpha.onEach { values.add(it) }.launchIn(this) // Should start running here... + repository.sendTransitionStep(step(0f, TransitionState.STARTED)) repository.sendTransitionStep(step(0f)) repository.sendTransitionStep(step(0.1f)) repository.sendTransitionStep(step(0.4f)) - // ...up to here repository.sendTransitionStep(step(0.7f)) + // ...up to here repository.sendTransitionStep(step(1f)) // Only 3 values should be present, since the dream overlay runs for a small 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)) - assertThat(values[2]).isEqualTo(1f - animValue(0.4f, LOCKSCREEN_ALPHA)) + assertThat(values.size).isEqualTo(5) + values.forEach { assertThat(it).isIn(Range.closed(0f, 1f)) } job.cancel() } @@ -86,54 +81,51 @@ class LockscreenToOccludedTransitionViewModelTest : SysuiTestCase() { underTest.lockscreenTranslationY(pixels).onEach { values.add(it) }.launchIn(this) // Should start running here... + repository.sendTransitionStep(step(0f, TransitionState.STARTED)) repository.sendTransitionStep(step(0f)) repository.sendTransitionStep(step(0.3f)) repository.sendTransitionStep(step(0.5f)) repository.sendTransitionStep(step(1f)) // ...up to here - assertThat(values.size).isEqualTo(4) - assertThat(values[0]) - .isEqualTo( - EMPHASIZED_ACCELERATE.getInterpolation( - animValue(0f, LOCKSCREEN_TRANSLATION_Y) - ) * pixels - ) - assertThat(values[1]) - .isEqualTo( - EMPHASIZED_ACCELERATE.getInterpolation( - animValue(0.3f, LOCKSCREEN_TRANSLATION_Y) - ) * pixels - ) - assertThat(values[2]) - .isEqualTo( - EMPHASIZED_ACCELERATE.getInterpolation( - animValue(0.5f, LOCKSCREEN_TRANSLATION_Y) - ) * pixels - ) - assertThat(values[3]) - .isEqualTo( - EMPHASIZED_ACCELERATE.getInterpolation( - animValue(1f, LOCKSCREEN_TRANSLATION_Y) - ) * pixels - ) + assertThat(values.size).isEqualTo(5) + values.forEach { assertThat(it).isIn(Range.closed(0f, 100f)) } + job.cancel() } - private fun animValue(stepValue: Float, params: AnimationParams): Float { - val totalDuration = TO_OCCLUDED_DURATION - val startValue = (params.startTime / totalDuration).toFloat() + @Test + fun lockscreenTranslationYIsCanceled() = + runTest(UnconfinedTestDispatcher()) { + val values = mutableListOf<Float>() - val multiplier = (totalDuration / params.duration).toFloat() - return (stepValue - startValue) * multiplier - } + val pixels = 100 + val job = + underTest.lockscreenTranslationY(pixels).onEach { values.add(it) }.launchIn(this) + + repository.sendTransitionStep(step(0f, TransitionState.STARTED)) + repository.sendTransitionStep(step(0f)) + repository.sendTransitionStep(step(0.3f)) + repository.sendTransitionStep(step(0.3f, TransitionState.CANCELED)) + + assertThat(values.size).isEqualTo(4) + values.forEach { assertThat(it).isIn(Range.closed(0f, 100f)) } + + // Cancel will reset the translation + assertThat(values[3]).isEqualTo(0) + + job.cancel() + } - private fun step(value: Float): TransitionStep { + private fun step( + value: Float, + state: TransitionState = TransitionState.RUNNING, + ): TransitionStep { return TransitionStep( from = KeyguardState.LOCKSCREEN, to = KeyguardState.OCCLUDED, value = value, - transitionState = TransitionState.RUNNING, + transitionState = state, ownerName = "LockscreenToOccludedTransitionViewModelTest" ) } diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToLockscreenTransitionViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToLockscreenTransitionViewModelTest.kt index 98d292d689e4..a36214ecdb2f 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToLockscreenTransitionViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToLockscreenTransitionViewModelTest.kt @@ -18,16 +18,12 @@ package com.android.systemui.keyguard.ui.viewmodel import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase -import com.android.systemui.animation.Interpolators.EMPHASIZED_DECELERATE import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository -import com.android.systemui.keyguard.domain.interactor.FromOccludedTransitionInteractor.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.KeyguardState import com.android.systemui.keyguard.shared.model.TransitionState import com.android.systemui.keyguard.shared.model.TransitionStep -import com.android.systemui.keyguard.ui.viewmodel.OccludedToLockscreenTransitionViewModel.Companion.LOCKSCREEN_ALPHA -import com.android.systemui.keyguard.ui.viewmodel.OccludedToLockscreenTransitionViewModel.Companion.LOCKSCREEN_TRANSLATION_Y +import com.google.common.collect.Range import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach @@ -58,21 +54,19 @@ class OccludedToLockscreenTransitionViewModelTest : SysuiTestCase() { val job = underTest.lockscreenAlpha.onEach { values.add(it) }.launchIn(this) - repository.sendTransitionStep(step(0f)) + repository.sendTransitionStep(step(0f, TransitionState.STARTED)) + repository.sendTransitionStep(step(0.1f)) // Should start running here... repository.sendTransitionStep(step(0.3f)) repository.sendTransitionStep(step(0.4f)) repository.sendTransitionStep(step(0.5f)) - // ...up to here repository.sendTransitionStep(step(0.6f)) + // ...up to here + repository.sendTransitionStep(step(0.8f)) repository.sendTransitionStep(step(1f)) - // Only two values should be present, since the dream overlay runs for a small fraction - // of the overall animation time - assertThat(values.size).isEqualTo(3) - assertThat(values[0]).isEqualTo(animValue(0.3f, LOCKSCREEN_ALPHA)) - assertThat(values[1]).isEqualTo(animValue(0.4f, LOCKSCREEN_ALPHA)) - assertThat(values[2]).isEqualTo(animValue(0.5f, LOCKSCREEN_ALPHA)) + assertThat(values.size).isEqualTo(5) + values.forEach { assertThat(it).isIn(Range.closed(0f, 1f)) } job.cancel() } @@ -86,58 +80,27 @@ class OccludedToLockscreenTransitionViewModelTest : SysuiTestCase() { val job = underTest.lockscreenTranslationY(pixels).onEach { values.add(it) }.launchIn(this) + repository.sendTransitionStep(step(0f, TransitionState.STARTED)) repository.sendTransitionStep(step(0f)) repository.sendTransitionStep(step(0.3f)) repository.sendTransitionStep(step(0.5f)) repository.sendTransitionStep(step(1f)) - assertThat(values.size).isEqualTo(4) - assertThat(values[0]) - .isEqualTo( - -pixels + - EMPHASIZED_DECELERATE.getInterpolation( - animValue(0f, LOCKSCREEN_TRANSLATION_Y) - ) * pixels - ) - assertThat(values[1]) - .isEqualTo( - -pixels + - EMPHASIZED_DECELERATE.getInterpolation( - animValue(0.3f, LOCKSCREEN_TRANSLATION_Y) - ) * pixels - ) - assertThat(values[2]) - .isEqualTo( - -pixels + - EMPHASIZED_DECELERATE.getInterpolation( - animValue(0.5f, LOCKSCREEN_TRANSLATION_Y) - ) * pixels - ) - assertThat(values[3]) - .isEqualTo( - -pixels + - EMPHASIZED_DECELERATE.getInterpolation( - animValue(1f, LOCKSCREEN_TRANSLATION_Y) - ) * pixels - ) + assertThat(values.size).isEqualTo(5) + values.forEach { assertThat(it).isIn(Range.closed(-100f, 0f)) } job.cancel() } - private fun animValue(stepValue: Float, params: AnimationParams): Float { - val totalDuration = TO_LOCKSCREEN_DURATION - val startValue = (params.startTime / totalDuration).toFloat() - - val multiplier = (totalDuration / params.duration).toFloat() - return (stepValue - startValue) * multiplier - } - - private fun step(value: Float): TransitionStep { + private fun step( + value: Float, + state: TransitionState = TransitionState.RUNNING + ): TransitionStep { return TransitionStep( from = KeyguardState.OCCLUDED, to = KeyguardState.LOCKSCREEN, value = value, - transitionState = TransitionState.RUNNING, + transitionState = state, ownerName = "OccludedToLockscreenTransitionViewModelTest" ) } |