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
This commit is contained in:
Matt Pietal
2022-12-23 19:33:15 +00:00
parent 123d9f6360
commit 455996f7b5
31 changed files with 1097 additions and 308 deletions

View File

@@ -1293,6 +1293,12 @@
<!-- OCCLUDED -> LOCKSCREEN transition: Amount to shift lockscreen content on entering -->
<dimen name="occluded_to_lockscreen_transition_lockscreen_translation_y">40dp</dimen>
<!-- LOCKSCREEN -> DREAMING transition: Amount to shift lockscreen content on entering -->
<dimen name="lockscreen_to_dreaming_transition_lockscreen_translation_y">-40dp</dimen>
<!-- LOCKSCREEN -> OCCLUDED transition: Amount to shift lockscreen content on entering -->
<dimen name="lockscreen_to_occluded_transition_lockscreen_translation_y">-40dp</dimen>
<!-- The amount of vertical offset for the keyguard during the full shade transition. -->
<dimen name="lockscreen_shade_keyguard_transition_vertical_offset">0dp</dimen>

View File

@@ -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<ShadeController> shadeControllerLazy,
Lazy<NotificationShadeWindowController> notificationShadeWindowControllerLazy,
Lazy<ActivityLaunchAnimator> 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

View File

@@ -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> shadeController,
Lazy<NotificationShadeWindowController> notificationShadeWindowController,
Lazy<ActivityLaunchAnimator> activityLaunchAnimator,
@@ -142,6 +144,7 @@ public class KeyguardModule {
screenOnCoordinator,
interactionJankMonitor,
dreamOverlayStateController,
featureFlags,
shadeController,
notificationShadeWindowController,
activityLaunchAnimator,

View File

@@ -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

View File

@@ -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
}
}

View File

@@ -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<Boolean> = repository.isDreaming
/** Whether the system is dreaming with an overlay active */
val isDreamingWithOverlay: Flow<Boolean> = repository.isDreamingWithOverlay
/** Event for when the camera gesture is detected */
val onCameraLaunchDetected: Flow<CameraLaunchSourceModel> = 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"
}
}

View File

@@ -42,24 +42,32 @@ class KeyguardTransitionInteractor
constructor(
repository: KeyguardTransitionRepository,
) {
/** (any)->AOD transition information */
val anyStateToAodTransition: Flow<TransitionStep> =
repository.transitions.filter { step -> step.to == KeyguardState.AOD }
/** AOD->LOCKSCREEN transition information. */
val aodToLockscreenTransition: Flow<TransitionStep> = repository.transition(AOD, LOCKSCREEN)
/** LOCKSCREEN->AOD transition information. */
val lockscreenToAodTransition: Flow<TransitionStep> = repository.transition(LOCKSCREEN, AOD)
/** DREAMING->LOCKSCREEN transition information. */
val dreamingToLockscreenTransition: Flow<TransitionStep> =
repository.transition(DREAMING, LOCKSCREEN)
/** LOCKSCREEN->AOD transition information. */
val lockscreenToAodTransition: Flow<TransitionStep> = repository.transition(LOCKSCREEN, AOD)
/** LOCKSCREEN->DREAMING transition information. */
val lockscreenToDreamingTransition: Flow<TransitionStep> =
repository.transition(LOCKSCREEN, DREAMING)
/** LOCKSCREEN->OCCLUDED transition information. */
val lockscreenToOccludedTransition: Flow<TransitionStep> =
repository.transition(LOCKSCREEN, OCCLUDED)
/** OCCLUDED->LOCKSCREEN transition information. */
val occludedToLockscreenTransition: Flow<TransitionStep> =
repository.transition(OCCLUDED, LOCKSCREEN)
/** (any)->AOD transition information */
val anyStateToAodTransition: Flow<TransitionStep> =
repository.transitions.filter { step -> step.to == KeyguardState.AOD }
/**
* AOD<->LOCKSCREEN transition information, mapped to dozeAmount range of AOD (1f) <->
* Lockscreen (0f).

View File

@@ -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,
}

View File

@@ -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<Float> {
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<Float> = flowForAnimation(LOCKSCREEN_ALPHA).map { 1f - it }
private fun flowForAnimation(params: AnimationParams): Flow<Float> {
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)
}
}

View File

@@ -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<Float> {
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<Float> = flowForAnimation(LOCKSCREEN_ALPHA).map { 1f - it }
private fun flowForAnimation(params: AnimationParams): Flow<Float> {
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)
}
}

View File

@@ -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<TransitionStep> mDreamingToLockscreenTransition =
(TransitionStep step) -> {
mIsToLockscreenTransitionRunning =
mIsOcclusionTransitionRunning =
step.getTransitionState() == TransitionState.RUNNING;
};
private final Consumer<TransitionStep> mOccludedToLockscreenTransition =
(TransitionStep step) -> {
mIsToLockscreenTransitionRunning =
mIsOcclusionTransitionRunning =
step.getTransitionState() == TransitionState.RUNNING;
};
private final Consumer<TransitionStep> mLockscreenToDreamingTransition =
(TransitionStep step) -> {
mIsOcclusionTransitionRunning =
step.getTransitionState() == TransitionState.RUNNING;
};
private final Consumer<TransitionStep> 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<Float> toLockscreenTransitionAlpha(
private Consumer<Float> setTransitionAlpha(
NotificationStackScrollLayoutController stackScroller) {
return (Float alpha) -> {
mKeyguardStatusViewController.setAlpha(alpha);
@@ -5908,7 +5956,7 @@ public final class NotificationPanelViewController implements Dumpable {
};
}
private Consumer<Float> toLockscreenTransitionY(
private Consumer<Float> setTransitionY(
NotificationStackScrollLayoutController stackScroller) {
return (Float translationY) -> {
mKeyguardStatusViewController.setTranslationY(translationY, /* excludeMedia= */false);

View File

@@ -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<TransitionStep> 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);

View File

@@ -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,

View File

@@ -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
);
}

View File

@@ -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,

View File

@@ -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,

View File

@@ -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,

View File

@@ -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<CommandQueue.Callbacks>()
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) }
}
}

View File

@@ -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(

View File

@@ -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(

View File

@@ -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),
)

View File

@@ -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)

View File

@@ -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<Float>()
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<Float>()
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"
)
}
}

View File

@@ -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<Float>()
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<Float>()
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"
)
}
}

View File

@@ -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);

View File

@@ -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<InteractionEventHandler>
private lateinit var interactionEventHandler: InteractionEventHandler
@@ -136,7 +138,8 @@ class NotificationShadeWindowViewControllerTest : SysuiTestCase() {
featureFlags,
keyguardBouncerViewModel,
keyguardBouncerComponentFactory,
alternateBouncerInteractor
alternateBouncerInteractor,
keyguardTransitionInteractor,
)
underTest.setupExpandedStatusBar()

View File

@@ -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<NotificationShadeWindowView.InteractionEventHandler>
mInteractionEventHandlerCaptor;
@@ -135,7 +137,8 @@ public class NotificationShadeWindowViewTest extends SysuiTestCase {
mFeatureFlags,
mKeyguardBouncerViewModel,
mKeyguardBouncerComponentFactory,
mAlternateBouncerInteractor
mAlternateBouncerInteractor,
mKeyguardTransitionInteractor
);
mController.setupExpandedStatusBar();
mController.setDragDownHelper(mDragDownHelper);

View File

@@ -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<FoldStateListener>
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) {

View File

@@ -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,

View File

@@ -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) },

View File

@@ -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<List<UserViewModel>>()
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<Int>()
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<Int>()
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<Boolean>()
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<Boolean>()
val job = launch(testDispatcher) { underTest.isOpenMenuButtonVisible.toList(isVisible) }
assertThat(isVisible.last()).isFalse()
job.cancel()
}
@Test
fun menu() = testScope.runTest {
val isMenuVisible = mutableListOf<Boolean>()
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<List<UserActionViewModel>>()
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<List<UserViewModel>>()
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<Boolean>()
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<Boolean>()
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<Boolean>()
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<List<UserViewModel>>()
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<List<UserViewModel>>()
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<Int>()
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<Int>()
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<Boolean>()
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<Boolean>()
val job = launch(testDispatcher) { underTest.isOpenMenuButtonVisible.toList(isVisible) }
assertThat(isVisible.last()).isFalse()
job.cancel()
}
@Test
fun menu() =
testScope.runTest {
val isMenuVisible = mutableListOf<Boolean>()
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<List<UserActionViewModel>>()
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<Boolean>()
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<Boolean>()
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<Boolean>()
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<List<UserViewModel>>()
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<List<UserViewModel>>()
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<UserInfo> {
val userInfos =