Merge "[DO NOT MERGE] Coordinated DREAMING->LOCKSCREEN animation" into tm-qpr-dev
This commit is contained in:
@@ -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>
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
@@ -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,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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?
|
||||
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
)
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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()) {
|
||||
|
||||
@@ -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"
|
||||
)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user