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:
Matt Pietal
2023-01-27 14:47:00 +00:00
parent d2fe4b9e81
commit cd12088334
14 changed files with 477 additions and 452 deletions

View File

@@ -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 }
}
}

View File

@@ -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,
)

View File

@@ -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()
}
}

View File

@@ -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)
}
}

View File

@@ -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)
}
}

View File

@@ -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)
}
}

View File

@@ -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)
}
}

View File

@@ -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 },
)
}

View File

@@ -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"
)
}
}

View File

@@ -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"
)
}

View File

@@ -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

View File

@@ -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

View File

@@ -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"
)
}

View File

@@ -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"
)
}