[DO NOT MERGE] Coordinated DREAMING->LOCKSCREEN animation

Part 1: Update dream side. Shift to utilize
KeyguardTransitionRepository as the source for all motion between
states. DreamingToLockscreenTransitionViewModel contains all logic
that subdivides the total animation time into flowables that emit
events during only their proper subsection. This means one animator
now runs the view animations across all spaces.

As part of this, add DreamCallbackController as a data source for
KeyguardRepository. The dream activity will signal the transition to
begin via onWakeUp(), which will start the view motions, then run the
unocclude transition, and then finally the lockscreen view motions.

Part 2 coming next... lockscreen tweaks

Test: atest KeyguardRepositoryImplTest
DreamOverlayContainerViewControllerTest
DreamOverlayAnimationsControllerTest
DreamingToLockscreenTransitionViewModelTest DreamOverlayServiceTest
Bug: 260631205

Change-Id: I9bcf877927d1e92e8c751d0b83d2dd70f15912f1
This commit is contained in:
Matt Pietal
2022-12-06 19:26:36 +00:00
parent 08ea7a84f1
commit 79c7aaa06e
19 changed files with 565 additions and 265 deletions

View File

@@ -743,27 +743,6 @@
<!-- How long in milliseconds before full burn-in protection is achieved. -->
<integer name="config_dreamOverlayMillisUntilFullJitter">240000</integer>
<!-- The duration in milliseconds of the y-translation animation when waking up from
the dream -->
<integer name="config_dreamOverlayOutTranslationYDurationMs">333</integer>
<!-- The delay in milliseconds of the y-translation animation when waking up from
the dream for the complications at the bottom of the screen -->
<integer name="config_dreamOverlayOutTranslationYDelayBottomMs">33</integer>
<!-- The delay in milliseconds of the y-translation animation when waking up from
the dream for the complications at the top of the screen -->
<integer name="config_dreamOverlayOutTranslationYDelayTopMs">117</integer>
<!-- The duration in milliseconds of the alpha animation when waking up from the dream -->
<integer name="config_dreamOverlayOutAlphaDurationMs">200</integer>
<!-- The delay in milliseconds of the alpha animation when waking up from the dream for the
complications at the top of the screen -->
<integer name="config_dreamOverlayOutAlphaDelayTopMs">217</integer>
<!-- The delay in milliseconds of the alpha animation when waking up from the dream for the
complications at the bottom of the screen -->
<integer name="config_dreamOverlayOutAlphaDelayBottomMs">133</integer>
<!-- The duration in milliseconds of the blur animation when waking up from
the dream -->
<integer name="config_dreamOverlayOutBlurDurationMs">250</integer>
<integer name="complicationFadeOutMs">500</integer>
<integer name="complicationFadeInMs">500</integer>

View File

@@ -0,0 +1,45 @@
/*
* 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.dreams
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.statusbar.policy.CallbackController
import javax.inject.Inject
/** Dream-related callback information */
@SysUISingleton
class DreamCallbackController @Inject constructor() :
CallbackController<DreamCallbackController.DreamCallback> {
private val callbacks = mutableSetOf<DreamCallbackController.DreamCallback>()
override fun addCallback(callback: DreamCallbackController.DreamCallback) {
callbacks.add(callback)
}
override fun removeCallback(callback: DreamCallbackController.DreamCallback) {
callbacks.remove(callback)
}
fun onWakeUp() {
callbacks.forEach { it.onWakeUp() }
}
interface DreamCallback {
fun onWakeUp()
}
}

View File

@@ -22,6 +22,9 @@ import android.animation.ValueAnimator
import android.view.View
import android.view.animation.Interpolator
import androidx.core.animation.doOnEnd
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.repeatOnLifecycle
import com.android.systemui.R
import com.android.systemui.animation.Interpolators
import com.android.systemui.dreams.complication.ComplicationHostViewController
import com.android.systemui.dreams.complication.ComplicationLayoutParams
@@ -29,10 +32,20 @@ import com.android.systemui.dreams.complication.ComplicationLayoutParams.POSITIO
import com.android.systemui.dreams.complication.ComplicationLayoutParams.POSITION_TOP
import com.android.systemui.dreams.complication.ComplicationLayoutParams.Position
import com.android.systemui.dreams.dagger.DreamOverlayModule
import com.android.systemui.keyguard.ui.viewmodel.DreamingToLockscreenTransitionViewModel
import com.android.systemui.keyguard.ui.viewmodel.DreamingToLockscreenTransitionViewModel.Companion.DREAM_ANIMATION_DURATION
import com.android.systemui.lifecycle.repeatWhenAttached
import com.android.systemui.statusbar.BlurUtils
import com.android.systemui.statusbar.CrossFadeHelper
import com.android.systemui.statusbar.policy.ConfigurationController
import com.android.systemui.statusbar.policy.ConfigurationController.ConfigurationListener
import com.android.systemui.util.concurrency.DelayableExecutor
import javax.inject.Inject
import javax.inject.Named
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.launch
/** Controller for dream overlay animations. */
class DreamOverlayAnimationsController
@@ -43,6 +56,8 @@ constructor(
private val mStatusBarViewController: DreamOverlayStatusBarViewController,
private val mOverlayStateController: DreamOverlayStateController,
@Named(DreamOverlayModule.DREAM_BLUR_RADIUS) private val mDreamBlurRadius: Int,
private val transitionViewModel: DreamingToLockscreenTransitionViewModel,
private val configController: ConfigurationController,
@Named(DreamOverlayModule.DREAM_IN_BLUR_ANIMATION_DURATION)
private val mDreamInBlurAnimDurationMs: Long,
@Named(DreamOverlayModule.DREAM_IN_COMPLICATIONS_ANIMATION_DURATION)
@@ -51,22 +66,10 @@ constructor(
private val mDreamInTranslationYDistance: Int,
@Named(DreamOverlayModule.DREAM_IN_TRANSLATION_Y_DURATION)
private val mDreamInTranslationYDurationMs: Long,
@Named(DreamOverlayModule.DREAM_OUT_TRANSLATION_Y_DISTANCE)
private val mDreamOutTranslationYDistance: Int,
@Named(DreamOverlayModule.DREAM_OUT_TRANSLATION_Y_DURATION)
private val mDreamOutTranslationYDurationMs: Long,
@Named(DreamOverlayModule.DREAM_OUT_TRANSLATION_Y_DELAY_BOTTOM)
private val mDreamOutTranslationYDelayBottomMs: Long,
@Named(DreamOverlayModule.DREAM_OUT_TRANSLATION_Y_DELAY_TOP)
private val mDreamOutTranslationYDelayTopMs: Long,
@Named(DreamOverlayModule.DREAM_OUT_ALPHA_DURATION) private val mDreamOutAlphaDurationMs: Long,
@Named(DreamOverlayModule.DREAM_OUT_ALPHA_DELAY_BOTTOM)
private val mDreamOutAlphaDelayBottomMs: Long,
@Named(DreamOverlayModule.DREAM_OUT_ALPHA_DELAY_TOP) private val mDreamOutAlphaDelayTopMs: Long,
@Named(DreamOverlayModule.DREAM_OUT_BLUR_DURATION) private val mDreamOutBlurDurationMs: Long
) {
private var mAnimator: Animator? = null
private lateinit var view: View
/**
* Store the current alphas at the various positions. This is so that we may resume an animation
@@ -76,9 +79,63 @@ constructor(
private var mCurrentBlurRadius: Float = 0f
fun init(view: View) {
this.view = view
view.repeatWhenAttached {
val configurationBasedDimensions = MutableStateFlow(loadFromResources(view))
val configCallback =
object : ConfigurationListener {
override fun onDensityOrFontScaleChanged() {
configurationBasedDimensions.value = loadFromResources(view)
}
}
configController.addCallback(configCallback)
repeatOnLifecycle(Lifecycle.State.CREATED) {
/* Translation animations, when moving from DREAMING->LOCKSCREEN state */
launch {
configurationBasedDimensions
.flatMapLatest {
transitionViewModel.dreamOverlayTranslationY(it.translationYPx)
}
.collect { px ->
setElementsTranslationYAtPosition(
px,
ComplicationLayoutParams.POSITION_TOP
)
setElementsTranslationYAtPosition(
px,
ComplicationLayoutParams.POSITION_BOTTOM
)
}
}
/* Alpha animations, when moving from DREAMING->LOCKSCREEN state */
launch {
transitionViewModel.dreamOverlayAlpha.collect { alpha ->
setElementsAlphaAtPosition(
alpha = alpha,
position = ComplicationLayoutParams.POSITION_TOP,
fadingOut = true,
)
setElementsAlphaAtPosition(
alpha = alpha,
position = ComplicationLayoutParams.POSITION_BOTTOM,
fadingOut = true,
)
}
}
}
configController.removeCallback(configCallback)
}
}
/** Starts the dream content and dream overlay entry animations. */
@JvmOverloads
fun startEntryAnimations(view: View, animatorBuilder: () -> AnimatorSet = { AnimatorSet() }) {
fun startEntryAnimations(animatorBuilder: () -> AnimatorSet = { AnimatorSet() }) {
cancelAnimations()
mAnimator =
@@ -113,73 +170,9 @@ constructor(
}
/** Starts the dream content and dream overlay exit animations. */
@JvmOverloads
fun startExitAnimations(
view: View,
doneCallback: () -> Unit,
animatorBuilder: () -> AnimatorSet = { AnimatorSet() }
) {
fun wakeUp(doneCallback: Runnable, executor: DelayableExecutor) {
cancelAnimations()
mAnimator =
animatorBuilder().apply {
playTogether(
blurAnimator(
view = view,
// Start the blurring wherever the entry animation ended, in
// case it was cancelled early.
fromBlurRadius = mCurrentBlurRadius,
toBlurRadius = mDreamBlurRadius.toFloat(),
durationMs = mDreamOutBlurDurationMs,
interpolator = Interpolators.EMPHASIZED_ACCELERATE
),
translationYAnimator(
from = 0f,
to = mDreamOutTranslationYDistance.toFloat(),
durationMs = mDreamOutTranslationYDurationMs,
delayMs = mDreamOutTranslationYDelayBottomMs,
positions = POSITION_BOTTOM,
interpolator = Interpolators.EMPHASIZED_ACCELERATE
),
translationYAnimator(
from = 0f,
to = mDreamOutTranslationYDistance.toFloat(),
durationMs = mDreamOutTranslationYDurationMs,
delayMs = mDreamOutTranslationYDelayTopMs,
positions = POSITION_TOP,
interpolator = Interpolators.EMPHASIZED_ACCELERATE
),
alphaAnimator(
from =
mCurrentAlphaAtPosition.getOrDefault(
key = POSITION_BOTTOM,
defaultValue = 1f
),
to = 0f,
durationMs = mDreamOutAlphaDurationMs,
delayMs = mDreamOutAlphaDelayBottomMs,
positions = POSITION_BOTTOM
),
alphaAnimator(
from =
mCurrentAlphaAtPosition.getOrDefault(
key = POSITION_TOP,
defaultValue = 1f
),
to = 0f,
durationMs = mDreamOutAlphaDurationMs,
delayMs = mDreamOutAlphaDelayTopMs,
positions = POSITION_TOP
)
)
doOnEnd {
mAnimator = null
mOverlayStateController.setExitAnimationsRunning(false)
doneCallback()
}
start()
}
mOverlayStateController.setExitAnimationsRunning(true)
executor.executeDelayed(doneCallback, DREAM_ANIMATION_DURATION.inWholeMilliseconds)
}
/** Cancels the dream content and dream overlay animations, if they're currently running. */
@@ -288,4 +281,15 @@ constructor(
mStatusBarViewController.setTranslationY(translationY)
}
}
private fun loadFromResources(view: View): ConfigurationBasedDimensions {
return ConfigurationBasedDimensions(
translationYPx =
view.resources.getDimensionPixelSize(R.dimen.dream_overlay_exit_y_offset),
)
}
private data class ConfigurationBasedDimensions(
val translationYPx: Int,
)
}

View File

@@ -42,9 +42,9 @@ import com.android.systemui.statusbar.BlurUtils;
import com.android.systemui.statusbar.phone.KeyguardBouncer;
import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
import com.android.systemui.util.ViewController;
import com.android.systemui.util.concurrency.DelayableExecutor;
import java.util.Arrays;
import java.util.concurrent.Executor;
import javax.inject.Inject;
import javax.inject.Named;
@@ -170,6 +170,7 @@ public class DreamOverlayContainerViewController extends ViewController<DreamOve
protected void onInit() {
mStatusBarViewController.init();
mComplicationHostViewController.init();
mDreamOverlayAnimationsController.init(mView);
}
@Override
@@ -184,7 +185,7 @@ public class DreamOverlayContainerViewController extends ViewController<DreamOve
// Start dream entry animations. Skip animations for low light clock.
if (!mStateController.isLowLightActive()) {
mDreamOverlayAnimationsController.startEntryAnimations(mView);
mDreamOverlayAnimationsController.startEntryAnimations();
}
}
@@ -261,10 +262,8 @@ public class DreamOverlayContainerViewController extends ViewController<DreamOve
* @param onAnimationEnd Callback to trigger once animations are finished.
* @param callbackExecutor Executor to execute the callback on.
*/
public void wakeUp(@NonNull Runnable onAnimationEnd, @NonNull Executor callbackExecutor) {
mDreamOverlayAnimationsController.startExitAnimations(mView, () -> {
callbackExecutor.execute(onAnimationEnd);
return null;
});
public void wakeUp(@NonNull Runnable onAnimationEnd,
@NonNull DelayableExecutor callbackExecutor) {
mDreamOverlayAnimationsController.wakeUp(onAnimationEnd, callbackExecutor);
}
}

View File

@@ -43,8 +43,7 @@ import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.dreams.complication.Complication;
import com.android.systemui.dreams.dagger.DreamOverlayComponent;
import com.android.systemui.dreams.touch.DreamOverlayTouchMonitor;
import java.util.concurrent.Executor;
import com.android.systemui.util.concurrency.DelayableExecutor;
import javax.inject.Inject;
import javax.inject.Named;
@@ -61,10 +60,11 @@ public class DreamOverlayService extends android.service.dreams.DreamOverlayServ
// The Context is used to construct the hosting constraint layout and child overlay views.
private final Context mContext;
// The Executor ensures actions and ui updates happen on the same thread.
private final Executor mExecutor;
private final DelayableExecutor mExecutor;
// A controller for the dream overlay container view (which contains both the status bar and the
// content area).
private DreamOverlayContainerViewController mDreamOverlayContainerViewController;
private final DreamCallbackController mDreamCallbackController;
private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
@Nullable
private final ComponentName mLowLightDreamComponent;
@@ -126,14 +126,15 @@ public class DreamOverlayService extends android.service.dreams.DreamOverlayServ
@Inject
public DreamOverlayService(
Context context,
@Main Executor executor,
@Main DelayableExecutor executor,
WindowManager windowManager,
DreamOverlayComponent.Factory dreamOverlayComponentFactory,
DreamOverlayStateController stateController,
KeyguardUpdateMonitor keyguardUpdateMonitor,
UiEventLogger uiEventLogger,
@Nullable @Named(LowLightDreamModule.LOW_LIGHT_DREAM_COMPONENT)
ComponentName lowLightDreamComponent) {
ComponentName lowLightDreamComponent,
DreamCallbackController dreamCallbackController) {
mContext = context;
mExecutor = executor;
mWindowManager = windowManager;
@@ -142,6 +143,7 @@ public class DreamOverlayService extends android.service.dreams.DreamOverlayServ
mKeyguardUpdateMonitor.registerCallback(mKeyguardCallback);
mStateController = stateController;
mUiEventLogger = uiEventLogger;
mDreamCallbackController = dreamCallbackController;
final ViewModelStore viewModelStore = new ViewModelStore();
final Complication.Host host =
@@ -217,6 +219,7 @@ public class DreamOverlayService extends android.service.dreams.DreamOverlayServ
public void onWakeUp(@NonNull Runnable onCompletedCallback) {
mExecutor.execute(() -> {
if (mDreamOverlayContainerViewController != null) {
mDreamCallbackController.onWakeUp();
mDreamOverlayContainerViewController.wakeUp(onCompletedCallback, mExecutor);
}
});

View File

@@ -55,22 +55,6 @@ public abstract class DreamOverlayModule {
"dream_in_complications_translation_y";
public static final String DREAM_IN_TRANSLATION_Y_DURATION =
"dream_in_complications_translation_y_duration";
public static final String DREAM_OUT_TRANSLATION_Y_DISTANCE =
"dream_out_complications_translation_y";
public static final String DREAM_OUT_TRANSLATION_Y_DURATION =
"dream_out_complications_translation_y_duration";
public static final String DREAM_OUT_TRANSLATION_Y_DELAY_BOTTOM =
"dream_out_complications_translation_y_delay_bottom";
public static final String DREAM_OUT_TRANSLATION_Y_DELAY_TOP =
"dream_out_complications_translation_y_delay_top";
public static final String DREAM_OUT_ALPHA_DURATION =
"dream_out_complications_alpha_duration";
public static final String DREAM_OUT_ALPHA_DELAY_BOTTOM =
"dream_out_complications_alpha_delay_bottom";
public static final String DREAM_OUT_ALPHA_DELAY_TOP =
"dream_out_complications_alpha_delay_top";
public static final String DREAM_OUT_BLUR_DURATION =
"dream_out_blur_duration";
/** */
@Provides
@@ -185,66 +169,6 @@ public abstract class DreamOverlayModule {
return (long) resources.getInteger(R.integer.config_dreamOverlayInTranslationYDurationMs);
}
/**
* Provides the number of pixels to translate complications when waking up from dream.
*/
@Provides
@Named(DREAM_OUT_TRANSLATION_Y_DISTANCE)
@DreamOverlayComponent.DreamOverlayScope
static int providesDreamOutComplicationsTranslationY(@Main Resources resources) {
return resources.getDimensionPixelSize(R.dimen.dream_overlay_exit_y_offset);
}
@Provides
@Named(DREAM_OUT_TRANSLATION_Y_DURATION)
@DreamOverlayComponent.DreamOverlayScope
static long providesDreamOutComplicationsTranslationYDuration(@Main Resources resources) {
return (long) resources.getInteger(R.integer.config_dreamOverlayOutTranslationYDurationMs);
}
@Provides
@Named(DREAM_OUT_TRANSLATION_Y_DELAY_BOTTOM)
@DreamOverlayComponent.DreamOverlayScope
static long providesDreamOutComplicationsTranslationYDelayBottom(@Main Resources resources) {
return (long) resources.getInteger(
R.integer.config_dreamOverlayOutTranslationYDelayBottomMs);
}
@Provides
@Named(DREAM_OUT_TRANSLATION_Y_DELAY_TOP)
@DreamOverlayComponent.DreamOverlayScope
static long providesDreamOutComplicationsTranslationYDelayTop(@Main Resources resources) {
return (long) resources.getInteger(R.integer.config_dreamOverlayOutTranslationYDelayTopMs);
}
@Provides
@Named(DREAM_OUT_ALPHA_DURATION)
@DreamOverlayComponent.DreamOverlayScope
static long providesDreamOutComplicationsAlphaDuration(@Main Resources resources) {
return (long) resources.getInteger(R.integer.config_dreamOverlayOutAlphaDurationMs);
}
@Provides
@Named(DREAM_OUT_ALPHA_DELAY_BOTTOM)
@DreamOverlayComponent.DreamOverlayScope
static long providesDreamOutComplicationsAlphaDelayBottom(@Main Resources resources) {
return (long) resources.getInteger(R.integer.config_dreamOverlayOutAlphaDelayBottomMs);
}
@Provides
@Named(DREAM_OUT_ALPHA_DELAY_TOP)
@DreamOverlayComponent.DreamOverlayScope
static long providesDreamOutComplicationsAlphaDelayTop(@Main Resources resources) {
return (long) resources.getInteger(R.integer.config_dreamOverlayOutAlphaDelayTopMs);
}
@Provides
@Named(DREAM_OUT_BLUR_DURATION)
@DreamOverlayComponent.DreamOverlayScope
static long providesDreamOutBlurDuration(@Main Resources resources) {
return (long) resources.getInteger(R.integer.config_dreamOverlayOutBlurDurationMs);
}
@Provides
@DreamOverlayComponent.DreamOverlayScope
static LifecycleOwner providesLifecycleOwner(Lazy<LifecycleRegistry> lifecycleRegistryLazy) {

View File

@@ -33,6 +33,7 @@ import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STR
import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_TIMEOUT;
import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_FOR_UNATTENDED_UPDATE;
import static com.android.systemui.DejankUtils.whitelistIpcs;
import static com.android.systemui.keyguard.domain.interactor.DreamingTransitionInteractor.TO_LOCKSCREEN_DURATION_MS;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
@@ -1231,8 +1232,7 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable,
mDreamOpenAnimationDuration = context.getResources().getInteger(
com.android.internal.R.integer.config_dreamOpenAnimationDuration);
mDreamCloseAnimationDuration = context.getResources().getInteger(
com.android.internal.R.integer.config_dreamCloseAnimationDuration);
mDreamCloseAnimationDuration = (int) TO_LOCKSCREEN_DURATION_MS;
}
public void userActivity() {

View File

@@ -29,6 +29,8 @@ import com.android.systemui.doze.DozeHost
import com.android.systemui.doze.DozeMachine
import com.android.systemui.doze.DozeTransitionCallback
import com.android.systemui.doze.DozeTransitionListener
import com.android.systemui.dreams.DreamCallbackController
import com.android.systemui.dreams.DreamCallbackController.DreamCallback
import com.android.systemui.keyguard.WakefulnessLifecycle
import com.android.systemui.keyguard.shared.model.BiometricUnlockModel
import com.android.systemui.keyguard.shared.model.BiometricUnlockSource
@@ -47,6 +49,7 @@ import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.merge
/** Defines interface for classes that encapsulate application state for the keyguard. */
interface KeyguardRepository {
@@ -176,6 +179,7 @@ constructor(
private val keyguardUpdateMonitor: KeyguardUpdateMonitor,
private val dozeTransitionListener: DozeTransitionListener,
private val authController: AuthController,
private val dreamCallbackController: DreamCallbackController,
) : KeyguardRepository {
private val _animateBottomAreaDozingTransitions = MutableStateFlow(false)
override val animateBottomAreaDozingTransitions =
@@ -276,22 +280,35 @@ constructor(
.distinctUntilChanged()
override val isDreaming: Flow<Boolean> =
conflatedCallbackFlow {
val callback =
object : KeyguardUpdateMonitorCallback() {
override fun onDreamingStateChanged(isDreaming: Boolean) {
trySendWithFailureLogging(isDreaming, TAG, "updated isDreaming")
merge(
conflatedCallbackFlow {
val callback =
object : KeyguardUpdateMonitorCallback() {
override fun onDreamingStateChanged(isDreaming: Boolean) {
trySendWithFailureLogging(isDreaming, TAG, "updated isDreaming")
}
}
}
keyguardUpdateMonitor.registerCallback(callback)
trySendWithFailureLogging(
keyguardUpdateMonitor.isDreaming,
TAG,
"initial isDreaming",
)
keyguardUpdateMonitor.registerCallback(callback)
trySendWithFailureLogging(
keyguardUpdateMonitor.isDreaming,
TAG,
"initial isDreaming",
)
awaitClose { keyguardUpdateMonitor.removeCallback(callback) }
}
awaitClose { keyguardUpdateMonitor.removeCallback(callback) }
},
conflatedCallbackFlow {
val callback =
object : DreamCallback {
override fun onWakeUp() {
trySendWithFailureLogging(false, TAG, "updated isDreaming")
}
}
dreamCallbackController.addCallback(callback)
awaitClose { dreamCallbackController.removeCallback(callback) }
}
)
.distinctUntilChanged()
override val linearDozeAmount: Flow<Float> = conflatedCallbackFlow {

View File

@@ -66,8 +66,8 @@ interface KeyguardTransitionRepository {
}
/**
* Begin a transition from one state to another. Will not start if another transition is in
* progress.
* Begin a transition from one state to another. Transitions are interruptible, and will issue a
* [TransitionStep] with state = [TransitionState.CANCELED] before beginning the next one.
*/
fun startTransition(info: TransitionInfo): UUID?

View File

@@ -28,6 +28,8 @@ import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.keyguard.shared.model.TransitionInfo
import com.android.systemui.util.kotlin.sample
import javax.inject.Inject
import kotlin.time.Duration
import kotlin.time.Duration.Companion.milliseconds
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.flow.combine
@@ -110,7 +112,7 @@ constructor(
name,
KeyguardState.DREAMING,
KeyguardState.LOCKSCREEN,
getAnimator(),
getAnimator(TO_LOCKSCREEN_DURATION),
)
)
}
@@ -167,14 +169,16 @@ constructor(
}
}
private fun getAnimator(): ValueAnimator {
private fun getAnimator(duration: Duration = DEFAULT_DURATION): ValueAnimator {
return ValueAnimator().apply {
setInterpolator(Interpolators.LINEAR)
setDuration(TRANSITION_DURATION_MS)
setDuration(duration.inWholeMilliseconds)
}
}
companion object {
private const val TRANSITION_DURATION_MS = 500L
private val DEFAULT_DURATION = 500.milliseconds
val TO_LOCKSCREEN_DURATION = 1183.milliseconds
@JvmField val TO_LOCKSCREEN_DURATION_MS = TO_LOCKSCREEN_DURATION.inWholeMilliseconds
}
}

View File

@@ -19,12 +19,15 @@ 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.DREAMING
import com.android.systemui.keyguard.shared.model.KeyguardState.LOCKSCREEN
import com.android.systemui.keyguard.shared.model.TransitionState
import com.android.systemui.keyguard.shared.model.TransitionStep
import javax.inject.Inject
import kotlin.time.Duration
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.map
@@ -43,6 +46,10 @@ constructor(
/** LOCKSCREEN->AOD transition information. */
val lockscreenToAodTransition: Flow<TransitionStep> = repository.transition(LOCKSCREEN, AOD)
/** DREAMING->LOCKSCREEN transition information. */
val dreamingToLockscreenTransition: Flow<TransitionStep> =
repository.transition(DREAMING, LOCKSCREEN)
/** (any)->AOD transition information */
val anyStateToAodTransition: Flow<TransitionStep> =
repository.transitions.filter { step -> step.to == KeyguardState.AOD }
@@ -72,4 +79,21 @@ 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()
return flow
.map { step -> (step.value - start) * chunks }
.filter { value -> value >= 0f && value <= 1f }
}
}

View File

@@ -0,0 +1,25 @@
/*
* 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,79 @@
/*
* 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.ui.viewmodel
import com.android.systemui.animation.Interpolators.EMPHASIZED_ACCELERATE
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.keyguard.domain.interactor.DreamingTransitionInteractor.Companion.TO_LOCKSCREEN_DURATION
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
import com.android.systemui.keyguard.shared.model.AnimationParams
import javax.inject.Inject
import kotlin.time.Duration.Companion.milliseconds
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.map
/**
* Breaks down DREAMING->LOCKSCREEN transition into discrete steps for corresponding views to
* consume.
*/
@SysUISingleton
class DreamingToLockscreenTransitionViewModel
@Inject
constructor(
private val interactor: KeyguardTransitionInteractor,
) {
/** 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
}
}
/** Dream overlay views alpha - fade out */
val dreamOverlayAlpha: Flow<Float> = flowForAnimation(DREAM_OVERLAY_ALPHA).map { 1f - it }
/** Dream background alpha - fade out */
val dreamBackgroundAlpha: Flow<Float> = flowForAnimation(DREAM_BACKGROUND_ALPHA).map { 1f - it }
/** Lockscreen views y-translation */
fun lockscreenTranslationY(translatePx: Int): Flow<Float> {
return flowForAnimation(LOCKSCREEN_TRANSLATION_Y).map { it * translatePx }
}
/** 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
)
}
companion object {
/* Length of time before ending the dream activity, in order to start unoccluding */
val DREAM_ANIMATION_DURATION = 250.milliseconds
val DREAM_OVERLAY_TRANSLATION_Y = AnimationParams(duration = 500.milliseconds)
val DREAM_OVERLAY_ALPHA = AnimationParams(duration = 250.milliseconds)
val DREAM_BACKGROUND_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

@@ -0,0 +1,63 @@
/*
* 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.dreams
import android.testing.AndroidTestingRunner
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mock
import org.mockito.Mockito.never
import org.mockito.Mockito.reset
import org.mockito.Mockito.times
import org.mockito.Mockito.verify
import org.mockito.MockitoAnnotations
@SmallTest
@RunWith(AndroidTestingRunner::class)
class DreamCallbackControllerTest : SysuiTestCase() {
@Mock private lateinit var callback: DreamCallbackController.DreamCallback
private lateinit var underTest: DreamCallbackController
@Before
fun setUp() {
MockitoAnnotations.initMocks(this)
underTest = DreamCallbackController()
}
@Test
fun testOnWakeUpInvokesCallback() {
underTest.addCallback(callback)
underTest.onWakeUp()
verify(callback).onWakeUp()
// Adding twice should not invoke twice
reset(callback)
underTest.addCallback(callback)
underTest.onWakeUp()
verify(callback, times(1)).onWakeUp()
// After remove, no call to callback
reset(callback)
underTest.removeCallback(callback)
underTest.onWakeUp()
verify(callback, never()).onWakeUp()
}
}

View File

@@ -1,18 +1,25 @@
package com.android.systemui.dreams
import android.animation.Animator
import android.animation.AnimatorSet
import android.testing.AndroidTestingRunner
import android.view.View
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.dreams.complication.ComplicationHostViewController
import com.android.systemui.keyguard.ui.viewmodel.DreamingToLockscreenTransitionViewModel
import com.android.systemui.statusbar.BlurUtils
import com.android.systemui.util.mockito.argumentCaptor
import com.android.systemui.statusbar.policy.ConfigurationController
import com.android.systemui.util.concurrency.DelayableExecutor
import com.android.systemui.util.mockito.mock
import com.android.systemui.util.mockito.whenever
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.runBlocking
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mock
import org.mockito.Mockito.anyLong
import org.mockito.Mockito.eq
import org.mockito.Mockito.never
import org.mockito.Mockito.times
import org.mockito.Mockito.verify
@@ -28,14 +35,6 @@ class DreamOverlayAnimationsControllerTest : SysuiTestCase() {
private const val DREAM_IN_COMPLICATIONS_ANIMATION_DURATION = 3L
private const val DREAM_IN_TRANSLATION_Y_DISTANCE = 6
private const val DREAM_IN_TRANSLATION_Y_DURATION = 7L
private const val DREAM_OUT_TRANSLATION_Y_DISTANCE = 6
private const val DREAM_OUT_TRANSLATION_Y_DURATION = 7L
private const val DREAM_OUT_TRANSLATION_Y_DELAY_BOTTOM = 8L
private const val DREAM_OUT_TRANSLATION_Y_DELAY_TOP = 9L
private const val DREAM_OUT_ALPHA_DURATION = 10L
private const val DREAM_OUT_ALPHA_DELAY_BOTTOM = 11L
private const val DREAM_OUT_ALPHA_DELAY_TOP = 12L
private const val DREAM_OUT_BLUR_DURATION = 13L
}
@Mock private lateinit var mockAnimator: AnimatorSet
@@ -43,6 +42,8 @@ class DreamOverlayAnimationsControllerTest : SysuiTestCase() {
@Mock private lateinit var hostViewController: ComplicationHostViewController
@Mock private lateinit var statusBarViewController: DreamOverlayStatusBarViewController
@Mock private lateinit var stateController: DreamOverlayStateController
@Mock private lateinit var configController: ConfigurationController
@Mock private lateinit var transitionViewModel: DreamingToLockscreenTransitionViewModel
private lateinit var controller: DreamOverlayAnimationsController
@Before
@@ -55,71 +56,48 @@ class DreamOverlayAnimationsControllerTest : SysuiTestCase() {
statusBarViewController,
stateController,
DREAM_BLUR_RADIUS,
transitionViewModel,
configController,
DREAM_IN_BLUR_ANIMATION_DURATION,
DREAM_IN_COMPLICATIONS_ANIMATION_DURATION,
DREAM_IN_TRANSLATION_Y_DISTANCE,
DREAM_IN_TRANSLATION_Y_DURATION,
DREAM_OUT_TRANSLATION_Y_DISTANCE,
DREAM_OUT_TRANSLATION_Y_DURATION,
DREAM_OUT_TRANSLATION_Y_DELAY_BOTTOM,
DREAM_OUT_TRANSLATION_Y_DELAY_TOP,
DREAM_OUT_ALPHA_DURATION,
DREAM_OUT_ALPHA_DELAY_BOTTOM,
DREAM_OUT_ALPHA_DELAY_TOP,
DREAM_OUT_BLUR_DURATION
)
val mockView: View = mock()
whenever(mockView.resources).thenReturn(mContext.resources)
runBlocking(Dispatchers.Main.immediate) { controller.init(mockView) }
}
@Test
fun testExitAnimationOnEnd() {
val mockCallback: () -> Unit = mock()
fun testWakeUpCallsExecutor() {
val mockExecutor: DelayableExecutor = mock()
val mockCallback: Runnable = mock()
controller.startExitAnimations(
view = mock(),
controller.wakeUp(
doneCallback = mockCallback,
animatorBuilder = { mockAnimator }
executor = mockExecutor,
)
val captor = argumentCaptor<Animator.AnimatorListener>()
verify(mockAnimator).addListener(captor.capture())
val listener = captor.value
verify(mockCallback, never()).invoke()
listener.onAnimationEnd(mockAnimator)
verify(mockCallback, times(1)).invoke()
verify(mockExecutor).executeDelayed(eq(mockCallback), anyLong())
}
@Test
fun testCancellation() {
controller.startExitAnimations(
view = mock(),
doneCallback = mock(),
animatorBuilder = { mockAnimator }
)
verify(mockAnimator, never()).cancel()
controller.cancelAnimations()
verify(mockAnimator, times(1)).cancel()
}
@Test
fun testExitAfterStartWillCancel() {
fun testWakeUpAfterStartWillCancel() {
val mockStartAnimator: AnimatorSet = mock()
val mockExitAnimator: AnimatorSet = mock()
controller.startEntryAnimations(view = mock(), animatorBuilder = { mockStartAnimator })
controller.startEntryAnimations(animatorBuilder = { mockStartAnimator })
verify(mockStartAnimator, never()).cancel()
controller.startExitAnimations(
view = mock(),
controller.wakeUp(
doneCallback = mock(),
animatorBuilder = { mockExitAnimator }
executor = mock(),
)
// Verify that we cancelled the start animator in favor of the exit
// animator.
verify(mockStartAnimator, times(1)).cancel()
verify(mockExitAnimator, never()).cancel()
}
}

View File

@@ -203,7 +203,7 @@ public class DreamOverlayContainerViewControllerTest extends SysuiTestCase {
mController.onViewAttached();
verify(mAnimationsController).startEntryAnimations(mDreamOverlayContainerView);
verify(mAnimationsController).startEntryAnimations();
verify(mAnimationsController, never()).cancelAnimations();
}
@@ -213,7 +213,7 @@ public class DreamOverlayContainerViewControllerTest extends SysuiTestCase {
mController.onViewAttached();
verify(mAnimationsController, never()).startEntryAnimations(mDreamOverlayContainerView);
verify(mAnimationsController, never()).startEntryAnimations();
}
@Test

View File

@@ -113,6 +113,9 @@ public class DreamOverlayServiceTest extends SysuiTestCase {
@Mock
UiEventLogger mUiEventLogger;
@Mock
DreamCallbackController mDreamCallbackController;
@Captor
ArgumentCaptor<View> mViewCaptor;
@@ -141,7 +144,8 @@ public class DreamOverlayServiceTest extends SysuiTestCase {
mStateController,
mKeyguardUpdateMonitor,
mUiEventLogger,
LOW_LIGHT_COMPONENT);
LOW_LIGHT_COMPONENT,
mDreamCallbackController);
}
@Test
@@ -353,6 +357,7 @@ public class DreamOverlayServiceTest extends SysuiTestCase {
mService.onWakeUp(callback);
mMainExecutor.runAllReady();
verify(mDreamOverlayContainerViewController).wakeUp(callback, mMainExecutor);
verify(mDreamCallbackController).onWakeUp();
}
@Test

View File

@@ -28,6 +28,8 @@ import com.android.systemui.doze.DozeHost
import com.android.systemui.doze.DozeMachine
import com.android.systemui.doze.DozeTransitionCallback
import com.android.systemui.doze.DozeTransitionListener
import com.android.systemui.dreams.DreamCallbackController
import com.android.systemui.dreams.DreamCallbackController.DreamCallback
import com.android.systemui.keyguard.WakefulnessLifecycle
import com.android.systemui.keyguard.shared.model.BiometricUnlockModel
import com.android.systemui.keyguard.shared.model.BiometricUnlockSource
@@ -66,6 +68,7 @@ class KeyguardRepositoryImplTest : SysuiTestCase() {
@Mock private lateinit var dozeTransitionListener: DozeTransitionListener
@Mock private lateinit var authController: AuthController
@Mock private lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor
@Mock private lateinit var dreamCallbackController: DreamCallbackController
private lateinit var underTest: KeyguardRepositoryImpl
@@ -83,6 +86,7 @@ class KeyguardRepositoryImplTest : SysuiTestCase() {
keyguardUpdateMonitor,
dozeTransitionListener,
authController,
dreamCallbackController,
)
}
@@ -318,7 +322,7 @@ class KeyguardRepositoryImplTest : SysuiTestCase() {
}
@Test
fun isDreaming() =
fun isDreamingFromKeyguardUpdateMonitor() =
runTest(UnconfinedTestDispatcher()) {
whenever(keyguardUpdateMonitor.isDreaming()).thenReturn(false)
var latest: Boolean? = null
@@ -338,6 +342,26 @@ class KeyguardRepositoryImplTest : SysuiTestCase() {
job.cancel()
}
@Test
fun isDreamingFromDreamCallbackController() =
runTest(UnconfinedTestDispatcher()) {
whenever(keyguardUpdateMonitor.isDreaming()).thenReturn(true)
var latest: Boolean? = null
val job = underTest.isDreaming.onEach { latest = it }.launchIn(this)
assertThat(latest).isTrue()
val listener =
withArgCaptor<DreamCallbackController.DreamCallback> {
verify(dreamCallbackController).addCallback(capture())
}
listener.onWakeUp()
assertThat(latest).isFalse()
job.cancel()
}
@Test
fun biometricUnlockState() =
runTest(UnconfinedTestDispatcher()) {

View File

@@ -0,0 +1,127 @@
/*
* 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.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.DreamingTransitionInteractor.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.google.common.truth.Truth.assertThat
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.test.UnconfinedTestDispatcher
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 DreamingToLockscreenTransitionViewModelTest : SysuiTestCase() {
private lateinit var underTest: DreamingToLockscreenTransitionViewModel
private lateinit var repository: FakeKeyguardTransitionRepository
@Before
fun setUp() {
repository = FakeKeyguardTransitionRepository()
val interactor = KeyguardTransitionInteractor(repository)
underTest = DreamingToLockscreenTransitionViewModel(interactor)
}
@Test
fun dreamOverlayTranslationY() =
runTest(UnconfinedTestDispatcher()) {
val values = mutableListOf<Float>()
val pixels = 100
val job =
underTest.dreamOverlayTranslationY(pixels).onEach { values.add(it) }.launchIn(this)
repository.sendTransitionStep(step(0f))
repository.sendTransitionStep(step(0.3f))
repository.sendTransitionStep(step(0.5f))
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(
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
)
job.cancel()
}
@Test
fun dreamOverlayFadeOut() =
runTest(UnconfinedTestDispatcher()) {
val values = mutableListOf<Float>()
val job = underTest.dreamOverlayAlpha.onEach { values.add(it) }.launchIn(this)
repository.sendTransitionStep(step(0f))
repository.sendTransitionStep(step(0.1f))
repository.sendTransitionStep(step(0.5f))
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))
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 {
return TransitionStep(
from = KeyguardState.DREAMING,
to = KeyguardState.LOCKSCREEN,
value = value,
transitionState = TransitionState.RUNNING,
ownerName = "DreamingToLockscreenTransitionViewModelTest"
)
}
}