Merge changes I72c43c93,I254d2130 into tm-qpr-dev am: 5f3bb10877

Original change: https://googleplex-android-review.googlesource.com/c/platform/frameworks/base/+/21142718

Change-Id: Ibf6e90c18a7ab2c6144c7f6c4d09f1947c97e2c2
Signed-off-by: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
This commit is contained in:
Matt Pietal
2023-01-31 16:28:38 +00:00
committed by Automerger Merge Worker
16 changed files with 539 additions and 452 deletions

View File

@@ -21,6 +21,7 @@ import com.android.systemui.animation.Interpolators
import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository
import com.android.systemui.keyguard.shared.model.BiometricUnlockModel.Companion.isWakeAndUnlock
import com.android.systemui.keyguard.shared.model.KeyguardState import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.keyguard.shared.model.TransitionInfo import com.android.systemui.keyguard.shared.model.TransitionInfo
import com.android.systemui.keyguard.shared.model.WakefulnessModel.Companion.isWakingOrStartingToWake import com.android.systemui.keyguard.shared.model.WakefulnessModel.Companion.isWakingOrStartingToWake
@@ -44,6 +45,7 @@ constructor(
override fun start() { override fun start() {
listenForDozingToLockscreen() listenForDozingToLockscreen()
listenForDozingToGone()
} }
private fun listenForDozingToLockscreen() { private fun listenForDozingToLockscreen() {
@@ -68,6 +70,28 @@ constructor(
} }
} }
private fun listenForDozingToGone() {
scope.launch {
keyguardInteractor.biometricUnlockState
.sample(keyguardTransitionInteractor.startedKeyguardTransitionStep, ::Pair)
.collect { (biometricUnlockState, lastStartedTransition) ->
if (
lastStartedTransition.to == KeyguardState.DOZING &&
isWakeAndUnlock(biometricUnlockState)
) {
keyguardTransitionRepository.startTransition(
TransitionInfo(
name,
KeyguardState.DOZING,
KeyguardState.GONE,
getAnimator(),
)
)
}
}
}
}
private fun getAnimator(duration: Duration = DEFAULT_DURATION): ValueAnimator { private fun getAnimator(duration: Duration = DEFAULT_DURATION): ValueAnimator {
return ValueAnimator().apply { return ValueAnimator().apply {
setInterpolator(Interpolators.LINEAR) setInterpolator(Interpolators.LINEAR)

View File

@@ -19,7 +19,6 @@ package com.android.systemui.keyguard.domain.interactor
import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository 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
import com.android.systemui.keyguard.shared.model.KeyguardState.AOD import com.android.systemui.keyguard.shared.model.KeyguardState.AOD
import com.android.systemui.keyguard.shared.model.KeyguardState.BOUNCER 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.TransitionState.STARTED
import com.android.systemui.keyguard.shared.model.TransitionStep import com.android.systemui.keyguard.shared.model.TransitionStep
import javax.inject.Inject 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.Flow
import kotlinx.coroutines.flow.filter import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.map
@@ -104,38 +100,4 @@ constructor(
/* The last completed [KeyguardState] transition */ /* The last completed [KeyguardState] transition */
val finishedKeyguardState: Flow<KeyguardState> = val finishedKeyguardState: Flow<KeyguardState> =
finishedKeyguardTransitionStep.map { step -> step.to } 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.dagger.SysUISingleton
import com.android.systemui.keyguard.domain.interactor.FromDreamingTransitionInteractor.Companion.TO_LOCKSCREEN_DURATION 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.domain.interactor.KeyguardTransitionInteractor
import com.android.systemui.keyguard.shared.model.AnimationParams import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow
import com.android.systemui.keyguard.shared.model.TransitionState.CANCELED
import com.android.systemui.keyguard.shared.model.TransitionState.FINISHED
import javax.inject.Inject import javax.inject.Inject
import kotlin.time.Duration.Companion.milliseconds import kotlin.time.Duration.Companion.milliseconds
import kotlinx.coroutines.flow.Flow 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 * Breaks down DREAMING->LOCKSCREEN transition into discrete steps for corresponding views to
@@ -41,39 +36,46 @@ class DreamingToLockscreenTransitionViewModel
constructor( constructor(
private val interactor: KeyguardTransitionInteractor, private val interactor: KeyguardTransitionInteractor,
) { ) {
private val transitionAnimation =
KeyguardTransitionAnimationFlow(
transitionDuration = TO_LOCKSCREEN_DURATION,
transitionFlow = interactor.dreamingToLockscreenTransition,
)
/** Dream overlay y-translation on exit */ /** Dream overlay y-translation on exit */
fun dreamOverlayTranslationY(translatePx: Int): Flow<Float> { fun dreamOverlayTranslationY(translatePx: Int): Flow<Float> {
return flowForAnimation(DREAM_OVERLAY_TRANSLATION_Y).map { value -> return transitionAnimation.createFlow(
EMPHASIZED_ACCELERATE.getInterpolation(value) * translatePx duration = 600.milliseconds,
} onStep = { it * translatePx },
interpolator = EMPHASIZED_ACCELERATE,
)
} }
/** Dream overlay views alpha - fade out */ /** 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 */ /** Lockscreen views y-translation */
fun lockscreenTranslationY(translatePx: Int): Flow<Float> { fun lockscreenTranslationY(translatePx: Int): Flow<Float> {
return merge( return transitionAnimation.createFlow(
flowForAnimation(LOCKSCREEN_TRANSLATION_Y).map { value -> duration = TO_LOCKSCREEN_DURATION,
-translatePx + (EMPHASIZED_DECELERATE.getInterpolation(value) * translatePx) onStep = { value -> -translatePx + value * translatePx },
}, // Reset on cancel or finish
// On end, reset the translation to 0 onFinish = { 0f },
interactor.dreamingToLockscreenTransition onCancel = { 0f },
.filter { it.transitionState == FINISHED || it.transitionState == CANCELED } interpolator = EMPHASIZED_DECELERATE,
.map { 0f }
) )
} }
/** Lockscreen views alpha */ /** Lockscreen views alpha */
val lockscreenAlpha: Flow<Float> = flowForAnimation(LOCKSCREEN_ALPHA) val lockscreenAlpha: Flow<Float> =
transitionAnimation.createFlow(
private fun flowForAnimation(params: AnimationParams): Flow<Float> { startTime = 233.milliseconds,
return interactor.transitionStepAnimation( duration = 250.milliseconds,
interactor.dreamingToLockscreenTransition, onStep = { it },
params,
totalDuration = TO_LOCKSCREEN_DURATION
) )
}
companion object { companion object {
/* Length of time before ending the dream activity, in order to start unoccluding */ /* Length of time before ending the dream activity, in order to start unoccluding */
@@ -81,11 +83,5 @@ constructor(
@JvmField @JvmField
val LOCKSCREEN_ANIMATION_DURATION_MS = val LOCKSCREEN_ANIMATION_DURATION_MS =
(TO_LOCKSCREEN_DURATION - DREAM_ANIMATION_DURATION).inWholeMilliseconds (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.dagger.SysUISingleton
import com.android.systemui.keyguard.domain.interactor.FromGoneTransitionInteractor.Companion.TO_DREAMING_DURATION 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.domain.interactor.KeyguardTransitionInteractor
import com.android.systemui.keyguard.shared.model.AnimationParams import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow
import com.android.systemui.keyguard.shared.model.TransitionState.CANCELED
import com.android.systemui.keyguard.shared.model.TransitionState.FINISHED
import javax.inject.Inject import javax.inject.Inject
import kotlin.time.Duration.Companion.milliseconds import kotlin.time.Duration.Companion.milliseconds
import kotlinx.coroutines.flow.Flow 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. */ /** Breaks down GONE->DREAMING transition into discrete steps for corresponding views to consume. */
@SysUISingleton @SysUISingleton
@@ -38,32 +33,28 @@ constructor(
private val interactor: KeyguardTransitionInteractor, private val interactor: KeyguardTransitionInteractor,
) { ) {
private val transitionAnimation =
KeyguardTransitionAnimationFlow(
transitionDuration = TO_DREAMING_DURATION,
transitionFlow = interactor.goneToDreamingTransition,
)
/** Lockscreen views y-translation */ /** Lockscreen views y-translation */
fun lockscreenTranslationY(translatePx: Int): Flow<Float> { fun lockscreenTranslationY(translatePx: Int): Flow<Float> {
return merge( return transitionAnimation.createFlow(
flowForAnimation(LOCKSCREEN_TRANSLATION_Y).map { value -> duration = 500.milliseconds,
(EMPHASIZED_ACCELERATE.getInterpolation(value) * translatePx) onStep = { it * translatePx },
}, // Reset on cancel or finish
// On end, reset the translation to 0 onFinish = { 0f },
interactor.goneToDreamingTransition onCancel = { 0f },
.filter { it.transitionState == FINISHED || it.transitionState == CANCELED } interpolator = EMPHASIZED_ACCELERATE,
.map { 0f }
) )
} }
/** Lockscreen views alpha */ /** Lockscreen views alpha */
val lockscreenAlpha: Flow<Float> = flowForAnimation(LOCKSCREEN_ALPHA).map { 1f - it } val lockscreenAlpha: Flow<Float> =
transitionAnimation.createFlow(
private fun flowForAnimation(params: AnimationParams): Flow<Float> { duration = 250.milliseconds,
return interactor.transitionStepAnimation( onStep = { 1f - it },
interactor.goneToDreamingTransition,
params,
totalDuration = TO_DREAMING_DURATION
) )
}
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.dagger.SysUISingleton
import com.android.systemui.keyguard.domain.interactor.FromLockscreenTransitionInteractor.Companion.TO_DREAMING_DURATION 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.domain.interactor.KeyguardTransitionInteractor
import com.android.systemui.keyguard.shared.model.AnimationParams import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow
import com.android.systemui.keyguard.shared.model.TransitionState.CANCELED
import com.android.systemui.keyguard.shared.model.TransitionState.FINISHED
import javax.inject.Inject import javax.inject.Inject
import kotlin.time.Duration.Companion.milliseconds import kotlin.time.Duration.Companion.milliseconds
import kotlinx.coroutines.flow.Flow 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 * Breaks down LOCKSCREEN->DREAMING transition into discrete steps for corresponding views to
@@ -40,35 +35,32 @@ class LockscreenToDreamingTransitionViewModel
constructor( constructor(
private val interactor: KeyguardTransitionInteractor, private val interactor: KeyguardTransitionInteractor,
) { ) {
private val transitionAnimation =
KeyguardTransitionAnimationFlow(
transitionDuration = TO_DREAMING_DURATION,
transitionFlow = interactor.lockscreenToDreamingTransition,
)
/** Lockscreen views y-translation */ /** Lockscreen views y-translation */
fun lockscreenTranslationY(translatePx: Int): Flow<Float> { fun lockscreenTranslationY(translatePx: Int): Flow<Float> {
return merge( return transitionAnimation.createFlow(
flowForAnimation(LOCKSCREEN_TRANSLATION_Y).map { value -> duration = 500.milliseconds,
(EMPHASIZED_ACCELERATE.getInterpolation(value) * translatePx) onStep = { it * translatePx },
}, // Reset on cancel or finish
// On end, reset the translation to 0 onFinish = { 0f },
interactor.lockscreenToDreamingTransition onCancel = { 0f },
.filter { it.transitionState == FINISHED || it.transitionState == CANCELED } interpolator = EMPHASIZED_ACCELERATE,
.map { 0f }
) )
} }
/** Lockscreen views alpha */ /** Lockscreen views alpha */
val lockscreenAlpha: Flow<Float> = flowForAnimation(LOCKSCREEN_ALPHA).map { 1f - it } val lockscreenAlpha: Flow<Float> =
transitionAnimation.createFlow(
private fun flowForAnimation(params: AnimationParams): Flow<Float> { duration = 250.milliseconds,
return interactor.transitionStepAnimation( onStep = { 1f - it },
interactor.lockscreenToDreamingTransition,
params,
totalDuration = TO_DREAMING_DURATION
) )
}
companion object { companion object {
@JvmField val DREAMING_ANIMATION_DURATION_MS = TO_DREAMING_DURATION.inWholeMilliseconds @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.dagger.SysUISingleton
import com.android.systemui.keyguard.domain.interactor.FromLockscreenTransitionInteractor.Companion.TO_OCCLUDED_DURATION 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.domain.interactor.KeyguardTransitionInteractor
import com.android.systemui.keyguard.shared.model.AnimationParams import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow
import com.android.systemui.keyguard.shared.model.TransitionState
import javax.inject.Inject import javax.inject.Inject
import kotlin.time.Duration.Companion.milliseconds import kotlin.time.Duration.Companion.milliseconds
import kotlinx.coroutines.flow.Flow 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 * Breaks down LOCKSCREEN->OCCLUDED transition into discrete steps for corresponding views to
@@ -39,33 +35,28 @@ class LockscreenToOccludedTransitionViewModel
constructor( constructor(
private val interactor: KeyguardTransitionInteractor, 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 */ /** Lockscreen views y-translation */
fun lockscreenTranslationY(translatePx: Int): Flow<Float> { fun lockscreenTranslationY(translatePx: Int): Flow<Float> {
return merge( return transitionAnimation.createFlow(
flowForAnimation(LOCKSCREEN_TRANSLATION_Y).map { value -> duration = TO_OCCLUDED_DURATION,
(EMPHASIZED_ACCELERATE.getInterpolation(value) * translatePx) onStep = { value -> value * translatePx },
}, // Reset on cancel or finish
// On end, reset the translation to 0 onFinish = { 0f },
interactor.lockscreenToOccludedTransition onCancel = { 0f },
.filter { step -> step.transitionState == TransitionState.FINISHED } interpolator = EMPHASIZED_ACCELERATE,
.map { 0f }
) )
} }
/** 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.dagger.SysUISingleton
import com.android.systemui.keyguard.domain.interactor.FromOccludedTransitionInteractor.Companion.TO_LOCKSCREEN_DURATION 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.domain.interactor.KeyguardTransitionInteractor
import com.android.systemui.keyguard.shared.model.AnimationParams import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow
import javax.inject.Inject import javax.inject.Inject
import kotlin.time.Duration.Companion.milliseconds import kotlin.time.Duration.Companion.milliseconds
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.map
/** /**
* Breaks down OCCLUDED->LOCKSCREEN transition into discrete steps for corresponding views to * Breaks down OCCLUDED->LOCKSCREEN transition into discrete steps for corresponding views to
@@ -36,28 +35,26 @@ class OccludedToLockscreenTransitionViewModel
constructor( constructor(
private val interactor: KeyguardTransitionInteractor, private val interactor: KeyguardTransitionInteractor,
) { ) {
private val transitionAnimation =
KeyguardTransitionAnimationFlow(
transitionDuration = TO_LOCKSCREEN_DURATION,
transitionFlow = interactor.occludedToLockscreenTransition,
)
/** Lockscreen views y-translation */ /** Lockscreen views y-translation */
fun lockscreenTranslationY(translatePx: Int): Flow<Float> { fun lockscreenTranslationY(translatePx: Int): Flow<Float> {
return flowForAnimation(LOCKSCREEN_TRANSLATION_Y).map { value -> return transitionAnimation.createFlow(
-translatePx + (EMPHASIZED_DECELERATE.getInterpolation(value) * translatePx) duration = TO_LOCKSCREEN_DURATION,
} onStep = { value -> -translatePx + value * translatePx },
} interpolator = EMPHASIZED_DECELERATE,
/** Lockscreen views alpha */
val lockscreenAlpha: Flow<Float> = flowForAnimation(LOCKSCREEN_ALPHA)
private fun flowForAnimation(params: AnimationParams): Flow<Float> {
return interactor.transitionStepAnimation(
interactor.occludedToLockscreenTransition,
params,
totalDuration = TO_LOCKSCREEN_DURATION
) )
} }
companion object { /** Lockscreen views alpha */
@JvmField val LOCKSCREEN_ANIMATION_DURATION_MS = TO_LOCKSCREEN_DURATION.inWholeMilliseconds val lockscreenAlpha: Flow<Float> =
val LOCKSCREEN_TRANSLATION_Y = AnimationParams(duration = TO_LOCKSCREEN_DURATION) transitionAnimation.createFlow(
val LOCKSCREEN_ALPHA = startTime = 233.milliseconds,
AnimationParams(startTime = 233.milliseconds, duration = 250.milliseconds) duration = 250.milliseconds,
} onStep = { it },
)
} }

View File

@@ -24,6 +24,7 @@ import com.android.systemui.animation.Interpolators
import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository
import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepositoryImpl import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepositoryImpl
import com.android.systemui.keyguard.shared.model.BiometricUnlockModel
import com.android.systemui.keyguard.shared.model.DozeStateModel import com.android.systemui.keyguard.shared.model.DozeStateModel
import com.android.systemui.keyguard.shared.model.DozeTransitionModel import com.android.systemui.keyguard.shared.model.DozeTransitionModel
import com.android.systemui.keyguard.shared.model.KeyguardState import com.android.systemui.keyguard.shared.model.KeyguardState
@@ -436,6 +437,43 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() {
coroutineContext.cancelChildren() coroutineContext.cancelChildren()
} }
@Test
fun `DOZING to GONE`() =
testScope.runTest {
// GIVEN a prior transition has run to DOZING
runner.startTransition(
testScope,
TransitionInfo(
ownerName = "",
from = KeyguardState.LOCKSCREEN,
to = KeyguardState.DOZING,
animator =
ValueAnimator().apply {
duration = 10
interpolator = Interpolators.LINEAR
},
)
)
runCurrent()
reset(mockTransitionRepository)
// WHEN biometrics succeeds with wake and unlock mode
keyguardRepository.setBiometricUnlockState(BiometricUnlockModel.WAKE_AND_UNLOCK)
runCurrent()
val info =
withArgCaptor<TransitionInfo> {
verify(mockTransitionRepository).startTransition(capture())
}
// THEN a transition to DOZING should occur
assertThat(info.ownerName).isEqualTo("FromDozingTransitionInteractor")
assertThat(info.from).isEqualTo(KeyguardState.DOZING)
assertThat(info.to).isEqualTo(KeyguardState.GONE)
assertThat(info.animator).isNotNull()
coroutineContext.cancelChildren()
}
@Test @Test
fun `GONE to DOZING`() = fun `GONE to DOZING`() =
testScope.runTest { testScope.runTest {

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 androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase 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.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.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.KeyguardState
import com.android.systemui.keyguard.shared.model.TransitionState import com.android.systemui.keyguard.shared.model.TransitionState
import com.android.systemui.keyguard.shared.model.TransitionStep 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.KeyguardTransitionAnimationFlow
import com.android.systemui.keyguard.ui.viewmodel.DreamingToLockscreenTransitionViewModel.Companion.DREAM_OVERLAY_TRANSLATION_Y import com.google.common.collect.Range
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.google.common.truth.Truth.assertThat import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.onEach
@@ -46,6 +40,7 @@ import org.junit.runners.JUnit4
class DreamingToLockscreenTransitionViewModelTest : SysuiTestCase() { class DreamingToLockscreenTransitionViewModelTest : SysuiTestCase() {
private lateinit var underTest: DreamingToLockscreenTransitionViewModel private lateinit var underTest: DreamingToLockscreenTransitionViewModel
private lateinit var repository: FakeKeyguardTransitionRepository private lateinit var repository: FakeKeyguardTransitionRepository
private lateinit var transitionAnimation: KeyguardTransitionAnimationFlow
@Before @Before
fun setUp() { fun setUp() {
@@ -63,32 +58,18 @@ class DreamingToLockscreenTransitionViewModelTest : SysuiTestCase() {
val job = val job =
underTest.dreamOverlayTranslationY(pixels).onEach { values.add(it) }.launchIn(this) 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(0f))
repository.sendTransitionStep(step(0.3f)) repository.sendTransitionStep(step(0.3f))
repository.sendTransitionStep(step(0.5f)) repository.sendTransitionStep(step(0.5f))
repository.sendTransitionStep(step(0.6f))
// ...up to here
repository.sendTransitionStep(step(0.8f))
repository.sendTransitionStep(step(1f)) repository.sendTransitionStep(step(1f))
// Only 3 values should be present, since the dream overlay runs for a small fraction assertThat(values.size).isEqualTo(5)
// of the overall animation time values.forEach { assertThat(it).isIn(Range.closed(0f, 100f)) }
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
)
job.cancel() job.cancel()
} }
@@ -100,16 +81,18 @@ class DreamingToLockscreenTransitionViewModelTest : SysuiTestCase() {
val job = underTest.dreamOverlayAlpha.onEach { values.add(it) }.launchIn(this) 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(0f))
repository.sendTransitionStep(step(0.1f)) repository.sendTransitionStep(step(0.1f))
repository.sendTransitionStep(step(0.5f)) repository.sendTransitionStep(step(0.5f))
// ...up to here
repository.sendTransitionStep(step(1f)) repository.sendTransitionStep(step(1f))
// Only two values should be present, since the dream overlay runs for a small fraction // Only two values should be present, since the dream overlay runs for a small fraction
// of the overall animation time // of the overall animation time
assertThat(values.size).isEqualTo(2) assertThat(values.size).isEqualTo(4)
assertThat(values[0]).isEqualTo(1f - animValue(0f, DREAM_OVERLAY_ALPHA)) values.forEach { assertThat(it).isIn(Range.closed(0f, 1f)) }
assertThat(values[1]).isEqualTo(1f - animValue(0.1f, DREAM_OVERLAY_ALPHA))
job.cancel() job.cancel()
} }
@@ -121,19 +104,15 @@ class DreamingToLockscreenTransitionViewModelTest : SysuiTestCase() {
val job = underTest.lockscreenAlpha.onEach { values.add(it) }.launchIn(this) val job = underTest.lockscreenAlpha.onEach { values.add(it) }.launchIn(this)
repository.sendTransitionStep(step(0f, TransitionState.STARTED))
repository.sendTransitionStep(step(0f)) repository.sendTransitionStep(step(0f))
repository.sendTransitionStep(step(0.1f)) repository.sendTransitionStep(step(0.1f))
// Should start running here...
repository.sendTransitionStep(step(0.2f)) repository.sendTransitionStep(step(0.2f))
repository.sendTransitionStep(step(0.3f)) repository.sendTransitionStep(step(0.3f))
// ...up to here
repository.sendTransitionStep(step(1f)) repository.sendTransitionStep(step(1f))
// Only two values should be present, since the dream overlay runs for a small fraction assertThat(values.size).isEqualTo(4)
// of the overall animation time values.forEach { assertThat(it).isIn(Range.closed(0f, 1f)) }
assertThat(values.size).isEqualTo(2)
assertThat(values[0]).isEqualTo(animValue(0.2f, LOCKSCREEN_ALPHA))
assertThat(values[1]).isEqualTo(animValue(0.3f, LOCKSCREEN_ALPHA))
job.cancel() job.cancel()
} }
@@ -147,58 +126,27 @@ class DreamingToLockscreenTransitionViewModelTest : SysuiTestCase() {
val job = val job =
underTest.lockscreenTranslationY(pixels).onEach { values.add(it) }.launchIn(this) underTest.lockscreenTranslationY(pixels).onEach { values.add(it) }.launchIn(this)
repository.sendTransitionStep(step(0f, TransitionState.STARTED))
repository.sendTransitionStep(step(0f)) repository.sendTransitionStep(step(0f))
repository.sendTransitionStep(step(0.3f)) repository.sendTransitionStep(step(0.3f))
repository.sendTransitionStep(step(0.5f)) repository.sendTransitionStep(step(0.5f))
repository.sendTransitionStep(step(1f)) repository.sendTransitionStep(step(1f))
assertThat(values.size).isEqualTo(4) assertThat(values.size).isEqualTo(5)
assertThat(values[0]) values.forEach { assertThat(it).isIn(Range.closed(-100f, 0f)) }
.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
)
job.cancel() job.cancel()
} }
private fun animValue(stepValue: Float, params: AnimationParams): Float { private fun step(
val totalDuration = TO_LOCKSCREEN_DURATION value: Float,
val startValue = (params.startTime / totalDuration).toFloat() state: TransitionState = TransitionState.RUNNING
): TransitionStep {
val multiplier = (totalDuration / params.duration).toFloat()
return (stepValue - startValue) * multiplier
}
private fun step(value: Float): TransitionStep {
return TransitionStep( return TransitionStep(
from = KeyguardState.DREAMING, from = KeyguardState.DREAMING,
to = KeyguardState.LOCKSCREEN, to = KeyguardState.LOCKSCREEN,
value = value, value = value,
transitionState = TransitionState.RUNNING, transitionState = state,
ownerName = "DreamingToLockscreenTransitionViewModelTest" ownerName = "DreamingToLockscreenTransitionViewModelTest"
) )
} }

View File

@@ -18,16 +18,12 @@ package com.android.systemui.keyguard.ui.viewmodel
import androidx.test.filters.SmallTest import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase 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.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.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.KeyguardState
import com.android.systemui.keyguard.shared.model.TransitionState import com.android.systemui.keyguard.shared.model.TransitionState
import com.android.systemui.keyguard.shared.model.TransitionStep import com.android.systemui.keyguard.shared.model.TransitionStep
import com.android.systemui.keyguard.ui.viewmodel.GoneToDreamingTransitionViewModel.Companion.LOCKSCREEN_ALPHA import com.google.common.collect.Range
import com.android.systemui.keyguard.ui.viewmodel.GoneToDreamingTransitionViewModel.Companion.LOCKSCREEN_TRANSLATION_Y
import com.google.common.truth.Truth.assertThat import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.onEach
@@ -59,20 +55,18 @@ class GoneToDreamingTransitionViewModelTest : SysuiTestCase() {
val job = underTest.lockscreenAlpha.onEach { values.add(it) }.launchIn(this) val job = underTest.lockscreenAlpha.onEach { values.add(it) }.launchIn(this)
// Should start running here... // Should start running here...
repository.sendTransitionStep(step(0f, TransitionState.STARTED))
repository.sendTransitionStep(step(0f)) repository.sendTransitionStep(step(0f))
repository.sendTransitionStep(step(0.1f)) repository.sendTransitionStep(step(0.1f))
repository.sendTransitionStep(step(0.2f)) repository.sendTransitionStep(step(0.2f))
// ...up to here
repository.sendTransitionStep(step(0.3f)) repository.sendTransitionStep(step(0.3f))
// ...up to here
repository.sendTransitionStep(step(1f)) repository.sendTransitionStep(step(1f))
// Only three values should be present, since the dream overlay runs for a small // Only three values should be present, since the dream overlay runs for a small
// fraction // fraction of the overall animation time
// of the overall animation time assertThat(values.size).isEqualTo(5)
assertThat(values.size).isEqualTo(3) values.forEach { assertThat(it).isIn(Range.closed(0f, 1f)) }
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))
job.cancel() job.cancel()
} }
@@ -87,45 +81,19 @@ class GoneToDreamingTransitionViewModelTest : SysuiTestCase() {
underTest.lockscreenTranslationY(pixels).onEach { values.add(it) }.launchIn(this) underTest.lockscreenTranslationY(pixels).onEach { values.add(it) }.launchIn(this)
// Should start running here... // Should start running here...
repository.sendTransitionStep(step(0f, TransitionState.STARTED))
repository.sendTransitionStep(step(0f)) repository.sendTransitionStep(step(0f))
repository.sendTransitionStep(step(0.3f)) repository.sendTransitionStep(step(0.3f))
repository.sendTransitionStep(step(0.5f)) repository.sendTransitionStep(step(0.5f))
// ...up to here
repository.sendTransitionStep(step(1f))
// And a final reset event on CANCEL // And a final reset event on CANCEL
repository.sendTransitionStep(step(0.8f, TransitionState.CANCELED)) repository.sendTransitionStep(step(0.8f, TransitionState.CANCELED))
assertThat(values.size).isEqualTo(4) assertThat(values.size).isEqualTo(5)
assertThat(values[0]) values.forEach { assertThat(it).isIn(Range.closed(0f, 100f)) }
.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)
job.cancel() 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( private fun step(
value: Float, value: Float,
state: TransitionState = TransitionState.RUNNING state: TransitionState = TransitionState.RUNNING

View File

@@ -18,16 +18,12 @@ package com.android.systemui.keyguard.ui.viewmodel
import androidx.test.filters.SmallTest import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase 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.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.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.KeyguardState
import com.android.systemui.keyguard.shared.model.TransitionState import com.android.systemui.keyguard.shared.model.TransitionState
import com.android.systemui.keyguard.shared.model.TransitionStep import com.android.systemui.keyguard.shared.model.TransitionStep
import com.android.systemui.keyguard.ui.viewmodel.LockscreenToDreamingTransitionViewModel.Companion.LOCKSCREEN_ALPHA import com.google.common.collect.Range
import com.android.systemui.keyguard.ui.viewmodel.LockscreenToDreamingTransitionViewModel.Companion.LOCKSCREEN_TRANSLATION_Y
import com.google.common.truth.Truth.assertThat import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.onEach
@@ -59,19 +55,18 @@ class LockscreenToDreamingTransitionViewModelTest : SysuiTestCase() {
val job = underTest.lockscreenAlpha.onEach { values.add(it) }.launchIn(this) val job = underTest.lockscreenAlpha.onEach { values.add(it) }.launchIn(this)
// Should start running here... // Should start running here...
repository.sendTransitionStep(step(0f, TransitionState.STARTED))
repository.sendTransitionStep(step(0f)) repository.sendTransitionStep(step(0f))
repository.sendTransitionStep(step(0.1f)) repository.sendTransitionStep(step(0.1f))
repository.sendTransitionStep(step(0.2f)) repository.sendTransitionStep(step(0.2f))
// ...up to here
repository.sendTransitionStep(step(0.3f)) repository.sendTransitionStep(step(0.3f))
// ...up to here
repository.sendTransitionStep(step(1f)) repository.sendTransitionStep(step(1f))
// Only three values should be present, since the dream overlay runs for a small // Only three values should be present, since the dream overlay runs for a small
// fraction of the overall animation time // fraction of the overall animation time
assertThat(values.size).isEqualTo(3) assertThat(values.size).isEqualTo(5)
assertThat(values[0]).isEqualTo(1f - animValue(0f, LOCKSCREEN_ALPHA)) values.forEach { assertThat(it).isIn(Range.closed(0f, 1f)) }
assertThat(values[1]).isEqualTo(1f - animValue(0.1f, LOCKSCREEN_ALPHA))
assertThat(values[2]).isEqualTo(1f - animValue(0.2f, LOCKSCREEN_ALPHA))
job.cancel() job.cancel()
} }
@@ -85,47 +80,22 @@ class LockscreenToDreamingTransitionViewModelTest : SysuiTestCase() {
val job = val job =
underTest.lockscreenTranslationY(pixels).onEach { values.add(it) }.launchIn(this) 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(0f))
repository.sendTransitionStep(step(0.3f)) repository.sendTransitionStep(step(0.3f))
repository.sendTransitionStep(step(0.5f)) repository.sendTransitionStep(step(0.5f))
// ...up to here
repository.sendTransitionStep(step(1f)) repository.sendTransitionStep(step(1f))
// And a final reset event on FINISHED // And a final reset event on FINISHED
repository.sendTransitionStep(step(1f, TransitionState.FINISHED)) repository.sendTransitionStep(step(1f, TransitionState.FINISHED))
assertThat(values.size).isEqualTo(4) assertThat(values.size).isEqualTo(6)
assertThat(values[0]) values.forEach { assertThat(it).isIn(Range.closed(0f, 100f)) }
.isEqualTo( // Validate finished value
EMPHASIZED_ACCELERATE.getInterpolation( assertThat(values[5]).isEqualTo(0f)
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)
job.cancel() 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( private fun step(
value: Float, value: Float,
state: TransitionState = TransitionState.RUNNING state: TransitionState = TransitionState.RUNNING

View File

@@ -18,16 +18,12 @@ package com.android.systemui.keyguard.ui.viewmodel
import androidx.test.filters.SmallTest import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase 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.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.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.KeyguardState
import com.android.systemui.keyguard.shared.model.TransitionState import com.android.systemui.keyguard.shared.model.TransitionState
import com.android.systemui.keyguard.shared.model.TransitionStep import com.android.systemui.keyguard.shared.model.TransitionStep
import com.android.systemui.keyguard.ui.viewmodel.LockscreenToOccludedTransitionViewModel.Companion.LOCKSCREEN_ALPHA import com.google.common.collect.Range
import com.android.systemui.keyguard.ui.viewmodel.LockscreenToOccludedTransitionViewModel.Companion.LOCKSCREEN_TRANSLATION_Y
import com.google.common.truth.Truth.assertThat import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.onEach
@@ -59,19 +55,18 @@ class LockscreenToOccludedTransitionViewModelTest : SysuiTestCase() {
val job = underTest.lockscreenAlpha.onEach { values.add(it) }.launchIn(this) val job = underTest.lockscreenAlpha.onEach { values.add(it) }.launchIn(this)
// Should start running here... // Should start running here...
repository.sendTransitionStep(step(0f, TransitionState.STARTED))
repository.sendTransitionStep(step(0f)) repository.sendTransitionStep(step(0f))
repository.sendTransitionStep(step(0.1f)) repository.sendTransitionStep(step(0.1f))
repository.sendTransitionStep(step(0.4f)) repository.sendTransitionStep(step(0.4f))
// ...up to here
repository.sendTransitionStep(step(0.7f)) repository.sendTransitionStep(step(0.7f))
// ...up to here
repository.sendTransitionStep(step(1f)) repository.sendTransitionStep(step(1f))
// Only 3 values should be present, since the dream overlay runs for a small fraction // Only 3 values should be present, since the dream overlay runs for a small fraction
// of the overall animation time // of the overall animation time
assertThat(values.size).isEqualTo(3) assertThat(values.size).isEqualTo(5)
assertThat(values[0]).isEqualTo(1f - animValue(0f, LOCKSCREEN_ALPHA)) values.forEach { assertThat(it).isIn(Range.closed(0f, 1f)) }
assertThat(values[1]).isEqualTo(1f - animValue(0.1f, LOCKSCREEN_ALPHA))
assertThat(values[2]).isEqualTo(1f - animValue(0.4f, LOCKSCREEN_ALPHA))
job.cancel() job.cancel()
} }
@@ -86,54 +81,51 @@ class LockscreenToOccludedTransitionViewModelTest : SysuiTestCase() {
underTest.lockscreenTranslationY(pixels).onEach { values.add(it) }.launchIn(this) underTest.lockscreenTranslationY(pixels).onEach { values.add(it) }.launchIn(this)
// Should start running here... // Should start running here...
repository.sendTransitionStep(step(0f, TransitionState.STARTED))
repository.sendTransitionStep(step(0f)) repository.sendTransitionStep(step(0f))
repository.sendTransitionStep(step(0.3f)) repository.sendTransitionStep(step(0.3f))
repository.sendTransitionStep(step(0.5f)) repository.sendTransitionStep(step(0.5f))
repository.sendTransitionStep(step(1f)) repository.sendTransitionStep(step(1f))
// ...up to here // ...up to here
assertThat(values.size).isEqualTo(4) assertThat(values.size).isEqualTo(5)
assertThat(values[0]) values.forEach { assertThat(it).isIn(Range.closed(0f, 100f)) }
.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
)
job.cancel() job.cancel()
} }
private fun animValue(stepValue: Float, params: AnimationParams): Float { @Test
val totalDuration = TO_OCCLUDED_DURATION fun lockscreenTranslationYIsCanceled() =
val startValue = (params.startTime / totalDuration).toFloat() runTest(UnconfinedTestDispatcher()) {
val values = mutableListOf<Float>()
val multiplier = (totalDuration / params.duration).toFloat() val pixels = 100
return (stepValue - startValue) * multiplier 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( return TransitionStep(
from = KeyguardState.LOCKSCREEN, from = KeyguardState.LOCKSCREEN,
to = KeyguardState.OCCLUDED, to = KeyguardState.OCCLUDED,
value = value, value = value,
transitionState = TransitionState.RUNNING, transitionState = state,
ownerName = "LockscreenToOccludedTransitionViewModelTest" ownerName = "LockscreenToOccludedTransitionViewModelTest"
) )
} }

View File

@@ -18,16 +18,12 @@ package com.android.systemui.keyguard.ui.viewmodel
import androidx.test.filters.SmallTest import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase 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.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.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.KeyguardState
import com.android.systemui.keyguard.shared.model.TransitionState import com.android.systemui.keyguard.shared.model.TransitionState
import com.android.systemui.keyguard.shared.model.TransitionStep import com.android.systemui.keyguard.shared.model.TransitionStep
import com.android.systemui.keyguard.ui.viewmodel.OccludedToLockscreenTransitionViewModel.Companion.LOCKSCREEN_ALPHA import com.google.common.collect.Range
import com.android.systemui.keyguard.ui.viewmodel.OccludedToLockscreenTransitionViewModel.Companion.LOCKSCREEN_TRANSLATION_Y
import com.google.common.truth.Truth.assertThat import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.onEach
@@ -58,21 +54,19 @@ class OccludedToLockscreenTransitionViewModelTest : SysuiTestCase() {
val job = underTest.lockscreenAlpha.onEach { values.add(it) }.launchIn(this) 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... // Should start running here...
repository.sendTransitionStep(step(0.3f)) repository.sendTransitionStep(step(0.3f))
repository.sendTransitionStep(step(0.4f)) repository.sendTransitionStep(step(0.4f))
repository.sendTransitionStep(step(0.5f)) repository.sendTransitionStep(step(0.5f))
// ...up to here
repository.sendTransitionStep(step(0.6f)) repository.sendTransitionStep(step(0.6f))
// ...up to here
repository.sendTransitionStep(step(0.8f))
repository.sendTransitionStep(step(1f)) repository.sendTransitionStep(step(1f))
// Only two values should be present, since the dream overlay runs for a small fraction assertThat(values.size).isEqualTo(5)
// of the overall animation time values.forEach { assertThat(it).isIn(Range.closed(0f, 1f)) }
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))
job.cancel() job.cancel()
} }
@@ -86,58 +80,27 @@ class OccludedToLockscreenTransitionViewModelTest : SysuiTestCase() {
val job = val job =
underTest.lockscreenTranslationY(pixels).onEach { values.add(it) }.launchIn(this) underTest.lockscreenTranslationY(pixels).onEach { values.add(it) }.launchIn(this)
repository.sendTransitionStep(step(0f, TransitionState.STARTED))
repository.sendTransitionStep(step(0f)) repository.sendTransitionStep(step(0f))
repository.sendTransitionStep(step(0.3f)) repository.sendTransitionStep(step(0.3f))
repository.sendTransitionStep(step(0.5f)) repository.sendTransitionStep(step(0.5f))
repository.sendTransitionStep(step(1f)) repository.sendTransitionStep(step(1f))
assertThat(values.size).isEqualTo(4) assertThat(values.size).isEqualTo(5)
assertThat(values[0]) values.forEach { assertThat(it).isIn(Range.closed(-100f, 0f)) }
.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
)
job.cancel() job.cancel()
} }
private fun animValue(stepValue: Float, params: AnimationParams): Float { private fun step(
val totalDuration = TO_LOCKSCREEN_DURATION value: Float,
val startValue = (params.startTime / totalDuration).toFloat() state: TransitionState = TransitionState.RUNNING
): TransitionStep {
val multiplier = (totalDuration / params.duration).toFloat()
return (stepValue - startValue) * multiplier
}
private fun step(value: Float): TransitionStep {
return TransitionStep( return TransitionStep(
from = KeyguardState.OCCLUDED, from = KeyguardState.OCCLUDED,
to = KeyguardState.LOCKSCREEN, to = KeyguardState.LOCKSCREEN,
value = value, value = value,
transitionState = TransitionState.RUNNING, transitionState = state,
ownerName = "OccludedToLockscreenTransitionViewModelTest" ownerName = "OccludedToLockscreenTransitionViewModelTest"
) )
} }