Merge "[DO NOT MERGE] Coordinated DREAMING->LOCKSCREEN animation" into tm-qpr-dev

This commit is contained in:
Matt Pietal
2022-12-20 17:18:41 +00:00
committed by Android (Google) Code Review
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. --> <!-- How long in milliseconds before full burn-in protection is achieved. -->
<integer name="config_dreamOverlayMillisUntilFullJitter">240000</integer> <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="complicationFadeOutMs">500</integer>
<integer name="complicationFadeInMs">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.View
import android.view.animation.Interpolator import android.view.animation.Interpolator
import androidx.core.animation.doOnEnd 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.animation.Interpolators
import com.android.systemui.dreams.complication.ComplicationHostViewController import com.android.systemui.dreams.complication.ComplicationHostViewController
import com.android.systemui.dreams.complication.ComplicationLayoutParams 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_TOP
import com.android.systemui.dreams.complication.ComplicationLayoutParams.Position import com.android.systemui.dreams.complication.ComplicationLayoutParams.Position
import com.android.systemui.dreams.dagger.DreamOverlayModule 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.BlurUtils
import com.android.systemui.statusbar.CrossFadeHelper 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.Inject
import javax.inject.Named 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. */ /** Controller for dream overlay animations. */
class DreamOverlayAnimationsController class DreamOverlayAnimationsController
@@ -43,6 +56,8 @@ constructor(
private val mStatusBarViewController: DreamOverlayStatusBarViewController, private val mStatusBarViewController: DreamOverlayStatusBarViewController,
private val mOverlayStateController: DreamOverlayStateController, private val mOverlayStateController: DreamOverlayStateController,
@Named(DreamOverlayModule.DREAM_BLUR_RADIUS) private val mDreamBlurRadius: Int, @Named(DreamOverlayModule.DREAM_BLUR_RADIUS) private val mDreamBlurRadius: Int,
private val transitionViewModel: DreamingToLockscreenTransitionViewModel,
private val configController: ConfigurationController,
@Named(DreamOverlayModule.DREAM_IN_BLUR_ANIMATION_DURATION) @Named(DreamOverlayModule.DREAM_IN_BLUR_ANIMATION_DURATION)
private val mDreamInBlurAnimDurationMs: Long, private val mDreamInBlurAnimDurationMs: Long,
@Named(DreamOverlayModule.DREAM_IN_COMPLICATIONS_ANIMATION_DURATION) @Named(DreamOverlayModule.DREAM_IN_COMPLICATIONS_ANIMATION_DURATION)
@@ -51,22 +66,10 @@ constructor(
private val mDreamInTranslationYDistance: Int, private val mDreamInTranslationYDistance: Int,
@Named(DreamOverlayModule.DREAM_IN_TRANSLATION_Y_DURATION) @Named(DreamOverlayModule.DREAM_IN_TRANSLATION_Y_DURATION)
private val mDreamInTranslationYDurationMs: Long, 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 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 * 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 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. */ /** Starts the dream content and dream overlay entry animations. */
@JvmOverloads @JvmOverloads
fun startEntryAnimations(view: View, animatorBuilder: () -> AnimatorSet = { AnimatorSet() }) { fun startEntryAnimations(animatorBuilder: () -> AnimatorSet = { AnimatorSet() }) {
cancelAnimations() cancelAnimations()
mAnimator = mAnimator =
@@ -113,73 +170,9 @@ constructor(
} }
/** Starts the dream content and dream overlay exit animations. */ /** Starts the dream content and dream overlay exit animations. */
@JvmOverloads fun wakeUp(doneCallback: Runnable, executor: DelayableExecutor) {
fun startExitAnimations(
view: View,
doneCallback: () -> Unit,
animatorBuilder: () -> AnimatorSet = { AnimatorSet() }
) {
cancelAnimations() cancelAnimations()
executor.executeDelayed(doneCallback, DREAM_ANIMATION_DURATION.inWholeMilliseconds)
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)
} }
/** Cancels the dream content and dream overlay animations, if they're currently running. */ /** Cancels the dream content and dream overlay animations, if they're currently running. */
@@ -288,4 +281,15 @@ constructor(
mStatusBarViewController.setTranslationY(translationY) 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.KeyguardBouncer;
import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager; import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
import com.android.systemui.util.ViewController; import com.android.systemui.util.ViewController;
import com.android.systemui.util.concurrency.DelayableExecutor;
import java.util.Arrays; import java.util.Arrays;
import java.util.concurrent.Executor;
import javax.inject.Inject; import javax.inject.Inject;
import javax.inject.Named; import javax.inject.Named;
@@ -170,6 +170,7 @@ public class DreamOverlayContainerViewController extends ViewController<DreamOve
protected void onInit() { protected void onInit() {
mStatusBarViewController.init(); mStatusBarViewController.init();
mComplicationHostViewController.init(); mComplicationHostViewController.init();
mDreamOverlayAnimationsController.init(mView);
} }
@Override @Override
@@ -184,7 +185,7 @@ public class DreamOverlayContainerViewController extends ViewController<DreamOve
// Start dream entry animations. Skip animations for low light clock. // Start dream entry animations. Skip animations for low light clock.
if (!mStateController.isLowLightActive()) { 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 onAnimationEnd Callback to trigger once animations are finished.
* @param callbackExecutor Executor to execute the callback on. * @param callbackExecutor Executor to execute the callback on.
*/ */
public void wakeUp(@NonNull Runnable onAnimationEnd, @NonNull Executor callbackExecutor) { public void wakeUp(@NonNull Runnable onAnimationEnd,
mDreamOverlayAnimationsController.startExitAnimations(mView, () -> { @NonNull DelayableExecutor callbackExecutor) {
callbackExecutor.execute(onAnimationEnd); mDreamOverlayAnimationsController.wakeUp(onAnimationEnd, callbackExecutor);
return null;
});
} }
} }

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

View File

@@ -55,22 +55,6 @@ public abstract class DreamOverlayModule {
"dream_in_complications_translation_y"; "dream_in_complications_translation_y";
public static final String DREAM_IN_TRANSLATION_Y_DURATION = public static final String DREAM_IN_TRANSLATION_Y_DURATION =
"dream_in_complications_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 @Provides
@@ -185,66 +169,6 @@ public abstract class DreamOverlayModule {
return (long) resources.getInteger(R.integer.config_dreamOverlayInTranslationYDurationMs); 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 @Provides
@DreamOverlayComponent.DreamOverlayScope @DreamOverlayComponent.DreamOverlayScope
static LifecycleOwner providesLifecycleOwner(Lazy<LifecycleRegistry> lifecycleRegistryLazy) { 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_AFTER_TIMEOUT;
import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_FOR_UNATTENDED_UPDATE; 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.DejankUtils.whitelistIpcs;
import static com.android.systemui.keyguard.domain.interactor.DreamingTransitionInteractor.TO_LOCKSCREEN_DURATION_MS;
import android.animation.Animator; import android.animation.Animator;
import android.animation.AnimatorListenerAdapter; import android.animation.AnimatorListenerAdapter;
@@ -1231,8 +1232,7 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable,
mDreamOpenAnimationDuration = context.getResources().getInteger( mDreamOpenAnimationDuration = context.getResources().getInteger(
com.android.internal.R.integer.config_dreamOpenAnimationDuration); com.android.internal.R.integer.config_dreamOpenAnimationDuration);
mDreamCloseAnimationDuration = context.getResources().getInteger( mDreamCloseAnimationDuration = (int) TO_LOCKSCREEN_DURATION_MS;
com.android.internal.R.integer.config_dreamCloseAnimationDuration);
} }
public void userActivity() { 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.DozeMachine
import com.android.systemui.doze.DozeTransitionCallback import com.android.systemui.doze.DozeTransitionCallback
import com.android.systemui.doze.DozeTransitionListener 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.WakefulnessLifecycle
import com.android.systemui.keyguard.shared.model.BiometricUnlockModel import com.android.systemui.keyguard.shared.model.BiometricUnlockModel
import com.android.systemui.keyguard.shared.model.BiometricUnlockSource 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.StateFlow
import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.merge
/** Defines interface for classes that encapsulate application state for the keyguard. */ /** Defines interface for classes that encapsulate application state for the keyguard. */
interface KeyguardRepository { interface KeyguardRepository {
@@ -176,6 +179,7 @@ constructor(
private val keyguardUpdateMonitor: KeyguardUpdateMonitor, private val keyguardUpdateMonitor: KeyguardUpdateMonitor,
private val dozeTransitionListener: DozeTransitionListener, private val dozeTransitionListener: DozeTransitionListener,
private val authController: AuthController, private val authController: AuthController,
private val dreamCallbackController: DreamCallbackController,
) : KeyguardRepository { ) : KeyguardRepository {
private val _animateBottomAreaDozingTransitions = MutableStateFlow(false) private val _animateBottomAreaDozingTransitions = MutableStateFlow(false)
override val animateBottomAreaDozingTransitions = override val animateBottomAreaDozingTransitions =
@@ -276,22 +280,35 @@ constructor(
.distinctUntilChanged() .distinctUntilChanged()
override val isDreaming: Flow<Boolean> = override val isDreaming: Flow<Boolean> =
conflatedCallbackFlow { merge(
val callback = conflatedCallbackFlow {
object : KeyguardUpdateMonitorCallback() { val callback =
override fun onDreamingStateChanged(isDreaming: Boolean) { object : KeyguardUpdateMonitorCallback() {
trySendWithFailureLogging(isDreaming, TAG, "updated isDreaming") override fun onDreamingStateChanged(isDreaming: Boolean) {
trySendWithFailureLogging(isDreaming, TAG, "updated isDreaming")
}
} }
} keyguardUpdateMonitor.registerCallback(callback)
keyguardUpdateMonitor.registerCallback(callback) trySendWithFailureLogging(
trySendWithFailureLogging( keyguardUpdateMonitor.isDreaming,
keyguardUpdateMonitor.isDreaming, TAG,
TAG, "initial isDreaming",
"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() .distinctUntilChanged()
override val linearDozeAmount: Flow<Float> = conflatedCallbackFlow { 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 * Begin a transition from one state to another. Transitions are interruptible, and will issue a
* progress. * [TransitionStep] with state = [TransitionState.CANCELED] before beginning the next one.
*/ */
fun startTransition(info: TransitionInfo): UUID? 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.keyguard.shared.model.TransitionInfo
import com.android.systemui.util.kotlin.sample import com.android.systemui.util.kotlin.sample
import javax.inject.Inject import javax.inject.Inject
import kotlin.time.Duration
import kotlin.time.Duration.Companion.milliseconds
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.collect import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.combine
@@ -110,7 +112,7 @@ constructor(
name, name,
KeyguardState.DREAMING, KeyguardState.DREAMING,
KeyguardState.LOCKSCREEN, 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 { return ValueAnimator().apply {
setInterpolator(Interpolators.LINEAR) setInterpolator(Interpolators.LINEAR)
setDuration(TRANSITION_DURATION_MS) setDuration(duration.inWholeMilliseconds)
} }
} }
companion object { 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.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.DREAMING
import com.android.systemui.keyguard.shared.model.KeyguardState.LOCKSCREEN import com.android.systemui.keyguard.shared.model.KeyguardState.LOCKSCREEN
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 javax.inject.Inject import javax.inject.Inject
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
@@ -43,6 +46,10 @@ constructor(
/** LOCKSCREEN->AOD transition information. */ /** LOCKSCREEN->AOD transition information. */
val lockscreenToAodTransition: Flow<TransitionStep> = repository.transition(LOCKSCREEN, AOD) val lockscreenToAodTransition: Flow<TransitionStep> = repository.transition(LOCKSCREEN, AOD)
/** DREAMING->LOCKSCREEN transition information. */
val dreamingToLockscreenTransition: Flow<TransitionStep> =
repository.transition(DREAMING, LOCKSCREEN)
/** (any)->AOD transition information */ /** (any)->AOD transition information */
val anyStateToAodTransition: Flow<TransitionStep> = val anyStateToAodTransition: Flow<TransitionStep> =
repository.transitions.filter { step -> step.to == KeyguardState.AOD } repository.transitions.filter { step -> step.to == KeyguardState.AOD }
@@ -72,4 +79,21 @@ 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()
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 package com.android.systemui.dreams
import android.animation.Animator
import android.animation.AnimatorSet import android.animation.AnimatorSet
import android.testing.AndroidTestingRunner import android.testing.AndroidTestingRunner
import android.view.View
import androidx.test.filters.SmallTest import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase import com.android.systemui.SysuiTestCase
import com.android.systemui.dreams.complication.ComplicationHostViewController 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.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.mock
import com.android.systemui.util.mockito.whenever
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.runBlocking
import org.junit.Before import org.junit.Before
import org.junit.Test import org.junit.Test
import org.junit.runner.RunWith import org.junit.runner.RunWith
import org.mockito.Mock import org.mockito.Mock
import org.mockito.Mockito.anyLong
import org.mockito.Mockito.eq
import org.mockito.Mockito.never import org.mockito.Mockito.never
import org.mockito.Mockito.times import org.mockito.Mockito.times
import org.mockito.Mockito.verify 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_COMPLICATIONS_ANIMATION_DURATION = 3L
private const val DREAM_IN_TRANSLATION_Y_DISTANCE = 6 private const val DREAM_IN_TRANSLATION_Y_DISTANCE = 6
private const val DREAM_IN_TRANSLATION_Y_DURATION = 7L 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 @Mock private lateinit var mockAnimator: AnimatorSet
@@ -43,6 +42,8 @@ class DreamOverlayAnimationsControllerTest : SysuiTestCase() {
@Mock private lateinit var hostViewController: ComplicationHostViewController @Mock private lateinit var hostViewController: ComplicationHostViewController
@Mock private lateinit var statusBarViewController: DreamOverlayStatusBarViewController @Mock private lateinit var statusBarViewController: DreamOverlayStatusBarViewController
@Mock private lateinit var stateController: DreamOverlayStateController @Mock private lateinit var stateController: DreamOverlayStateController
@Mock private lateinit var configController: ConfigurationController
@Mock private lateinit var transitionViewModel: DreamingToLockscreenTransitionViewModel
private lateinit var controller: DreamOverlayAnimationsController private lateinit var controller: DreamOverlayAnimationsController
@Before @Before
@@ -55,71 +56,48 @@ class DreamOverlayAnimationsControllerTest : SysuiTestCase() {
statusBarViewController, statusBarViewController,
stateController, stateController,
DREAM_BLUR_RADIUS, DREAM_BLUR_RADIUS,
transitionViewModel,
configController,
DREAM_IN_BLUR_ANIMATION_DURATION, DREAM_IN_BLUR_ANIMATION_DURATION,
DREAM_IN_COMPLICATIONS_ANIMATION_DURATION, DREAM_IN_COMPLICATIONS_ANIMATION_DURATION,
DREAM_IN_TRANSLATION_Y_DISTANCE, DREAM_IN_TRANSLATION_Y_DISTANCE,
DREAM_IN_TRANSLATION_Y_DURATION, 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 @Test
fun testExitAnimationOnEnd() { fun testWakeUpCallsExecutor() {
val mockCallback: () -> Unit = mock() val mockExecutor: DelayableExecutor = mock()
val mockCallback: Runnable = mock()
controller.startExitAnimations( controller.wakeUp(
view = mock(),
doneCallback = mockCallback, doneCallback = mockCallback,
animatorBuilder = { mockAnimator } executor = mockExecutor,
) )
val captor = argumentCaptor<Animator.AnimatorListener>() verify(mockExecutor).executeDelayed(eq(mockCallback), anyLong())
verify(mockAnimator).addListener(captor.capture())
val listener = captor.value
verify(mockCallback, never()).invoke()
listener.onAnimationEnd(mockAnimator)
verify(mockCallback, times(1)).invoke()
} }
@Test @Test
fun testCancellation() { fun testWakeUpAfterStartWillCancel() {
controller.startExitAnimations(
view = mock(),
doneCallback = mock(),
animatorBuilder = { mockAnimator }
)
verify(mockAnimator, never()).cancel()
controller.cancelAnimations()
verify(mockAnimator, times(1)).cancel()
}
@Test
fun testExitAfterStartWillCancel() {
val mockStartAnimator: AnimatorSet = mock() val mockStartAnimator: AnimatorSet = mock()
val mockExitAnimator: AnimatorSet = mock()
controller.startEntryAnimations(view = mock(), animatorBuilder = { mockStartAnimator }) controller.startEntryAnimations(animatorBuilder = { mockStartAnimator })
verify(mockStartAnimator, never()).cancel() verify(mockStartAnimator, never()).cancel()
controller.startExitAnimations( controller.wakeUp(
view = mock(),
doneCallback = mock(), doneCallback = mock(),
animatorBuilder = { mockExitAnimator } executor = mock(),
) )
// Verify that we cancelled the start animator in favor of the exit // Verify that we cancelled the start animator in favor of the exit
// animator. // animator.
verify(mockStartAnimator, times(1)).cancel() verify(mockStartAnimator, times(1)).cancel()
verify(mockExitAnimator, never()).cancel()
} }
} }

View File

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

View File

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

View File

@@ -28,6 +28,8 @@ import com.android.systemui.doze.DozeHost
import com.android.systemui.doze.DozeMachine import com.android.systemui.doze.DozeMachine
import com.android.systemui.doze.DozeTransitionCallback import com.android.systemui.doze.DozeTransitionCallback
import com.android.systemui.doze.DozeTransitionListener 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.WakefulnessLifecycle
import com.android.systemui.keyguard.shared.model.BiometricUnlockModel import com.android.systemui.keyguard.shared.model.BiometricUnlockModel
import com.android.systemui.keyguard.shared.model.BiometricUnlockSource 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 dozeTransitionListener: DozeTransitionListener
@Mock private lateinit var authController: AuthController @Mock private lateinit var authController: AuthController
@Mock private lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor @Mock private lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor
@Mock private lateinit var dreamCallbackController: DreamCallbackController
private lateinit var underTest: KeyguardRepositoryImpl private lateinit var underTest: KeyguardRepositoryImpl
@@ -83,6 +86,7 @@ class KeyguardRepositoryImplTest : SysuiTestCase() {
keyguardUpdateMonitor, keyguardUpdateMonitor,
dozeTransitionListener, dozeTransitionListener,
authController, authController,
dreamCallbackController,
) )
} }
@@ -318,7 +322,7 @@ class KeyguardRepositoryImplTest : SysuiTestCase() {
} }
@Test @Test
fun isDreaming() = fun isDreamingFromKeyguardUpdateMonitor() =
runTest(UnconfinedTestDispatcher()) { runTest(UnconfinedTestDispatcher()) {
whenever(keyguardUpdateMonitor.isDreaming()).thenReturn(false) whenever(keyguardUpdateMonitor.isDreaming()).thenReturn(false)
var latest: Boolean? = null var latest: Boolean? = null
@@ -338,6 +342,26 @@ class KeyguardRepositoryImplTest : SysuiTestCase() {
job.cancel() 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 @Test
fun biometricUnlockState() = fun biometricUnlockState() =
runTest(UnconfinedTestDispatcher()) { 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"
)
}
}