From 455996f7b554a587f6bf66879c780a9fa7c0a5a0 Mon Sep 17 00:00:00 2001 From: Matt Pietal Date: Fri, 23 Dec 2022 19:33:15 +0000 Subject: [PATCH] Transitions: LOCKSCREEN->DREAMING Add general support for lockscren->occluded/dreaming transitions, including improved support for camera gesture occlusion. Double-tap power will send various signals, so capture the double-tap as soon as possible to cancel a transition in favor of the occlusion transition. For LOCKSCREEN->DREAMING only, delay call to setOccluded, as it causes jank when done too early. Also absorb touches while this transition is running to prevent strange artifacts from popping in if the display is touched. Test: atest KeyguardRepositoryImplTest KeyguardTransitionRepositoryTest NotificationShadeWindowViewControllerTest LockscreenToDreamingTransitionViewModelTest LockscreenToOccludedTransitionViewModelTest NotificationShadeWindowViewTest NotificationPanelViewControllerTest Bug: 260637747 Change-Id: Ie2593706f2848ca715e557c8d4dfc129d93a29f1 --- packages/SystemUI/res/values/dimens.xml | 6 + .../keyguard/KeyguardViewMediator.java | 29 +- .../keyguard/dagger/KeyguardModule.java | 3 + .../KeyguardTransitionRepository.kt | 11 +- .../FromLockscreenTransitionInteractor.kt | 50 +- .../domain/interactor/KeyguardInteractor.kt | 42 ++ .../KeyguardTransitionInteractor.kt | 22 +- .../shared/model/CameraLaunchSourceModel.kt | 28 + ...LockscreenToDreamingTransitionViewModel.kt | 73 +++ ...LockscreenToOccludedTransitionViewModel.kt | 71 +++ .../NotificationPanelViewController.java | 74 ++- ...NotificationShadeWindowViewController.java | 25 +- .../keyguard/ClockEventControllerTest.kt | 4 +- .../LockIconViewControllerBaseTest.java | 4 +- .../keyguard/CustomizationProviderTest.kt | 4 +- .../keyguard/KeyguardViewMediatorTest.java | 3 + .../KeyguardTransitionRepositoryTest.kt | 7 +- .../interactor/KeyguardInteractorTest.kt | 86 +++ ...ckAffordanceInteractorParameterizedTest.kt | 8 +- .../KeyguardQuickAffordanceInteractorTest.kt | 5 +- .../KeyguardTransitionScenariosTest.kt | 6 +- .../KeyguardBottomAreaViewModelTest.kt | 5 +- ...screenToDreamingTransitionViewModelTest.kt | 135 +++++ ...screenToOccludedTransitionViewModelTest.kt | 140 +++++ .../NotificationPanelViewControllerTest.java | 7 + ...tificationShadeWindowViewControllerTest.kt | 5 +- .../NotificationShadeWindowViewTest.java | 5 +- .../unfold/FoldAodAnimationControllerTest.kt | 6 +- .../domain/interactor/UserInteractorTest.kt | 3 + .../StatusBarUserChipViewModelTest.kt | 3 + .../ui/viewmodel/UserSwitcherViewModelTest.kt | 535 +++++++++--------- 31 files changed, 1097 insertions(+), 308 deletions(-) create mode 100644 packages/SystemUI/src/com/android/systemui/keyguard/shared/model/CameraLaunchSourceModel.kt create mode 100644 packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToDreamingTransitionViewModel.kt create mode 100644 packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToOccludedTransitionViewModel.kt create mode 100644 packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorTest.kt create mode 100644 packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToDreamingTransitionViewModelTest.kt create mode 100644 packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToOccludedTransitionViewModelTest.kt diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml index 3b17bce6c2f13..ae7ab9e199e46 100644 --- a/packages/SystemUI/res/values/dimens.xml +++ b/packages/SystemUI/res/values/dimens.xml @@ -1293,6 +1293,12 @@ 40dp + + -40dp + + + -40dp + 0dp diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java index 3d4347efd17d5..6b121b84680c4 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java +++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java @@ -34,6 +34,7 @@ import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STR 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.ui.viewmodel.DreamingToLockscreenTransitionViewModel.LOCKSCREEN_ANIMATION_DURATION_MS; +import static com.android.systemui.keyguard.ui.viewmodel.LockscreenToDreamingTransitionViewModel.DREAMING_ANIMATION_DURATION_MS; import android.animation.Animator; import android.animation.AnimatorListenerAdapter; @@ -122,6 +123,8 @@ import com.android.systemui.classifier.FalsingCollector; import com.android.systemui.dagger.qualifiers.UiBackground; import com.android.systemui.dreams.DreamOverlayStateController; import com.android.systemui.dump.DumpManager; +import com.android.systemui.flags.FeatureFlags; +import com.android.systemui.flags.Flags; import com.android.systemui.keyguard.dagger.KeyguardModule; import com.android.systemui.navigationbar.NavigationModeController; import com.android.systemui.plugins.statusbar.StatusBarStateController; @@ -507,6 +510,8 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable, private CentralSurfaces mCentralSurfaces; + private boolean mUnocclusionTransitionFlagEnabled = false; + private final DeviceConfig.OnPropertiesChangedListener mOnPropertiesChangedListener = new DeviceConfig.OnPropertiesChangedListener() { @Override @@ -959,8 +964,9 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable, public void onAnimationStart(int transit, RemoteAnimationTarget[] apps, RemoteAnimationTarget[] wallpapers, RemoteAnimationTarget[] nonApps, IRemoteAnimationFinishedCallback finishedCallback) throws RemoteException { - setOccluded(true /* isOccluded */, true /* animate */); - + if (!mUnocclusionTransitionFlagEnabled) { + setOccluded(true /* isOccluded */, true /* animate */); + } if (apps == null || apps.length == 0 || apps[0] == null) { if (DEBUG) { Log.d(TAG, "No apps provided to the OccludeByDream runner; " @@ -1002,9 +1008,20 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable, applier.scheduleApply(paramsBuilder.build()); }); mOccludeByDreamAnimator.addListener(new AnimatorListenerAdapter() { + private boolean mIsCancelled = false; + @Override + public void onAnimationCancel(Animator animation) { + mIsCancelled = true; + } + @Override public void onAnimationEnd(Animator animation) { try { + if (!mIsCancelled && mUnocclusionTransitionFlagEnabled) { + // We're already on the main thread, don't queue this call + handleSetOccluded(true /* isOccluded */, + false /* animate */); + } finishedCallback.onAnimationFinished(); mOccludeByDreamAnimator = null; } catch (RemoteException e) { @@ -1177,6 +1194,7 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable, ScreenOnCoordinator screenOnCoordinator, InteractionJankMonitor interactionJankMonitor, DreamOverlayStateController dreamOverlayStateController, + FeatureFlags featureFlags, Lazy shadeControllerLazy, Lazy notificationShadeWindowControllerLazy, Lazy activityLaunchAnimator, @@ -1231,9 +1249,9 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable, R.dimen.physical_power_button_center_screen_location_y); mWindowCornerRadius = ScreenDecorationsUtils.getWindowCornerRadius(context); - mDreamOpenAnimationDuration = context.getResources().getInteger( - com.android.internal.R.integer.config_dreamOpenAnimationDuration); + mDreamOpenAnimationDuration = (int) DREAMING_ANIMATION_DURATION_MS; mDreamCloseAnimationDuration = (int) LOCKSCREEN_ANIMATION_DURATION_MS; + mUnocclusionTransitionFlagEnabled = featureFlags.isEnabled(Flags.UNOCCLUSION_TRANSITION); } public void userActivity() { @@ -1793,7 +1811,6 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable, Trace.beginSection("KeyguardViewMediator#setOccluded"); if (DEBUG) Log.d(TAG, "setOccluded " + isOccluded); - mInteractionJankMonitor.cancel(CUJ_LOCKSCREEN_TRANSITION_FROM_AOD); mHandler.removeMessages(SET_OCCLUDED); Message msg = mHandler.obtainMessage(SET_OCCLUDED, isOccluded ? 1 : 0, animate ? 1 : 0); mHandler.sendMessage(msg); @@ -1826,6 +1843,8 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable, private void handleSetOccluded(boolean isOccluded, boolean animate) { Trace.beginSection("KeyguardViewMediator#handleSetOccluded"); Log.d(TAG, "handleSetOccluded(" + isOccluded + ")"); + mInteractionJankMonitor.cancel(CUJ_LOCKSCREEN_TRANSITION_FROM_AOD); + synchronized (KeyguardViewMediator.this) { if (mHiding && isOccluded) { // We're in the process of going away but WindowManager wants to show a diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java b/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java index 47ef0fac17ab3..98d3570106ceb 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java +++ b/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java @@ -39,6 +39,7 @@ import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.dagger.qualifiers.UiBackground; import com.android.systemui.dreams.DreamOverlayStateController; import com.android.systemui.dump.DumpManager; +import com.android.systemui.flags.FeatureFlags; import com.android.systemui.keyguard.DismissCallbackRegistry; import com.android.systemui.keyguard.KeyguardUnlockAnimationController; import com.android.systemui.keyguard.KeyguardViewMediator; @@ -112,6 +113,7 @@ public class KeyguardModule { ScreenOnCoordinator screenOnCoordinator, InteractionJankMonitor interactionJankMonitor, DreamOverlayStateController dreamOverlayStateController, + FeatureFlags featureFlags, Lazy shadeController, Lazy notificationShadeWindowController, Lazy activityLaunchAnimator, @@ -142,6 +144,7 @@ public class KeyguardModule { screenOnCoordinator, interactionJankMonitor, dreamOverlayStateController, + featureFlags, shadeController, notificationShadeWindowController, activityLaunchAnimator, 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 343c2dc172fc3..d14b66a68f119 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 @@ -135,11 +135,14 @@ class KeyguardTransitionRepositoryImpl @Inject constructor() : KeyguardTransitio Log.i(TAG, "Duplicate call to start the transition, rejecting: $info") return null } - if (lastStep.transitionState != TransitionState.FINISHED) { - Log.i(TAG, "Transition still active: $lastStep, canceling") - } + val startingValue = + if (lastStep.transitionState != TransitionState.FINISHED) { + Log.i(TAG, "Transition still active: $lastStep, canceling") + lastStep.value + } else { + 0f + } - val startingValue = 1f - lastStep.value lastAnimator?.cancel() lastAnimator = info.animator diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt index 326acc9eb7627..20c6531d580b4 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt @@ -30,6 +30,8 @@ import com.android.systemui.shade.data.repository.ShadeRepository import com.android.systemui.util.kotlin.sample import java.util.UUID 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 @@ -51,6 +53,7 @@ constructor( override fun start() { listenForLockscreenToGone() listenForLockscreenToOccluded() + listenForLockscreenToCamera() listenForLockscreenToAod() listenForLockscreenToBouncer() listenForLockscreenToDreaming() @@ -69,7 +72,7 @@ constructor( name, KeyguardState.LOCKSCREEN, KeyguardState.DREAMING, - getAnimator(), + getAnimator(TO_DREAMING_DURATION), ) ) } @@ -184,17 +187,42 @@ constructor( ), ::toTriple ) - .collect { triple -> - val (isOccluded, keyguardState, isDreaming) = triple - // Occlusion signals come from the framework, and should interrupt any - // existing transition - if (isOccluded && !isDreaming) { + .collect { (isOccluded, keyguardState, isDreaming) -> + if (isOccluded && !isDreaming && keyguardState == KeyguardState.LOCKSCREEN) { keyguardTransitionRepository.startTransition( TransitionInfo( name, keyguardState, KeyguardState.OCCLUDED, - getAnimator(), + getAnimator(TO_OCCLUDED_DURATION), + ) + ) + } + } + } + } + + /** This signal may come in before the occlusion signal, and can provide a custom transition */ + private fun listenForLockscreenToCamera() { + scope.launch { + keyguardInteractor.onCameraLaunchDetected + .sample(keyguardTransitionInteractor.startedKeyguardTransitionStep, ::Pair) + .collect { (_, lastStartedStep) -> + // DREAMING/AOD/OFF may trigger on the first power button push, so include this + // state in order to cancel and correct the transition + if ( + lastStartedStep.to == KeyguardState.LOCKSCREEN || + lastStartedStep.to == KeyguardState.DREAMING || + lastStartedStep.to == KeyguardState.DOZING || + lastStartedStep.to == KeyguardState.AOD || + lastStartedStep.to == KeyguardState.OFF + ) { + keyguardTransitionRepository.startTransition( + TransitionInfo( + name, + KeyguardState.LOCKSCREEN, + KeyguardState.OCCLUDED, + getAnimator(TO_OCCLUDED_DURATION), ) ) } @@ -223,14 +251,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_DREAMING_DURATION = 933.milliseconds + val TO_OCCLUDED_DURATION = 450.milliseconds } } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt index 402c1793f0b28..ac2d230ee605a 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt @@ -17,17 +17,24 @@ package com.android.systemui.keyguard.domain.interactor +import android.app.StatusBarManager import android.graphics.Point +import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging +import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow import com.android.systemui.dagger.SysUISingleton import com.android.systemui.keyguard.data.repository.KeyguardRepository import com.android.systemui.keyguard.shared.model.BiometricUnlockModel +import com.android.systemui.keyguard.shared.model.CameraLaunchSourceModel import com.android.systemui.keyguard.shared.model.DozeStateModel import com.android.systemui.keyguard.shared.model.DozeStateModel.Companion.isDozeOff import com.android.systemui.keyguard.shared.model.DozeTransitionModel import com.android.systemui.keyguard.shared.model.StatusBarState import com.android.systemui.keyguard.shared.model.WakefulnessModel +import com.android.systemui.statusbar.CommandQueue +import com.android.systemui.statusbar.CommandQueue.Callbacks import com.android.systemui.util.kotlin.sample import javax.inject.Inject +import kotlinx.coroutines.channels.awaitClose import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.filter @@ -41,6 +48,7 @@ class KeyguardInteractor @Inject constructor( private val repository: KeyguardRepository, + private val commandQueue: CommandQueue, ) { /** * The amount of doze the system is in, where `1.0` is fully dozing and `0.0` is not dozing at @@ -58,6 +66,23 @@ constructor( val isDreaming: Flow = repository.isDreaming /** Whether the system is dreaming with an overlay active */ val isDreamingWithOverlay: Flow = repository.isDreamingWithOverlay + /** Event for when the camera gesture is detected */ + val onCameraLaunchDetected: Flow = conflatedCallbackFlow { + val callback = + object : CommandQueue.Callbacks { + override fun onCameraLaunchGestureDetected(source: Int) { + trySendWithFailureLogging( + cameraLaunchSourceIntToModel(source), + TAG, + "updated onCameraLaunchGestureDetected" + ) + } + } + + commandQueue.addCallback(callback) + + awaitClose { commandQueue.removeCallback(callback) } + } /** * Dozing and dreaming have overlapping events. If the doze state remains in FINISH, it means @@ -103,4 +128,21 @@ constructor( fun isKeyguardShowing(): Boolean { return repository.isKeyguardShowing() } + + private fun cameraLaunchSourceIntToModel(value: Int): CameraLaunchSourceModel { + return when (value) { + StatusBarManager.CAMERA_LAUNCH_SOURCE_WIGGLE -> CameraLaunchSourceModel.WIGGLE + StatusBarManager.CAMERA_LAUNCH_SOURCE_POWER_DOUBLE_TAP -> + CameraLaunchSourceModel.POWER_DOUBLE_TAP + StatusBarManager.CAMERA_LAUNCH_SOURCE_LIFT_TRIGGER -> + CameraLaunchSourceModel.LIFT_TRIGGER + StatusBarManager.CAMERA_LAUNCH_SOURCE_QUICK_AFFORDANCE -> + CameraLaunchSourceModel.QUICK_AFFORDANCE + else -> throw IllegalArgumentException("Invalid CameraLaunchSourceModel value: $value") + } + } + + companion object { + private const val TAG = "KeyguardInteractor" + } } 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 04024be571c8c..9cdbcda1343df 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 @@ -42,24 +42,32 @@ class KeyguardTransitionInteractor constructor( repository: KeyguardTransitionRepository, ) { + /** (any)->AOD transition information */ + val anyStateToAodTransition: Flow = + repository.transitions.filter { step -> step.to == KeyguardState.AOD } + /** AOD->LOCKSCREEN transition information. */ val aodToLockscreenTransition: Flow = repository.transition(AOD, LOCKSCREEN) - /** LOCKSCREEN->AOD transition information. */ - val lockscreenToAodTransition: Flow = repository.transition(LOCKSCREEN, AOD) - /** DREAMING->LOCKSCREEN transition information. */ val dreamingToLockscreenTransition: Flow = repository.transition(DREAMING, LOCKSCREEN) + /** LOCKSCREEN->AOD transition information. */ + val lockscreenToAodTransition: Flow = repository.transition(LOCKSCREEN, AOD) + + /** LOCKSCREEN->DREAMING transition information. */ + val lockscreenToDreamingTransition: Flow = + repository.transition(LOCKSCREEN, DREAMING) + + /** LOCKSCREEN->OCCLUDED transition information. */ + val lockscreenToOccludedTransition: Flow = + repository.transition(LOCKSCREEN, OCCLUDED) + /** OCCLUDED->LOCKSCREEN transition information. */ val occludedToLockscreenTransition: Flow = repository.transition(OCCLUDED, LOCKSCREEN) - /** (any)->AOD transition information */ - val anyStateToAodTransition: Flow = - repository.transitions.filter { step -> step.to == KeyguardState.AOD } - /** * AOD<->LOCKSCREEN transition information, mapped to dozeAmount range of AOD (1f) <-> * Lockscreen (0f). diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/CameraLaunchSourceModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/CameraLaunchSourceModel.kt new file mode 100644 index 0000000000000..19baf7705546f --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/CameraLaunchSourceModel.kt @@ -0,0 +1,28 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ +package com.android.systemui.keyguard.shared.model + +/** Camera launch sources */ +enum class CameraLaunchSourceModel { + /** Device is wiggled */ + WIGGLE, + /** Power button has been double tapped */ + POWER_DOUBLE_TAP, + /** Device has been lifted */ + LIFT_TRIGGER, + /** Quick affordance button has been pressed */ + QUICK_AFFORDANCE, +} diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToDreamingTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToDreamingTransitionViewModel.kt new file mode 100644 index 0000000000000..d48f87deaaf49 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToDreamingTransitionViewModel.kt @@ -0,0 +1,73 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.keyguard.ui.viewmodel + +import com.android.systemui.animation.Interpolators.EMPHASIZED_ACCELERATE +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.keyguard.domain.interactor.FromLockscreenTransitionInteractor.Companion.TO_DREAMING_DURATION +import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor +import com.android.systemui.keyguard.shared.model.AnimationParams +import com.android.systemui.keyguard.shared.model.TransitionState +import javax.inject.Inject +import kotlin.time.Duration.Companion.milliseconds +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.filter +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.merge + +/** + * Breaks down LOCKSCREEN->DREAMING transition into discrete steps for corresponding views to + * consume. + */ +@SysUISingleton +class LockscreenToDreamingTransitionViewModel +@Inject +constructor( + private val interactor: KeyguardTransitionInteractor, +) { + + /** Lockscreen views y-translation */ + fun lockscreenTranslationY(translatePx: Int): Flow { + return merge( + flowForAnimation(LOCKSCREEN_TRANSLATION_Y).map { value -> + (EMPHASIZED_ACCELERATE.getInterpolation(value) * translatePx) + }, + // On end, reset the translation to 0 + interactor.lockscreenToDreamingTransition + .filter { step -> step.transitionState == TransitionState.FINISHED } + .map { 0f } + ) + } + + /** Lockscreen views alpha */ + val lockscreenAlpha: Flow = flowForAnimation(LOCKSCREEN_ALPHA).map { 1f - it } + + private fun flowForAnimation(params: AnimationParams): Flow { + return interactor.transitionStepAnimation( + interactor.lockscreenToDreamingTransition, + params, + totalDuration = TO_DREAMING_DURATION + ) + } + + companion object { + @JvmField val DREAMING_ANIMATION_DURATION_MS = TO_DREAMING_DURATION.inWholeMilliseconds + + val LOCKSCREEN_TRANSLATION_Y = AnimationParams(duration = 500.milliseconds) + val LOCKSCREEN_ALPHA = AnimationParams(duration = 250.milliseconds) + } +} diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToOccludedTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToOccludedTransitionViewModel.kt new file mode 100644 index 0000000000000..22d292e928565 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToOccludedTransitionViewModel.kt @@ -0,0 +1,71 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.keyguard.ui.viewmodel + +import com.android.systemui.animation.Interpolators.EMPHASIZED_ACCELERATE +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.keyguard.domain.interactor.FromLockscreenTransitionInteractor.Companion.TO_OCCLUDED_DURATION +import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor +import com.android.systemui.keyguard.shared.model.AnimationParams +import com.android.systemui.keyguard.shared.model.TransitionState +import javax.inject.Inject +import kotlin.time.Duration.Companion.milliseconds +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.filter +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.merge + +/** + * Breaks down LOCKSCREEN->OCCLUDED transition into discrete steps for corresponding views to + * consume. + */ +@SysUISingleton +class LockscreenToOccludedTransitionViewModel +@Inject +constructor( + private val interactor: KeyguardTransitionInteractor, +) { + + /** Lockscreen views y-translation */ + fun lockscreenTranslationY(translatePx: Int): Flow { + return merge( + flowForAnimation(LOCKSCREEN_TRANSLATION_Y).map { value -> + (EMPHASIZED_ACCELERATE.getInterpolation(value) * translatePx) + }, + // On end, reset the translation to 0 + interactor.lockscreenToOccludedTransition + .filter { step -> step.transitionState == TransitionState.FINISHED } + .map { 0f } + ) + } + + /** Lockscreen views alpha */ + val lockscreenAlpha: Flow = flowForAnimation(LOCKSCREEN_ALPHA).map { 1f - it } + + private fun flowForAnimation(params: AnimationParams): Flow { + return interactor.transitionStepAnimation( + interactor.lockscreenToOccludedTransition, + params, + totalDuration = TO_OCCLUDED_DURATION + ) + } + + companion object { + val LOCKSCREEN_TRANSLATION_Y = AnimationParams(duration = TO_OCCLUDED_DURATION) + val LOCKSCREEN_ALPHA = AnimationParams(duration = 250.milliseconds) + } +} diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java index d711d15122327..b48fd9846798d 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java +++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java @@ -145,6 +145,8 @@ import com.android.systemui.keyguard.shared.model.TransitionState; import com.android.systemui.keyguard.shared.model.TransitionStep; import com.android.systemui.keyguard.ui.viewmodel.DreamingToLockscreenTransitionViewModel; import com.android.systemui.keyguard.ui.viewmodel.KeyguardBottomAreaViewModel; +import com.android.systemui.keyguard.ui.viewmodel.LockscreenToDreamingTransitionViewModel; +import com.android.systemui.keyguard.ui.viewmodel.LockscreenToOccludedTransitionViewModel; import com.android.systemui.keyguard.ui.viewmodel.OccludedToLockscreenTransitionViewModel; import com.android.systemui.media.controls.pipeline.MediaDataManager; import com.android.systemui.media.controls.ui.KeyguardMediaController; @@ -689,12 +691,16 @@ public final class NotificationPanelViewController implements Dumpable { private boolean mExpandLatencyTracking; private DreamingToLockscreenTransitionViewModel mDreamingToLockscreenTransitionViewModel; private OccludedToLockscreenTransitionViewModel mOccludedToLockscreenTransitionViewModel; + private LockscreenToDreamingTransitionViewModel mLockscreenToDreamingTransitionViewModel; + private LockscreenToOccludedTransitionViewModel mLockscreenToOccludedTransitionViewModel; private KeyguardTransitionInteractor mKeyguardTransitionInteractor; private CoroutineDispatcher mMainDispatcher; - private boolean mIsToLockscreenTransitionRunning = false; + private boolean mIsOcclusionTransitionRunning = false; private int mDreamingToLockscreenTransitionTranslationY; private int mOccludedToLockscreenTransitionTranslationY; + private int mLockscreenToDreamingTransitionTranslationY; + private int mLockscreenToOccludedTransitionTranslationY; private boolean mUnocclusionTransitionFlagEnabled = false; private final Runnable mFlingCollapseRunnable = () -> fling(0, false /* expand */, @@ -713,13 +719,25 @@ public final class NotificationPanelViewController implements Dumpable { private final Consumer mDreamingToLockscreenTransition = (TransitionStep step) -> { - mIsToLockscreenTransitionRunning = + mIsOcclusionTransitionRunning = step.getTransitionState() == TransitionState.RUNNING; }; private final Consumer mOccludedToLockscreenTransition = (TransitionStep step) -> { - mIsToLockscreenTransitionRunning = + mIsOcclusionTransitionRunning = + step.getTransitionState() == TransitionState.RUNNING; + }; + + private final Consumer mLockscreenToDreamingTransition = + (TransitionStep step) -> { + mIsOcclusionTransitionRunning = + step.getTransitionState() == TransitionState.RUNNING; + }; + + private final Consumer mLockscreenToOccludedTransition = + (TransitionStep step) -> { + mIsOcclusionTransitionRunning = step.getTransitionState() == TransitionState.RUNNING; }; @@ -794,6 +812,8 @@ public final class NotificationPanelViewController implements Dumpable { AlternateBouncerInteractor alternateBouncerInteractor, DreamingToLockscreenTransitionViewModel dreamingToLockscreenTransitionViewModel, OccludedToLockscreenTransitionViewModel occludedToLockscreenTransitionViewModel, + LockscreenToDreamingTransitionViewModel lockscreenToDreamingTransitionViewModel, + LockscreenToOccludedTransitionViewModel lockscreenToOccludedTransitionViewModel, @Main CoroutineDispatcher mainDispatcher, KeyguardTransitionInteractor keyguardTransitionInteractor, DumpManager dumpManager) { @@ -813,6 +833,8 @@ public final class NotificationPanelViewController implements Dumpable { mGutsManager = gutsManager; mDreamingToLockscreenTransitionViewModel = dreamingToLockscreenTransitionViewModel; mOccludedToLockscreenTransitionViewModel = occludedToLockscreenTransitionViewModel; + mLockscreenToDreamingTransitionViewModel = lockscreenToDreamingTransitionViewModel; + mLockscreenToOccludedTransitionViewModel = lockscreenToOccludedTransitionViewModel; mKeyguardTransitionInteractor = keyguardTransitionInteractor; mView.addOnAttachStateChangeListener(new View.OnAttachStateChangeListener() { @Override @@ -1121,22 +1143,44 @@ public final class NotificationPanelViewController implements Dumpable { collectFlow(mView, mKeyguardTransitionInteractor.getDreamingToLockscreenTransition(), mDreamingToLockscreenTransition, mMainDispatcher); collectFlow(mView, mDreamingToLockscreenTransitionViewModel.getLockscreenAlpha(), - toLockscreenTransitionAlpha(mNotificationStackScrollLayoutController), + setTransitionAlpha(mNotificationStackScrollLayoutController), mMainDispatcher); collectFlow(mView, mDreamingToLockscreenTransitionViewModel.lockscreenTranslationY( mDreamingToLockscreenTransitionTranslationY), - toLockscreenTransitionY(mNotificationStackScrollLayoutController), + setTransitionY(mNotificationStackScrollLayoutController), mMainDispatcher); // Occluded->Lockscreen collectFlow(mView, mKeyguardTransitionInteractor.getOccludedToLockscreenTransition(), mOccludedToLockscreenTransition, mMainDispatcher); collectFlow(mView, mOccludedToLockscreenTransitionViewModel.getLockscreenAlpha(), - toLockscreenTransitionAlpha(mNotificationStackScrollLayoutController), + setTransitionAlpha(mNotificationStackScrollLayoutController), mMainDispatcher); collectFlow(mView, mOccludedToLockscreenTransitionViewModel.lockscreenTranslationY( mOccludedToLockscreenTransitionTranslationY), - toLockscreenTransitionY(mNotificationStackScrollLayoutController), + setTransitionY(mNotificationStackScrollLayoutController), + mMainDispatcher); + + // Lockscreen->Dreaming + collectFlow(mView, mKeyguardTransitionInteractor.getLockscreenToDreamingTransition(), + mLockscreenToDreamingTransition, mMainDispatcher); + collectFlow(mView, mLockscreenToDreamingTransitionViewModel.getLockscreenAlpha(), + setTransitionAlpha(mNotificationStackScrollLayoutController), + mMainDispatcher); + collectFlow(mView, mLockscreenToDreamingTransitionViewModel.lockscreenTranslationY( + mLockscreenToDreamingTransitionTranslationY), + setTransitionY(mNotificationStackScrollLayoutController), + mMainDispatcher); + + // Lockscreen->Occluded + collectFlow(mView, mKeyguardTransitionInteractor.getLockscreenToOccludedTransition(), + mLockscreenToOccludedTransition, mMainDispatcher); + collectFlow(mView, mLockscreenToOccludedTransitionViewModel.getLockscreenAlpha(), + setTransitionAlpha(mNotificationStackScrollLayoutController), + mMainDispatcher); + collectFlow(mView, mLockscreenToOccludedTransitionViewModel.lockscreenTranslationY( + mLockscreenToOccludedTransitionTranslationY), + setTransitionY(mNotificationStackScrollLayoutController), mMainDispatcher); } } @@ -1177,6 +1221,10 @@ public final class NotificationPanelViewController implements Dumpable { R.dimen.dreaming_to_lockscreen_transition_lockscreen_translation_y); mOccludedToLockscreenTransitionTranslationY = mResources.getDimensionPixelSize( R.dimen.occluded_to_lockscreen_transition_lockscreen_translation_y); + mLockscreenToDreamingTransitionTranslationY = mResources.getDimensionPixelSize( + R.dimen.lockscreen_to_dreaming_transition_lockscreen_translation_y); + mLockscreenToOccludedTransitionTranslationY = mResources.getDimensionPixelSize( + R.dimen.lockscreen_to_occluded_transition_lockscreen_translation_y); } private void updateViewControllers(KeyguardStatusView keyguardStatusView, @@ -1840,7 +1888,7 @@ public final class NotificationPanelViewController implements Dumpable { } private void updateClock() { - if (mIsToLockscreenTransitionRunning) { + if (mIsOcclusionTransitionRunning) { return; } float alpha = mClockPositionResult.clockAlpha * mKeyguardOnlyContentAlpha; @@ -2731,7 +2779,7 @@ public final class NotificationPanelViewController implements Dumpable { } else if (statusBarState == KEYGUARD || statusBarState == StatusBarState.SHADE_LOCKED) { mKeyguardBottomArea.setVisibility(View.VISIBLE); - if (!mIsToLockscreenTransitionRunning) { + if (!mIsOcclusionTransitionRunning) { mKeyguardBottomArea.setAlpha(1f); } } else { @@ -3600,7 +3648,7 @@ public final class NotificationPanelViewController implements Dumpable { } private void updateNotificationTranslucency() { - if (mIsToLockscreenTransitionRunning) { + if (mIsOcclusionTransitionRunning) { return; } float alpha = 1f; @@ -3658,7 +3706,7 @@ public final class NotificationPanelViewController implements Dumpable { } private void updateKeyguardBottomAreaAlpha() { - if (mIsToLockscreenTransitionRunning) { + if (mIsOcclusionTransitionRunning) { return; } // There are two possible panel expansion behaviors: @@ -5890,7 +5938,7 @@ public final class NotificationPanelViewController implements Dumpable { mCurrentPanelState = state; } - private Consumer toLockscreenTransitionAlpha( + private Consumer setTransitionAlpha( NotificationStackScrollLayoutController stackScroller) { return (Float alpha) -> { mKeyguardStatusViewController.setAlpha(alpha); @@ -5908,7 +5956,7 @@ public final class NotificationPanelViewController implements Dumpable { }; } - private Consumer toLockscreenTransitionY( + private Consumer setTransitionY( NotificationStackScrollLayoutController stackScroller) { return (Float translationY) -> { mKeyguardStatusViewController.setTranslationY(translationY, /* excludeMedia= */false); diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java index 64b6e6173ab4b..7ed6e3e556237 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java +++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java @@ -16,6 +16,8 @@ package com.android.systemui.shade; +import static com.android.systemui.util.kotlin.JavaAdapterKt.collectFlow; + import android.app.StatusBarManager; import android.media.AudioManager; import android.media.session.MediaSessionLegacyHelper; @@ -40,6 +42,9 @@ import com.android.systemui.flags.FeatureFlags; import com.android.systemui.flags.Flags; import com.android.systemui.keyguard.KeyguardUnlockAnimationController; import com.android.systemui.keyguard.domain.interactor.AlternateBouncerInteractor; +import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor; +import com.android.systemui.keyguard.shared.model.TransitionState; +import com.android.systemui.keyguard.shared.model.TransitionStep; import com.android.systemui.keyguard.ui.binder.KeyguardBouncerViewBinder; import com.android.systemui.keyguard.ui.viewmodel.KeyguardBouncerViewModel; import com.android.systemui.statusbar.DragDownHelper; @@ -58,6 +63,7 @@ import com.android.systemui.statusbar.phone.dagger.CentralSurfacesComponent; import com.android.systemui.statusbar.window.StatusBarWindowStateController; import java.io.PrintWriter; +import java.util.function.Consumer; import javax.inject.Inject; @@ -98,6 +104,13 @@ public class NotificationShadeWindowViewController { private final ShadeExpansionStateManager mShadeExpansionStateManager; private boolean mIsTrackingBarGesture = false; + private boolean mIsOcclusionTransitionRunning = false; + + private final Consumer mLockscreenToDreamingTransition = + (TransitionStep step) -> { + mIsOcclusionTransitionRunning = + step.getTransitionState() == TransitionState.RUNNING; + }; @Inject public NotificationShadeWindowViewController( @@ -122,7 +135,8 @@ public class NotificationShadeWindowViewController { FeatureFlags featureFlags, KeyguardBouncerViewModel keyguardBouncerViewModel, KeyguardBouncerComponent.Factory keyguardBouncerComponentFactory, - AlternateBouncerInteractor alternateBouncerInteractor + AlternateBouncerInteractor alternateBouncerInteractor, + KeyguardTransitionInteractor keyguardTransitionInteractor ) { mLockscreenShadeTransitionController = transitionController; mFalsingCollector = falsingCollector; @@ -152,6 +166,11 @@ public class NotificationShadeWindowViewController { keyguardBouncerViewModel, keyguardBouncerComponentFactory); } + + if (featureFlags.isEnabled(Flags.UNOCCLUSION_TRANSITION)) { + collectFlow(mView, keyguardTransitionInteractor.getLockscreenToDreamingTransition(), + mLockscreenToDreamingTransition); + } } /** @@ -219,6 +238,10 @@ public class NotificationShadeWindowViewController { return true; } + if (mIsOcclusionTransitionRunning) { + return false; + } + mFalsingCollector.onTouchEvent(ev); mPulsingWakeupGestureHandler.onTouchEvent(ev); mStatusBarKeyguardViewManager.onTouch(ev); diff --git a/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt index e8f8e25364b32..b4baa44985bb4 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt @@ -15,6 +15,7 @@ */ package com.android.keyguard +import com.android.systemui.statusbar.CommandQueue import android.content.BroadcastReceiver import android.testing.AndroidTestingRunner import android.view.View @@ -81,6 +82,7 @@ class ClockEventControllerTest : SysuiTestCase() { @Mock private lateinit var largeClockEvents: ClockFaceEvents @Mock private lateinit var parentView: View @Mock private lateinit var transitionRepository: KeyguardTransitionRepository + @Mock private lateinit var commandQueue: CommandQueue private lateinit var repository: FakeKeyguardRepository @Mock private lateinit var logBuffer: LogBuffer private lateinit var underTest: ClockEventController @@ -99,7 +101,7 @@ class ClockEventControllerTest : SysuiTestCase() { repository = FakeKeyguardRepository() underTest = ClockEventController( - KeyguardInteractor(repository = repository), + KeyguardInteractor(repository = repository, commandQueue = commandQueue), KeyguardTransitionInteractor(repository = transitionRepository), broadcastDispatcher, batteryController, diff --git a/packages/SystemUI/tests/src/com/android/keyguard/LockIconViewControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/keyguard/LockIconViewControllerBaseTest.java index e4c41a7ed804d..05bd1e482950a 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/LockIconViewControllerBaseTest.java +++ b/packages/SystemUI/tests/src/com/android/keyguard/LockIconViewControllerBaseTest.java @@ -49,6 +49,7 @@ import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor; import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor; import com.android.systemui.plugins.FalsingManager; import com.android.systemui.plugins.statusbar.StatusBarStateController; +import com.android.systemui.statusbar.CommandQueue; import com.android.systemui.statusbar.StatusBarState; import com.android.systemui.statusbar.VibratorHelper; import com.android.systemui.statusbar.policy.ConfigurationController; @@ -91,6 +92,7 @@ public class LockIconViewControllerBaseTest extends SysuiTestCase { protected @Mock AuthRippleController mAuthRippleController; protected @Mock FeatureFlags mFeatureFlags; protected @Mock KeyguardTransitionRepository mTransitionRepository; + protected @Mock CommandQueue mCommandQueue; protected FakeExecutor mDelayableExecutor = new FakeExecutor(new FakeSystemClock()); protected LockIconViewController mUnderTest; @@ -157,7 +159,7 @@ public class LockIconViewControllerBaseTest extends SysuiTestCase { mAuthRippleController, mResources, new KeyguardTransitionInteractor(mTransitionRepository), - new KeyguardInteractor(new FakeKeyguardRepository()), + new KeyguardInteractor(new FakeKeyguardRepository(), mCommandQueue), mFeatureFlags ); } diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/CustomizationProviderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/CustomizationProviderTest.kt index 465976607a01f..0a03b2c87f712 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/CustomizationProviderTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/CustomizationProviderTest.kt @@ -51,6 +51,7 @@ import com.android.systemui.settings.UserFileManager import com.android.systemui.settings.UserTracker import com.android.systemui.shared.customization.data.content.CustomizationProviderContract as Contract import com.android.systemui.shared.keyguard.shared.model.KeyguardQuickAffordanceSlots +import com.android.systemui.statusbar.CommandQueue import com.android.systemui.statusbar.policy.KeyguardStateController import com.android.systemui.util.FakeSharedPreferences import com.android.systemui.util.mockito.any @@ -86,9 +87,9 @@ class CustomizationProviderTest : SysuiTestCase() { @Mock private lateinit var backgroundHandler: Handler @Mock private lateinit var previewSurfacePackage: SurfaceControlViewHost.SurfacePackage @Mock private lateinit var launchAnimator: DialogLaunchAnimator + @Mock private lateinit var commandQueue: CommandQueue private lateinit var underTest: CustomizationProvider - private lateinit var testScope: TestScope @Before @@ -160,6 +161,7 @@ class CustomizationProviderTest : SysuiTestCase() { keyguardInteractor = KeyguardInteractor( repository = FakeKeyguardRepository(), + commandQueue = commandQueue, ), registry = mock(), lockPatternUtils = lockPatternUtils, diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java index 9b0d8dbe5a657..59e4655ce71e8 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java @@ -66,6 +66,7 @@ import com.android.systemui.classifier.FalsingCollectorFake; import com.android.systemui.colorextraction.SysuiColorExtractor; import com.android.systemui.dreams.DreamOverlayStateController; import com.android.systemui.dump.DumpManager; +import com.android.systemui.flags.FeatureFlags; import com.android.systemui.navigationbar.NavigationModeController; import com.android.systemui.settings.UserTracker; import com.android.systemui.shade.NotificationShadeWindowControllerImpl; @@ -134,6 +135,7 @@ public class KeyguardViewMediatorTest extends SysuiTestCase { private @Mock SysuiColorExtractor mColorExtractor; private @Mock AuthController mAuthController; private @Mock ShadeExpansionStateManager mShadeExpansionStateManager; + private @Mock FeatureFlags mFeatureFlags; private @Mock ShadeWindowLogger mShadeWindowLogger; private DeviceConfigProxy mDeviceConfig = new DeviceConfigProxyFake(); private FakeExecutor mUiBgExecutor = new FakeExecutor(new FakeSystemClock()); @@ -510,6 +512,7 @@ public class KeyguardViewMediatorTest extends SysuiTestCase { mScreenOnCoordinator, mInteractionJankMonitor, mDreamOverlayStateController, + mFeatureFlags, () -> mShadeController, () -> mNotificationShadeWindowController, () -> mActivityLaunchAnimator, diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepositoryTest.kt index 5d2f0eb01de19..f8f2a56d48086 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepositoryTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepositoryTest.kt @@ -104,7 +104,7 @@ class KeyguardTransitionRepositoryTest : SysuiTestCase() { val firstTransitionSteps = listWithStep(step = BigDecimal(.1), stop = BigDecimal(.1)) assertSteps(steps.subList(0, 4), firstTransitionSteps, AOD, LOCKSCREEN) - val secondTransitionSteps = listWithStep(step = BigDecimal(.1), start = BigDecimal(.9)) + val secondTransitionSteps = listWithStep(step = BigDecimal(.1), start = BigDecimal(.1)) assertSteps(steps.subList(4, steps.size), secondTransitionSteps, LOCKSCREEN, AOD) job.cancel() @@ -201,7 +201,10 @@ class KeyguardTransitionRepositoryTest : SysuiTestCase() { ) ) fractions.forEachIndexed { index, fraction -> - assertThat(steps[index + 1]) + val step = steps[index + 1] + val truncatedValue = + BigDecimal(step.value.toDouble()).setScale(2, RoundingMode.HALF_UP).toFloat() + assertThat(step.copy(value = truncatedValue)) .isEqualTo( TransitionStep( from, diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorTest.kt new file mode 100644 index 0000000000000..68d13d354a437 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorTest.kt @@ -0,0 +1,86 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package com.android.systemui.keyguard.domain.interactor + +import android.app.StatusBarManager +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.coroutines.collectLastValue +import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository +import com.android.systemui.keyguard.shared.model.CameraLaunchSourceModel +import com.android.systemui.statusbar.CommandQueue +import com.android.systemui.statusbar.CommandQueue.Callbacks +import com.android.systemui.util.mockito.argumentCaptor +import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.flow.onCompletion +import kotlinx.coroutines.test.runCurrent +import kotlinx.coroutines.test.runTest +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.junit.runners.JUnit4 +import org.mockito.Mock +import org.mockito.Mockito.verify +import org.mockito.MockitoAnnotations + +@SmallTest +@RunWith(JUnit4::class) +class KeyguardInteractorTest : SysuiTestCase() { + @Mock private lateinit var commandQueue: CommandQueue + + private lateinit var underTest: KeyguardInteractor + private lateinit var repository: FakeKeyguardRepository + + @Before + fun setUp() { + MockitoAnnotations.initMocks(this) + + repository = FakeKeyguardRepository() + underTest = KeyguardInteractor(repository, commandQueue) + } + + @Test + fun onCameraLaunchDetected() = runTest { + val flow = underTest.onCameraLaunchDetected + var cameraLaunchSource = collectLastValue(flow) + runCurrent() + + val captor = argumentCaptor() + verify(commandQueue).addCallback(captor.capture()) + + captor.value.onCameraLaunchGestureDetected(StatusBarManager.CAMERA_LAUNCH_SOURCE_WIGGLE) + assertThat(cameraLaunchSource()).isEqualTo(CameraLaunchSourceModel.WIGGLE) + + captor.value.onCameraLaunchGestureDetected( + StatusBarManager.CAMERA_LAUNCH_SOURCE_POWER_DOUBLE_TAP + ) + assertThat(cameraLaunchSource()).isEqualTo(CameraLaunchSourceModel.POWER_DOUBLE_TAP) + + captor.value.onCameraLaunchGestureDetected( + StatusBarManager.CAMERA_LAUNCH_SOURCE_LIFT_TRIGGER + ) + assertThat(cameraLaunchSource()).isEqualTo(CameraLaunchSourceModel.LIFT_TRIGGER) + + captor.value.onCameraLaunchGestureDetected( + StatusBarManager.CAMERA_LAUNCH_SOURCE_QUICK_AFFORDANCE + ) + assertThat(cameraLaunchSource()).isEqualTo(CameraLaunchSourceModel.QUICK_AFFORDANCE) + + flow.onCompletion { verify(commandQueue).removeCallback(captor.value) } + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorParameterizedTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorParameterizedTest.kt index 14b7c311d9afe..43287b03b36a6 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorParameterizedTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorParameterizedTest.kt @@ -44,6 +44,7 @@ import com.android.systemui.plugins.ActivityStarter import com.android.systemui.settings.FakeUserTracker import com.android.systemui.settings.UserFileManager import com.android.systemui.settings.UserTracker +import com.android.systemui.statusbar.CommandQueue import com.android.systemui.statusbar.policy.KeyguardStateController import com.android.systemui.util.FakeSharedPreferences import com.android.systemui.util.mockito.any @@ -216,6 +217,7 @@ class KeyguardQuickAffordanceInteractorParameterizedTest : SysuiTestCase() { @Mock private lateinit var animationController: ActivityLaunchAnimator.Controller @Mock private lateinit var expandable: Expandable @Mock private lateinit var launchAnimator: DialogLaunchAnimator + @Mock private lateinit var commandQueue: CommandQueue private lateinit var underTest: KeyguardQuickAffordanceInteractor @@ -286,7 +288,11 @@ class KeyguardQuickAffordanceInteractorParameterizedTest : SysuiTestCase() { ) underTest = KeyguardQuickAffordanceInteractor( - keyguardInteractor = KeyguardInteractor(repository = FakeKeyguardRepository()), + keyguardInteractor = + KeyguardInteractor( + repository = FakeKeyguardRepository(), + commandQueue = commandQueue + ), registry = FakeKeyguardQuickAffordanceRegistry( mapOf( diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorTest.kt index 972919a9d27a0..b75a15da641ab 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorTest.kt @@ -46,6 +46,7 @@ import com.android.systemui.plugins.ActivityStarter import com.android.systemui.settings.UserFileManager import com.android.systemui.settings.UserTracker import com.android.systemui.shared.keyguard.shared.model.KeyguardQuickAffordanceSlots +import com.android.systemui.statusbar.CommandQueue import com.android.systemui.statusbar.policy.KeyguardStateController import com.android.systemui.util.FakeSharedPreferences import com.android.systemui.util.mockito.mock @@ -75,6 +76,7 @@ class KeyguardQuickAffordanceInteractorTest : SysuiTestCase() { @Mock private lateinit var userTracker: UserTracker @Mock private lateinit var activityStarter: ActivityStarter @Mock private lateinit var launchAnimator: DialogLaunchAnimator + @Mock private lateinit var commandQueue: CommandQueue private lateinit var underTest: KeyguardQuickAffordanceInteractor @@ -152,7 +154,8 @@ class KeyguardQuickAffordanceInteractorTest : SysuiTestCase() { underTest = KeyguardQuickAffordanceInteractor( - keyguardInteractor = KeyguardInteractor(repository = repository), + keyguardInteractor = + KeyguardInteractor(repository = repository, commandQueue = commandQueue), registry = FakeKeyguardQuickAffordanceRegistry( mapOf( diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt index d2b7838274a9f..754adfdc48b33 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt @@ -33,6 +33,7 @@ import com.android.systemui.keyguard.shared.model.WakefulnessState import com.android.systemui.keyguard.util.KeyguardTransitionRunner import com.android.systemui.shade.data.repository.FakeShadeRepository import com.android.systemui.shade.data.repository.ShadeRepository +import com.android.systemui.statusbar.CommandQueue import com.android.systemui.util.mockito.withArgCaptor import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.cancelChildren @@ -66,6 +67,7 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() { // Used to verify transition requests for test output @Mock private lateinit var mockTransitionRepository: KeyguardTransitionRepository + @Mock private lateinit var commandQueue: CommandQueue private lateinit var fromLockscreenTransitionInteractor: FromLockscreenTransitionInteractor private lateinit var fromDreamingTransitionInteractor: FromDreamingTransitionInteractor @@ -85,7 +87,7 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() { fromLockscreenTransitionInteractor = FromLockscreenTransitionInteractor( scope = testScope, - keyguardInteractor = KeyguardInteractor(keyguardRepository), + keyguardInteractor = KeyguardInteractor(keyguardRepository, commandQueue), shadeRepository = shadeRepository, keyguardTransitionRepository = mockTransitionRepository, keyguardTransitionInteractor = KeyguardTransitionInteractor(transitionRepository), @@ -95,7 +97,7 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() { fromDreamingTransitionInteractor = FromDreamingTransitionInteractor( scope = testScope, - keyguardInteractor = KeyguardInteractor(keyguardRepository), + keyguardInteractor = KeyguardInteractor(keyguardRepository, commandQueue), keyguardTransitionRepository = mockTransitionRepository, keyguardTransitionInteractor = KeyguardTransitionInteractor(transitionRepository), ) diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt index a2c2f711b1d4b..022afdd61fc25 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt @@ -47,6 +47,7 @@ import com.android.systemui.plugins.ActivityStarter import com.android.systemui.settings.UserFileManager import com.android.systemui.settings.UserTracker import com.android.systemui.shared.keyguard.shared.model.KeyguardQuickAffordanceSlots +import com.android.systemui.statusbar.CommandQueue import com.android.systemui.statusbar.policy.KeyguardStateController import com.android.systemui.util.FakeSharedPreferences import com.android.systemui.util.mockito.any @@ -84,6 +85,7 @@ class KeyguardBottomAreaViewModelTest : SysuiTestCase() { @Mock private lateinit var userTracker: UserTracker @Mock private lateinit var activityStarter: ActivityStarter @Mock private lateinit var launchAnimator: DialogLaunchAnimator + @Mock private lateinit var commandQueue: CommandQueue private lateinit var underTest: KeyguardBottomAreaViewModel @@ -124,7 +126,8 @@ class KeyguardBottomAreaViewModelTest : SysuiTestCase() { ) repository = FakeKeyguardRepository() - val keyguardInteractor = KeyguardInteractor(repository = repository) + val keyguardInteractor = + KeyguardInteractor(repository = repository, commandQueue = commandQueue) whenever(userTracker.userHandle).thenReturn(mock()) whenever(lockPatternUtils.getStrongAuthForUser(anyInt())) .thenReturn(LockPatternUtils.StrongAuthTracker.STRONG_AUTH_NOT_REQUIRED) diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToDreamingTransitionViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToDreamingTransitionViewModelTest.kt new file mode 100644 index 0000000000000..739059126b04a --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToDreamingTransitionViewModelTest.kt @@ -0,0 +1,135 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.keyguard.ui.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.FromLockscreenTransitionInteractor.Companion.TO_DREAMING_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.LockscreenToDreamingTransitionViewModel.Companion.LOCKSCREEN_ALPHA +import com.android.systemui.keyguard.ui.viewmodel.LockscreenToDreamingTransitionViewModel.Companion.LOCKSCREEN_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 LockscreenToDreamingTransitionViewModelTest : SysuiTestCase() { + private lateinit var underTest: LockscreenToDreamingTransitionViewModel + private lateinit var repository: FakeKeyguardTransitionRepository + + @Before + fun setUp() { + repository = FakeKeyguardTransitionRepository() + val interactor = KeyguardTransitionInteractor(repository) + underTest = LockscreenToDreamingTransitionViewModel(interactor) + } + + @Test + fun lockscreenFadeOut() = + runTest(UnconfinedTestDispatcher()) { + val values = mutableListOf() + + val job = underTest.lockscreenAlpha.onEach { values.add(it) }.launchIn(this) + + // Should start running here... + repository.sendTransitionStep(step(0f)) + repository.sendTransitionStep(step(0.1f)) + repository.sendTransitionStep(step(0.2f)) + // ...up to here + repository.sendTransitionStep(step(0.3f)) + repository.sendTransitionStep(step(1f)) + + // Only three values should be present, since the dream overlay runs for a small + // fraction + // of the overall animation time + assertThat(values.size).isEqualTo(3) + assertThat(values[0]).isEqualTo(1f - animValue(0f, LOCKSCREEN_ALPHA)) + assertThat(values[1]).isEqualTo(1f - animValue(0.1f, LOCKSCREEN_ALPHA)) + assertThat(values[2]).isEqualTo(1f - animValue(0.2f, LOCKSCREEN_ALPHA)) + + job.cancel() + } + + @Test + fun lockscreenTranslationY() = + runTest(UnconfinedTestDispatcher()) { + val values = mutableListOf() + + val pixels = 100 + val job = + underTest.lockscreenTranslationY(pixels).onEach { values.add(it) }.launchIn(this) + + // Should start running here... + repository.sendTransitionStep(step(0f)) + repository.sendTransitionStep(step(0.3f)) + repository.sendTransitionStep(step(0.5f)) + // ...up to here + repository.sendTransitionStep(step(1f)) + + assertThat(values.size).isEqualTo(3) + assertThat(values[0]) + .isEqualTo( + EMPHASIZED_ACCELERATE.getInterpolation( + animValue(0f, LOCKSCREEN_TRANSLATION_Y) + ) * pixels + ) + assertThat(values[1]) + .isEqualTo( + EMPHASIZED_ACCELERATE.getInterpolation( + animValue(0.3f, LOCKSCREEN_TRANSLATION_Y) + ) * pixels + ) + assertThat(values[2]) + .isEqualTo( + EMPHASIZED_ACCELERATE.getInterpolation( + animValue(0.5f, LOCKSCREEN_TRANSLATION_Y) + ) * pixels + ) + job.cancel() + } + + private fun animValue(stepValue: Float, params: AnimationParams): Float { + val totalDuration = TO_DREAMING_DURATION + val startValue = (params.startTime / totalDuration).toFloat() + + val multiplier = (totalDuration / params.duration).toFloat() + return (stepValue - startValue) * multiplier + } + + private fun step(value: Float): TransitionStep { + return TransitionStep( + from = KeyguardState.LOCKSCREEN, + to = KeyguardState.DREAMING, + value = value, + transitionState = TransitionState.RUNNING, + ownerName = "LockscreenToDreamingTransitionViewModelTest" + ) + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToOccludedTransitionViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToOccludedTransitionViewModelTest.kt new file mode 100644 index 0000000000000..759345f51c59a --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToOccludedTransitionViewModelTest.kt @@ -0,0 +1,140 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.keyguard.ui.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.FromLockscreenTransitionInteractor.Companion.TO_OCCLUDED_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.LockscreenToOccludedTransitionViewModel.Companion.LOCKSCREEN_ALPHA +import com.android.systemui.keyguard.ui.viewmodel.LockscreenToOccludedTransitionViewModel.Companion.LOCKSCREEN_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 LockscreenToOccludedTransitionViewModelTest : SysuiTestCase() { + private lateinit var underTest: LockscreenToOccludedTransitionViewModel + private lateinit var repository: FakeKeyguardTransitionRepository + + @Before + fun setUp() { + repository = FakeKeyguardTransitionRepository() + val interactor = KeyguardTransitionInteractor(repository) + underTest = LockscreenToOccludedTransitionViewModel(interactor) + } + + @Test + fun lockscreenFadeOut() = + runTest(UnconfinedTestDispatcher()) { + val values = mutableListOf() + + val job = underTest.lockscreenAlpha.onEach { values.add(it) }.launchIn(this) + + // Should start running here... + repository.sendTransitionStep(step(0f)) + repository.sendTransitionStep(step(0.1f)) + repository.sendTransitionStep(step(0.4f)) + // ...up to here + repository.sendTransitionStep(step(0.7f)) + repository.sendTransitionStep(step(1f)) + + // Only 3 values should be present, since the dream overlay runs for a small fraction + // of the overall animation time + assertThat(values.size).isEqualTo(3) + assertThat(values[0]).isEqualTo(1f - animValue(0f, LOCKSCREEN_ALPHA)) + assertThat(values[1]).isEqualTo(1f - animValue(0.1f, LOCKSCREEN_ALPHA)) + assertThat(values[2]).isEqualTo(1f - animValue(0.4f, LOCKSCREEN_ALPHA)) + + job.cancel() + } + + @Test + fun lockscreenTranslationY() = + runTest(UnconfinedTestDispatcher()) { + val values = mutableListOf() + + val pixels = 100 + val job = + underTest.lockscreenTranslationY(pixels).onEach { values.add(it) }.launchIn(this) + + // Should start running here... + repository.sendTransitionStep(step(0f)) + repository.sendTransitionStep(step(0.3f)) + repository.sendTransitionStep(step(0.5f)) + repository.sendTransitionStep(step(1f)) + // ...up to here + + assertThat(values.size).isEqualTo(4) + assertThat(values[0]) + .isEqualTo( + EMPHASIZED_ACCELERATE.getInterpolation( + animValue(0f, LOCKSCREEN_TRANSLATION_Y) + ) * pixels + ) + assertThat(values[1]) + .isEqualTo( + EMPHASIZED_ACCELERATE.getInterpolation( + animValue(0.3f, LOCKSCREEN_TRANSLATION_Y) + ) * pixels + ) + assertThat(values[2]) + .isEqualTo( + EMPHASIZED_ACCELERATE.getInterpolation( + animValue(0.5f, LOCKSCREEN_TRANSLATION_Y) + ) * pixels + ) + assertThat(values[3]) + .isEqualTo( + EMPHASIZED_ACCELERATE.getInterpolation( + animValue(1f, LOCKSCREEN_TRANSLATION_Y) + ) * pixels + ) + job.cancel() + } + + private fun animValue(stepValue: Float, params: AnimationParams): Float { + val totalDuration = TO_OCCLUDED_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.LOCKSCREEN, + to = KeyguardState.OCCLUDED, + value = value, + transitionState = TransitionState.RUNNING, + ownerName = "LockscreenToOccludedTransitionViewModelTest" + ) + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java index 53ab19c47106c..a1e9a2700c233 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java @@ -108,6 +108,8 @@ import com.android.systemui.keyguard.domain.interactor.KeyguardBottomAreaInterac import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor; import com.android.systemui.keyguard.ui.viewmodel.DreamingToLockscreenTransitionViewModel; import com.android.systemui.keyguard.ui.viewmodel.KeyguardBottomAreaViewModel; +import com.android.systemui.keyguard.ui.viewmodel.LockscreenToDreamingTransitionViewModel; +import com.android.systemui.keyguard.ui.viewmodel.LockscreenToOccludedTransitionViewModel; import com.android.systemui.keyguard.ui.viewmodel.OccludedToLockscreenTransitionViewModel; import com.android.systemui.media.controls.pipeline.MediaDataManager; import com.android.systemui.media.controls.ui.KeyguardMediaController; @@ -295,6 +297,9 @@ public class NotificationPanelViewControllerTest extends SysuiTestCase { @Mock private AlternateBouncerInteractor mAlternateBouncerInteractor; @Mock private DreamingToLockscreenTransitionViewModel mDreamingToLockscreenTransitionViewModel; @Mock private OccludedToLockscreenTransitionViewModel mOccludedToLockscreenTransitionViewModel; + @Mock private LockscreenToDreamingTransitionViewModel mLockscreenToDreamingTransitionViewModel; + @Mock private LockscreenToOccludedTransitionViewModel mLockscreenToOccludedTransitionViewModel; + @Mock private KeyguardTransitionInteractor mKeyguardTransitionInteractor; @Mock private CoroutineDispatcher mMainDispatcher; @Mock private MotionEvent mDownMotionEvent; @@ -516,6 +521,8 @@ public class NotificationPanelViewControllerTest extends SysuiTestCase { mAlternateBouncerInteractor, mDreamingToLockscreenTransitionViewModel, mOccludedToLockscreenTransitionViewModel, + mLockscreenToDreamingTransitionViewModel, + mLockscreenToOccludedTransitionViewModel, mMainDispatcher, mKeyguardTransitionInteractor, mDumpManager); diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt index 3137aa57b1bf2..4c768253202a4 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt @@ -31,6 +31,7 @@ import com.android.systemui.dock.DockManager import com.android.systemui.flags.FeatureFlags import com.android.systemui.keyguard.KeyguardUnlockAnimationController import com.android.systemui.keyguard.domain.interactor.AlternateBouncerInteractor +import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor import com.android.systemui.keyguard.ui.viewmodel.KeyguardBouncerViewModel import com.android.systemui.shade.NotificationShadeWindowView.InteractionEventHandler import com.android.systemui.statusbar.LockscreenShadeTransitionController @@ -104,6 +105,7 @@ class NotificationShadeWindowViewControllerTest : SysuiTestCase() { @Mock lateinit var keyguardBouncerContainer: ViewGroup @Mock lateinit var keyguardBouncerComponent: KeyguardBouncerComponent @Mock lateinit var keyguardHostViewController: KeyguardHostViewController + @Mock lateinit var keyguardTransitionInteractor: KeyguardTransitionInteractor private lateinit var interactionEventHandlerCaptor: ArgumentCaptor private lateinit var interactionEventHandler: InteractionEventHandler @@ -136,7 +138,8 @@ class NotificationShadeWindowViewControllerTest : SysuiTestCase() { featureFlags, keyguardBouncerViewModel, keyguardBouncerComponentFactory, - alternateBouncerInteractor + alternateBouncerInteractor, + keyguardTransitionInteractor, ) underTest.setupExpandedStatusBar() diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.java index 544b00e3c9ee7..d43562443d6e2 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.java @@ -41,6 +41,7 @@ import com.android.systemui.dock.DockManager; import com.android.systemui.flags.FeatureFlags; import com.android.systemui.keyguard.KeyguardUnlockAnimationController; import com.android.systemui.keyguard.domain.interactor.AlternateBouncerInteractor; +import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor; import com.android.systemui.keyguard.ui.viewmodel.KeyguardBouncerViewModel; import com.android.systemui.statusbar.DragDownHelper; import com.android.systemui.statusbar.LockscreenShadeTransitionController; @@ -95,6 +96,7 @@ public class NotificationShadeWindowViewTest extends SysuiTestCase { @Mock private KeyguardBouncerComponent.Factory mKeyguardBouncerComponentFactory; @Mock private NotificationInsetsController mNotificationInsetsController; @Mock private AlternateBouncerInteractor mAlternateBouncerInteractor; + @Mock private KeyguardTransitionInteractor mKeyguardTransitionInteractor; @Captor private ArgumentCaptor mInteractionEventHandlerCaptor; @@ -135,7 +137,8 @@ public class NotificationShadeWindowViewTest extends SysuiTestCase { mFeatureFlags, mKeyguardBouncerViewModel, mKeyguardBouncerComponentFactory, - mAlternateBouncerInteractor + mAlternateBouncerInteractor, + mKeyguardTransitionInteractor ); mController.setupExpandedStatusBar(); mController.setDragDownHelper(mDragDownHelper); diff --git a/packages/SystemUI/tests/src/com/android/systemui/unfold/FoldAodAnimationControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/unfold/FoldAodAnimationControllerTest.kt index 89402de792dc1..30c4f97e5503f 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/unfold/FoldAodAnimationControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/unfold/FoldAodAnimationControllerTest.kt @@ -29,6 +29,7 @@ import com.android.systemui.keyguard.WakefulnessLifecycle import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor import com.android.systemui.shade.NotificationPanelViewController +import com.android.systemui.statusbar.CommandQueue import com.android.systemui.statusbar.LightRevealScrim import com.android.systemui.statusbar.phone.CentralSurfaces import com.android.systemui.unfold.util.FoldableDeviceStates @@ -73,6 +74,8 @@ class FoldAodAnimationControllerTest : SysuiTestCase() { @Mock lateinit var viewTreeObserver: ViewTreeObserver + @Mock private lateinit var commandQueue: CommandQueue + @Captor private lateinit var foldStateListenerCaptor: ArgumentCaptor private lateinit var deviceStates: FoldableDeviceStates @@ -102,7 +105,8 @@ class FoldAodAnimationControllerTest : SysuiTestCase() { } keyguardRepository = FakeKeyguardRepository() - val keyguardInteractor = KeyguardInteractor(repository = keyguardRepository) + val keyguardInteractor = + KeyguardInteractor(repository = keyguardRepository, commandQueue = commandQueue) // Needs to be run on the main thread runBlocking(IMMEDIATE) { diff --git a/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/UserInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/UserInteractorTest.kt index 1a2ee1809dff2..7380ca4596238 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/UserInteractorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/UserInteractorTest.kt @@ -43,6 +43,7 @@ import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor import com.android.systemui.plugins.ActivityStarter import com.android.systemui.qs.user.UserSwitchDialogController +import com.android.systemui.statusbar.CommandQueue import com.android.systemui.statusbar.policy.DeviceProvisionedController import com.android.systemui.telephony.data.repository.FakeTelephonyRepository import com.android.systemui.telephony.domain.interactor.TelephonyInteractor @@ -95,6 +96,7 @@ class UserInteractorTest : SysuiTestCase() { @Mock private lateinit var dialogShower: UserSwitchDialogController.DialogShower @Mock private lateinit var resumeSessionReceiver: GuestResumeSessionReceiver @Mock private lateinit var resetOrExitSessionReceiver: GuestResetOrExitSessionReceiver + @Mock private lateinit var commandQueue: CommandQueue private lateinit var underTest: UserInteractor @@ -138,6 +140,7 @@ class UserInteractorTest : SysuiTestCase() { keyguardInteractor = KeyguardInteractor( repository = keyguardRepository, + commandQueue = commandQueue, ), manager = manager, applicationScope = testScope.backgroundScope, diff --git a/packages/SystemUI/tests/src/com/android/systemui/user/ui/viewmodel/StatusBarUserChipViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/user/ui/viewmodel/StatusBarUserChipViewModelTest.kt index 5d4c1cc163d44..46f38ed8b4ef0 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/user/ui/viewmodel/StatusBarUserChipViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/user/ui/viewmodel/StatusBarUserChipViewModelTest.kt @@ -34,6 +34,7 @@ import com.android.systemui.flags.Flags import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor import com.android.systemui.plugins.ActivityStarter +import com.android.systemui.statusbar.CommandQueue import com.android.systemui.statusbar.policy.DeviceProvisionedController import com.android.systemui.telephony.data.repository.FakeTelephonyRepository import com.android.systemui.telephony.domain.interactor.TelephonyInteractor @@ -75,6 +76,7 @@ class StatusBarUserChipViewModelTest : SysuiTestCase() { @Mock private lateinit var uiEventLogger: UiEventLogger @Mock private lateinit var resumeSessionReceiver: GuestResumeSessionReceiver @Mock private lateinit var resetOrExitSessionReceiver: GuestResetOrExitSessionReceiver + @Mock private lateinit var commandQueue: CommandQueue private lateinit var underTest: StatusBarUserChipViewModel @@ -241,6 +243,7 @@ class StatusBarUserChipViewModelTest : SysuiTestCase() { keyguardInteractor = KeyguardInteractor( repository = keyguardRepository, + commandQueue = commandQueue, ), featureFlags = FakeFeatureFlags().apply { set(Flags.FULL_SCREEN_USER_SWITCHER, false) }, diff --git a/packages/SystemUI/tests/src/com/android/systemui/user/ui/viewmodel/UserSwitcherViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/user/ui/viewmodel/UserSwitcherViewModelTest.kt index 0efede1c2a1cf..1d57d0b69d42b 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/user/ui/viewmodel/UserSwitcherViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/user/ui/viewmodel/UserSwitcherViewModelTest.kt @@ -34,6 +34,7 @@ import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor import com.android.systemui.plugins.ActivityStarter import com.android.systemui.power.data.repository.FakePowerRepository import com.android.systemui.power.domain.interactor.PowerInteractor +import com.android.systemui.statusbar.CommandQueue import com.android.systemui.statusbar.policy.DeviceProvisionedController import com.android.systemui.telephony.data.repository.FakeTelephonyRepository import com.android.systemui.telephony.domain.interactor.TelephonyInteractor @@ -76,6 +77,7 @@ class UserSwitcherViewModelTest : SysuiTestCase() { @Mock private lateinit var uiEventLogger: UiEventLogger @Mock private lateinit var resumeSessionReceiver: GuestResumeSessionReceiver @Mock private lateinit var resetOrExitSessionReceiver: GuestResetOrExitSessionReceiver + @Mock private lateinit var commandQueue: CommandQueue private lateinit var underTest: UserSwitcherViewModel @@ -142,6 +144,7 @@ class UserSwitcherViewModelTest : SysuiTestCase() { keyguardInteractor = KeyguardInteractor( repository = keyguardRepository, + commandQueue = commandQueue, ), featureFlags = FakeFeatureFlags().apply { @@ -170,273 +173,295 @@ class UserSwitcherViewModelTest : SysuiTestCase() { } @Test - fun users() = testScope.runTest { - val userInfos = - listOf( - UserInfo( - /* id= */ 0, - /* name= */ "zero", - /* iconPath= */ "", - /* flags= */ UserInfo.FLAG_PRIMARY or UserInfo.FLAG_ADMIN or UserInfo.FLAG_FULL, - UserManager.USER_TYPE_FULL_SYSTEM, - ), - UserInfo( - /* id= */ 1, - /* name= */ "one", - /* iconPath= */ "", - /* flags= */ UserInfo.FLAG_FULL, - UserManager.USER_TYPE_FULL_SYSTEM, - ), - UserInfo( - /* id= */ 2, - /* name= */ "two", - /* iconPath= */ "", - /* flags= */ UserInfo.FLAG_FULL, - UserManager.USER_TYPE_FULL_SYSTEM, - ), - ) - userRepository.setUserInfos(userInfos) - userRepository.setSelectedUserInfo(userInfos[0]) - - val userViewModels = mutableListOf>() - val job = launch(testDispatcher) { underTest.users.toList(userViewModels) } - - assertThat(userViewModels.last()).hasSize(3) - assertUserViewModel( - viewModel = userViewModels.last()[0], - viewKey = 0, - name = Text.Loaded("zero"), - isSelectionMarkerVisible = true, - ) - assertUserViewModel( - viewModel = userViewModels.last()[1], - viewKey = 1, - name = Text.Loaded("one"), - isSelectionMarkerVisible = false, - ) - assertUserViewModel( - viewModel = userViewModels.last()[2], - viewKey = 2, - name = Text.Loaded("two"), - isSelectionMarkerVisible = false, - ) - job.cancel() - } - - @Test - fun `maximumUserColumns - few users`() = testScope.runTest { - setUsers(count = 2) - val values = mutableListOf() - val job = launch(testDispatcher) { underTest.maximumUserColumns.toList(values) } - - assertThat(values.last()).isEqualTo(4) - - job.cancel() - } - - @Test - fun `maximumUserColumns - many users`() = testScope.runTest { - setUsers(count = 5) - val values = mutableListOf() - val job = launch(testDispatcher) { underTest.maximumUserColumns.toList(values) } - - assertThat(values.last()).isEqualTo(3) - job.cancel() - } - - @Test - fun `isOpenMenuButtonVisible - has actions - true`() = testScope.runTest { - setUsers(2) - - val isVisible = mutableListOf() - val job = launch(testDispatcher) { underTest.isOpenMenuButtonVisible.toList(isVisible) } - - assertThat(isVisible.last()).isTrue() - job.cancel() - } - - @Test - fun `isOpenMenuButtonVisible - no actions - false`() = testScope.runTest { - val userInfos = setUsers(2) - userRepository.setSelectedUserInfo(userInfos[1]) - keyguardRepository.setKeyguardShowing(true) - whenever(manager.canAddMoreUsers(any())).thenReturn(false) - - val isVisible = mutableListOf() - val job = launch(testDispatcher) { underTest.isOpenMenuButtonVisible.toList(isVisible) } - - assertThat(isVisible.last()).isFalse() - job.cancel() - } - - @Test - fun menu() = testScope.runTest { - val isMenuVisible = mutableListOf() - val job = launch(testDispatcher) { underTest.isMenuVisible.toList(isMenuVisible) } - assertThat(isMenuVisible.last()).isFalse() - - underTest.onOpenMenuButtonClicked() - assertThat(isMenuVisible.last()).isTrue() - - underTest.onMenuClosed() - assertThat(isMenuVisible.last()).isFalse() - - job.cancel() - } - - @Test - fun `menu actions`() = testScope.runTest { - setUsers(2) - val actions = mutableListOf>() - val job = launch(testDispatcher) { underTest.menu.toList(actions) } - - assertThat(actions.last().map { it.viewKey }) - .isEqualTo( + fun users() = + testScope.runTest { + val userInfos = listOf( - UserActionModel.ENTER_GUEST_MODE.ordinal.toLong(), - UserActionModel.ADD_USER.ordinal.toLong(), - UserActionModel.ADD_SUPERVISED_USER.ordinal.toLong(), - UserActionModel.NAVIGATE_TO_USER_MANAGEMENT.ordinal.toLong(), + UserInfo( + /* id= */ 0, + /* name= */ "zero", + /* iconPath= */ "", + /* flags= */ UserInfo.FLAG_PRIMARY or + UserInfo.FLAG_ADMIN or + UserInfo.FLAG_FULL, + UserManager.USER_TYPE_FULL_SYSTEM, + ), + UserInfo( + /* id= */ 1, + /* name= */ "one", + /* iconPath= */ "", + /* flags= */ UserInfo.FLAG_FULL, + UserManager.USER_TYPE_FULL_SYSTEM, + ), + UserInfo( + /* id= */ 2, + /* name= */ "two", + /* iconPath= */ "", + /* flags= */ UserInfo.FLAG_FULL, + UserManager.USER_TYPE_FULL_SYSTEM, + ), ) - ) + userRepository.setUserInfos(userInfos) + userRepository.setSelectedUserInfo(userInfos[0]) - job.cancel() - } + val userViewModels = mutableListOf>() + val job = launch(testDispatcher) { underTest.users.toList(userViewModels) } - @Test - fun `isFinishRequested - finishes when user is switched`() = testScope.runTest { - val userInfos = setUsers(count = 2) - val isFinishRequested = mutableListOf() - val job = launch(testDispatcher) { underTest.isFinishRequested.toList(isFinishRequested) } - assertThat(isFinishRequested.last()).isFalse() - - userRepository.setSelectedUserInfo(userInfos[1]) - - assertThat(isFinishRequested.last()).isTrue() - - job.cancel() - } - - @Test - fun `isFinishRequested - finishes when the screen turns off`() = testScope.runTest { - setUsers(count = 2) - powerRepository.setInteractive(true) - val isFinishRequested = mutableListOf() - val job = launch(testDispatcher) { underTest.isFinishRequested.toList(isFinishRequested) } - assertThat(isFinishRequested.last()).isFalse() - - powerRepository.setInteractive(false) - - assertThat(isFinishRequested.last()).isTrue() - - job.cancel() - } - - @Test - fun `isFinishRequested - finishes when cancel button is clicked`() = testScope.runTest { - setUsers(count = 2) - powerRepository.setInteractive(true) - val isFinishRequested = mutableListOf() - val job = launch(testDispatcher) { underTest.isFinishRequested.toList(isFinishRequested) } - assertThat(isFinishRequested.last()).isFalse() - - underTest.onCancelButtonClicked() - - assertThat(isFinishRequested.last()).isTrue() - - underTest.onFinished() - - assertThat(isFinishRequested.last()).isFalse() - - job.cancel() - } - - @Test - fun `guest selected -- name is exit guest`() = testScope.runTest { - val userInfos = - listOf( - UserInfo( - /* id= */ 0, - /* name= */ "zero", - /* iconPath= */ "", - /* flags= */ UserInfo.FLAG_PRIMARY or UserInfo.FLAG_ADMIN or UserInfo.FLAG_FULL, - UserManager.USER_TYPE_FULL_SYSTEM, - ), - UserInfo( - /* id= */ 1, - /* name= */ "one", - /* iconPath= */ "", - /* flags= */ UserInfo.FLAG_FULL, - UserManager.USER_TYPE_FULL_GUEST, - ), - ) - - userRepository.setUserInfos(userInfos) - userRepository.setSelectedUserInfo(userInfos[1]) - - val userViewModels = mutableListOf>() - val job = launch(testDispatcher) { underTest.users.toList(userViewModels) } - - assertThat(userViewModels.last()).hasSize(2) - assertUserViewModel( - viewModel = userViewModels.last()[0], - viewKey = 0, - name = Text.Loaded("zero"), - isSelectionMarkerVisible = false, - ) - assertUserViewModel( - viewModel = userViewModels.last()[1], - viewKey = 1, - name = Text.Resource( - com.android.settingslib.R.string.guest_exit_quick_settings_button - ), - isSelectionMarkerVisible = true, - ) - job.cancel() - } - - @Test - fun `guest not selected -- name is guest`() = testScope.runTest { - val userInfos = - listOf( - UserInfo( - /* id= */ 0, - /* name= */ "zero", - /* iconPath= */ "", - /* flags= */ UserInfo.FLAG_PRIMARY or UserInfo.FLAG_ADMIN or UserInfo.FLAG_FULL, - UserManager.USER_TYPE_FULL_SYSTEM, - ), - UserInfo( - /* id= */ 1, - /* name= */ "one", - /* iconPath= */ "", - /* flags= */ UserInfo.FLAG_FULL, - UserManager.USER_TYPE_FULL_GUEST, - ), - ) - - userRepository.setUserInfos(userInfos) - userRepository.setSelectedUserInfo(userInfos[0]) - runCurrent() - - val userViewModels = mutableListOf>() - val job = launch(testDispatcher) { underTest.users.toList(userViewModels) } - - assertThat(userViewModels.last()).hasSize(2) - assertUserViewModel( + assertThat(userViewModels.last()).hasSize(3) + assertUserViewModel( viewModel = userViewModels.last()[0], viewKey = 0, name = Text.Loaded("zero"), isSelectionMarkerVisible = true, - ) - assertUserViewModel( + ) + assertUserViewModel( viewModel = userViewModels.last()[1], viewKey = 1, name = Text.Loaded("one"), isSelectionMarkerVisible = false, - ) - job.cancel() - } + ) + assertUserViewModel( + viewModel = userViewModels.last()[2], + viewKey = 2, + name = Text.Loaded("two"), + isSelectionMarkerVisible = false, + ) + job.cancel() + } + + @Test + fun `maximumUserColumns - few users`() = + testScope.runTest { + setUsers(count = 2) + val values = mutableListOf() + val job = launch(testDispatcher) { underTest.maximumUserColumns.toList(values) } + + assertThat(values.last()).isEqualTo(4) + + job.cancel() + } + + @Test + fun `maximumUserColumns - many users`() = + testScope.runTest { + setUsers(count = 5) + val values = mutableListOf() + val job = launch(testDispatcher) { underTest.maximumUserColumns.toList(values) } + + assertThat(values.last()).isEqualTo(3) + job.cancel() + } + + @Test + fun `isOpenMenuButtonVisible - has actions - true`() = + testScope.runTest { + setUsers(2) + + val isVisible = mutableListOf() + val job = launch(testDispatcher) { underTest.isOpenMenuButtonVisible.toList(isVisible) } + + assertThat(isVisible.last()).isTrue() + job.cancel() + } + + @Test + fun `isOpenMenuButtonVisible - no actions - false`() = + testScope.runTest { + val userInfos = setUsers(2) + userRepository.setSelectedUserInfo(userInfos[1]) + keyguardRepository.setKeyguardShowing(true) + whenever(manager.canAddMoreUsers(any())).thenReturn(false) + + val isVisible = mutableListOf() + val job = launch(testDispatcher) { underTest.isOpenMenuButtonVisible.toList(isVisible) } + + assertThat(isVisible.last()).isFalse() + job.cancel() + } + + @Test + fun menu() = + testScope.runTest { + val isMenuVisible = mutableListOf() + val job = launch(testDispatcher) { underTest.isMenuVisible.toList(isMenuVisible) } + assertThat(isMenuVisible.last()).isFalse() + + underTest.onOpenMenuButtonClicked() + assertThat(isMenuVisible.last()).isTrue() + + underTest.onMenuClosed() + assertThat(isMenuVisible.last()).isFalse() + + job.cancel() + } + + @Test + fun `menu actions`() = + testScope.runTest { + setUsers(2) + val actions = mutableListOf>() + val job = launch(testDispatcher) { underTest.menu.toList(actions) } + + assertThat(actions.last().map { it.viewKey }) + .isEqualTo( + listOf( + UserActionModel.ENTER_GUEST_MODE.ordinal.toLong(), + UserActionModel.ADD_USER.ordinal.toLong(), + UserActionModel.ADD_SUPERVISED_USER.ordinal.toLong(), + UserActionModel.NAVIGATE_TO_USER_MANAGEMENT.ordinal.toLong(), + ) + ) + + job.cancel() + } + + @Test + fun `isFinishRequested - finishes when user is switched`() = + testScope.runTest { + val userInfos = setUsers(count = 2) + val isFinishRequested = mutableListOf() + val job = + launch(testDispatcher) { underTest.isFinishRequested.toList(isFinishRequested) } + assertThat(isFinishRequested.last()).isFalse() + + userRepository.setSelectedUserInfo(userInfos[1]) + + assertThat(isFinishRequested.last()).isTrue() + + job.cancel() + } + + @Test + fun `isFinishRequested - finishes when the screen turns off`() = + testScope.runTest { + setUsers(count = 2) + powerRepository.setInteractive(true) + val isFinishRequested = mutableListOf() + val job = + launch(testDispatcher) { underTest.isFinishRequested.toList(isFinishRequested) } + assertThat(isFinishRequested.last()).isFalse() + + powerRepository.setInteractive(false) + + assertThat(isFinishRequested.last()).isTrue() + + job.cancel() + } + + @Test + fun `isFinishRequested - finishes when cancel button is clicked`() = + testScope.runTest { + setUsers(count = 2) + powerRepository.setInteractive(true) + val isFinishRequested = mutableListOf() + val job = + launch(testDispatcher) { underTest.isFinishRequested.toList(isFinishRequested) } + assertThat(isFinishRequested.last()).isFalse() + + underTest.onCancelButtonClicked() + + assertThat(isFinishRequested.last()).isTrue() + + underTest.onFinished() + + assertThat(isFinishRequested.last()).isFalse() + + job.cancel() + } + + @Test + fun `guest selected -- name is exit guest`() = + testScope.runTest { + val userInfos = + listOf( + UserInfo( + /* id= */ 0, + /* name= */ "zero", + /* iconPath= */ "", + /* flags= */ UserInfo.FLAG_PRIMARY or + UserInfo.FLAG_ADMIN or + UserInfo.FLAG_FULL, + UserManager.USER_TYPE_FULL_SYSTEM, + ), + UserInfo( + /* id= */ 1, + /* name= */ "one", + /* iconPath= */ "", + /* flags= */ UserInfo.FLAG_FULL, + UserManager.USER_TYPE_FULL_GUEST, + ), + ) + + userRepository.setUserInfos(userInfos) + userRepository.setSelectedUserInfo(userInfos[1]) + + val userViewModels = mutableListOf>() + val job = launch(testDispatcher) { underTest.users.toList(userViewModels) } + + assertThat(userViewModels.last()).hasSize(2) + assertUserViewModel( + viewModel = userViewModels.last()[0], + viewKey = 0, + name = Text.Loaded("zero"), + isSelectionMarkerVisible = false, + ) + assertUserViewModel( + viewModel = userViewModels.last()[1], + viewKey = 1, + name = + Text.Resource( + com.android.settingslib.R.string.guest_exit_quick_settings_button + ), + isSelectionMarkerVisible = true, + ) + job.cancel() + } + + @Test + fun `guest not selected -- name is guest`() = + testScope.runTest { + val userInfos = + listOf( + UserInfo( + /* id= */ 0, + /* name= */ "zero", + /* iconPath= */ "", + /* flags= */ UserInfo.FLAG_PRIMARY or + UserInfo.FLAG_ADMIN or + UserInfo.FLAG_FULL, + UserManager.USER_TYPE_FULL_SYSTEM, + ), + UserInfo( + /* id= */ 1, + /* name= */ "one", + /* iconPath= */ "", + /* flags= */ UserInfo.FLAG_FULL, + UserManager.USER_TYPE_FULL_GUEST, + ), + ) + + userRepository.setUserInfos(userInfos) + userRepository.setSelectedUserInfo(userInfos[0]) + runCurrent() + + val userViewModels = mutableListOf>() + val job = launch(testDispatcher) { underTest.users.toList(userViewModels) } + + assertThat(userViewModels.last()).hasSize(2) + assertUserViewModel( + viewModel = userViewModels.last()[0], + viewKey = 0, + name = Text.Loaded("zero"), + isSelectionMarkerVisible = true, + ) + assertUserViewModel( + viewModel = userViewModels.last()[1], + viewKey = 1, + name = Text.Loaded("one"), + isSelectionMarkerVisible = false, + ) + job.cancel() + } private suspend fun setUsers(count: Int): List { val userInfos =