diff options
19 files changed, 565 insertions, 265 deletions
diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml index 2d756ae25b3c..f0cc42e54131 100644 --- a/packages/SystemUI/res/values/config.xml +++ b/packages/SystemUI/res/values/config.xml @@ -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> diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamCallbackController.kt b/packages/SystemUI/src/com/android/systemui/dreams/DreamCallbackController.kt new file mode 100644 index 000000000000..ab4632b08fa1 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamCallbackController.kt @@ -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() + } +} diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayAnimationsController.kt b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayAnimationsController.kt index 9b8ef71882e9..abe9355d3375 100644 --- a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayAnimationsController.kt +++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayAnimationsController.kt @@ -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, + ) } diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayContainerViewController.java b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayContainerViewController.java index 9d7ad305c7e5..3106173d81b2 100644 --- a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayContainerViewController.java +++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayContainerViewController.java @@ -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); } } diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java index e76d5b30d3e6..1be9cd198604 100644 --- a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java +++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java @@ -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); } }); diff --git a/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamOverlayModule.java b/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamOverlayModule.java index 4f1ac1a8abd5..0d58a69d498c 100644 --- a/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamOverlayModule.java +++ b/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamOverlayModule.java @@ -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) { diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java index d231870997f3..ae714fbaa58b 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java +++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java @@ -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() { diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt index 148792ba779b..9a0fbbf60f60 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt @@ -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 { diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepository.kt index 5bb586e489e5..d72d7183b0f0 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepository.kt @@ -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? diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/DreamingTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/DreamingTransitionInteractor.kt index b73ce9e5689c..4d60579a75ac 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/DreamingTransitionInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/DreamingTransitionInteractor.kt @@ -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 } } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt index 54a4f493d21d..3b9d6f59a8e7 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt @@ -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 } + } } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/AnimationParams.kt b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/AnimationParams.kt new file mode 100644 index 000000000000..67733e905268 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/AnimationParams.kt @@ -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, +) diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToLockscreenTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToLockscreenTransitionViewModel.kt new file mode 100644 index 000000000000..9b36e8c6a8af --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToLockscreenTransitionViewModel.kt @@ -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) + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamCallbackControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamCallbackControllerTest.kt new file mode 100644 index 000000000000..003efbfdc4d7 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamCallbackControllerTest.kt @@ -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() + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayAnimationsControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayAnimationsControllerTest.kt index 8e689cf8f17e..6c23254941a8 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayAnimationsControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayAnimationsControllerTest.kt @@ -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 ) - } - - @Test - fun testExitAnimationOnEnd() { - val mockCallback: () -> Unit = mock() - controller.startExitAnimations( - view = mock(), - doneCallback = mockCallback, - animatorBuilder = { mockAnimator } - ) + val mockView: View = mock() + whenever(mockView.resources).thenReturn(mContext.resources) - 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() + runBlocking(Dispatchers.Main.immediate) { controller.init(mockView) } } @Test - fun testCancellation() { - controller.startExitAnimations( - view = mock(), - doneCallback = mock(), - animatorBuilder = { mockAnimator } + fun testWakeUpCallsExecutor() { + val mockExecutor: DelayableExecutor = mock() + val mockCallback: Runnable = mock() + + controller.wakeUp( + doneCallback = mockCallback, + executor = mockExecutor, ) - verify(mockAnimator, never()).cancel() - controller.cancelAnimations() - verify(mockAnimator, times(1)).cancel() + verify(mockExecutor).executeDelayed(eq(mockCallback), anyLong()) } @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() } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayContainerViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayContainerViewControllerTest.java index 73c226d11bc4..2799a25316d0 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayContainerViewControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayContainerViewControllerTest.java @@ -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 diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayServiceTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayServiceTest.java index ffb8342a56a5..d6f8deabc270 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayServiceTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayServiceTest.java @@ -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 diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt index 5deac1924ab7..563d44d3f15f 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt @@ -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 @@ -339,6 +343,26 @@ class KeyguardRepositoryImplTest : SysuiTestCase() { } @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()) { val values = mutableListOf<BiometricUnlockModel>() diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToLockscreenTransitionViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToLockscreenTransitionViewModelTest.kt new file mode 100644 index 000000000000..81950f1c22e5 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToLockscreenTransitionViewModelTest.kt @@ -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" + ) + } +} |