Transitions - Refactor view model
All view models were following the same pattern, so consolidate into KeyguardTransitionAnimationFlow. This also adds support for cancel/finish events. Rework how tests run for this. Explicity test logic for how animation turn into flows with KeyguardTransitionAnimationFlowTest. For the ViewModels, don't retest this logic, simply verify that the output stays in the expected range. Fixes: 266680387 Test: atest frameworks/base/packages/SystemUI/tests/src/com/android/systemui/keyguard/ Change-Id: I254d213026775e34c0852675ea14efb167271915
This commit is contained in:
@@ -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 }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
)
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
) {
|
||||
private val transitionAnimation =
|
||||
KeyguardTransitionAnimationFlow(
|
||||
transitionDuration = TO_OCCLUDED_DURATION,
|
||||
transitionFlow = interactor.lockscreenToOccludedTransition,
|
||||
)
|
||||
|
||||
/** Lockscreen views alpha */
|
||||
val lockscreenAlpha: 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 ->
|
||||
(EMPHASIZED_ACCELERATE.getInterpolation(value) * translatePx)
|
||||
},
|
||||
// On end, reset the translation to 0
|
||||
interactor.lockscreenToOccludedTransition
|
||||
.filter { step -> step.transitionState == TransitionState.FINISHED }
|
||||
.map { 0f }
|
||||
return transitionAnimation.createFlow(
|
||||
duration = TO_OCCLUDED_DURATION,
|
||||
onStep = { value -> value * 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.lockscreenToOccludedTransition,
|
||||
params,
|
||||
totalDuration = TO_OCCLUDED_DURATION
|
||||
)
|
||||
}
|
||||
|
||||
companion object {
|
||||
val LOCKSCREEN_TRANSLATION_Y = AnimationParams(duration = TO_OCCLUDED_DURATION)
|
||||
val LOCKSCREEN_ALPHA = AnimationParams(duration = 250.milliseconds)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
/** 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
|
||||
return transitionAnimation.createFlow(
|
||||
duration = TO_LOCKSCREEN_DURATION,
|
||||
onStep = { value -> -translatePx + value * translatePx },
|
||||
interpolator = EMPHASIZED_DECELERATE,
|
||||
)
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
/** Lockscreen views alpha */
|
||||
val lockscreenAlpha: Flow<Float> =
|
||||
transitionAnimation.createFlow(
|
||||
startTime = 233.milliseconds,
|
||||
duration = 250.milliseconds,
|
||||
onStep = { it },
|
||||
)
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -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"
|
||||
)
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
private fun step(value: Float): TransitionStep {
|
||||
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,
|
||||
state: TransitionState = TransitionState.RUNNING,
|
||||
): TransitionStep {
|
||||
return TransitionStep(
|
||||
from = KeyguardState.LOCKSCREEN,
|
||||
to = KeyguardState.OCCLUDED,
|
||||
value = value,
|
||||
transitionState = TransitionState.RUNNING,
|
||||
transitionState = state,
|
||||
ownerName = "LockscreenToOccludedTransitionViewModelTest"
|
||||
)
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user