[DO NOT MERGE] 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:
@@ -1279,6 +1279,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>
|
||||
|
||||
|
||||
@@ -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
|
||||
@@ -958,8 +963,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; "
|
||||
@@ -1001,9 +1007,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) {
|
||||
@@ -1176,6 +1193,7 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable,
|
||||
ScreenOnCoordinator screenOnCoordinator,
|
||||
InteractionJankMonitor interactionJankMonitor,
|
||||
DreamOverlayStateController dreamOverlayStateController,
|
||||
FeatureFlags featureFlags,
|
||||
Lazy<ShadeController> shadeControllerLazy,
|
||||
Lazy<NotificationShadeWindowController> notificationShadeWindowControllerLazy,
|
||||
Lazy<ActivityLaunchAnimator> activityLaunchAnimator,
|
||||
@@ -1230,9 +1248,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() {
|
||||
@@ -1792,7 +1810,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);
|
||||
@@ -1825,6 +1842,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
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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).
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -144,6 +144,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;
|
||||
@@ -687,12 +689,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 */,
|
||||
@@ -711,13 +717,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;
|
||||
};
|
||||
|
||||
@@ -791,6 +809,8 @@ public final class NotificationPanelViewController implements Dumpable {
|
||||
KeyguardBottomAreaInteractor keyguardBottomAreaInteractor,
|
||||
DreamingToLockscreenTransitionViewModel dreamingToLockscreenTransitionViewModel,
|
||||
OccludedToLockscreenTransitionViewModel occludedToLockscreenTransitionViewModel,
|
||||
LockscreenToDreamingTransitionViewModel lockscreenToDreamingTransitionViewModel,
|
||||
LockscreenToOccludedTransitionViewModel lockscreenToOccludedTransitionViewModel,
|
||||
@Main CoroutineDispatcher mainDispatcher,
|
||||
KeyguardTransitionInteractor keyguardTransitionInteractor,
|
||||
DumpManager dumpManager) {
|
||||
@@ -810,6 +830,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
|
||||
@@ -1117,22 +1139,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);
|
||||
}
|
||||
}
|
||||
@@ -1173,6 +1217,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,
|
||||
@@ -1836,7 +1884,7 @@ public final class NotificationPanelViewController implements Dumpable {
|
||||
}
|
||||
|
||||
private void updateClock() {
|
||||
if (mIsToLockscreenTransitionRunning) {
|
||||
if (mIsOcclusionTransitionRunning) {
|
||||
return;
|
||||
}
|
||||
float alpha = mClockPositionResult.clockAlpha * mKeyguardOnlyContentAlpha;
|
||||
@@ -2727,7 +2775,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 {
|
||||
@@ -3596,7 +3644,7 @@ public final class NotificationPanelViewController implements Dumpable {
|
||||
}
|
||||
|
||||
private void updateNotificationTranslucency() {
|
||||
if (mIsToLockscreenTransitionRunning) {
|
||||
if (mIsOcclusionTransitionRunning) {
|
||||
return;
|
||||
}
|
||||
float alpha = 1f;
|
||||
@@ -3654,7 +3702,7 @@ public final class NotificationPanelViewController implements Dumpable {
|
||||
}
|
||||
|
||||
private void updateKeyguardBottomAreaAlpha() {
|
||||
if (mIsToLockscreenTransitionRunning) {
|
||||
if (mIsOcclusionTransitionRunning) {
|
||||
return;
|
||||
}
|
||||
// There are two possible panel expansion behaviors:
|
||||
@@ -5886,7 +5934,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);
|
||||
@@ -5904,7 +5952,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);
|
||||
|
||||
@@ -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;
|
||||
@@ -39,6 +41,9 @@ import com.android.systemui.dock.DockManager;
|
||||
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.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;
|
||||
@@ -57,6 +62,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;
|
||||
|
||||
@@ -96,6 +102,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(
|
||||
@@ -119,6 +132,7 @@ public class NotificationShadeWindowViewController {
|
||||
PulsingGestureListener pulsingGestureListener,
|
||||
FeatureFlags featureFlags,
|
||||
KeyguardBouncerViewModel keyguardBouncerViewModel,
|
||||
KeyguardTransitionInteractor keyguardTransitionInteractor,
|
||||
KeyguardBouncerComponent.Factory keyguardBouncerComponentFactory
|
||||
) {
|
||||
mLockscreenShadeTransitionController = transitionController;
|
||||
@@ -148,6 +162,11 @@ public class NotificationShadeWindowViewController {
|
||||
keyguardBouncerViewModel,
|
||||
keyguardBouncerComponentFactory);
|
||||
}
|
||||
|
||||
if (featureFlags.isEnabled(Flags.UNOCCLUSION_TRANSITION)) {
|
||||
collectFlow(mView, keyguardTransitionInteractor.getLockscreenToDreamingTransition(),
|
||||
mLockscreenToDreamingTransition);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -215,6 +234,10 @@ public class NotificationShadeWindowViewController {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (mIsOcclusionTransitionRunning) {
|
||||
return false;
|
||||
}
|
||||
|
||||
mFalsingCollector.onTouchEvent(ev);
|
||||
mPulsingWakeupGestureHandler.onTouchEvent(ev);
|
||||
mStatusBarKeyguardViewManager.onTouch(ev);
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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;
|
||||
@@ -135,6 +136,7 @@ public class KeyguardViewMediatorTest extends SysuiTestCase {
|
||||
private @Mock AuthController mAuthController;
|
||||
private @Mock ShadeExpansionStateManager mShadeExpansionStateManager;
|
||||
private @Mock ShadeWindowLogger mShadeWindowLogger;
|
||||
private @Mock FeatureFlags mFeatureFlags;
|
||||
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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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) }
|
||||
}
|
||||
}
|
||||
@@ -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(
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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),
|
||||
)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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"
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -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"
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -107,6 +107,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;
|
||||
@@ -293,6 +295,9 @@ public class NotificationPanelViewControllerTest extends SysuiTestCase {
|
||||
@Mock private KeyguardBottomAreaInteractor mKeyguardBottomAreaInteractor;
|
||||
@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;
|
||||
@@ -513,6 +518,8 @@ public class NotificationPanelViewControllerTest extends SysuiTestCase {
|
||||
mKeyguardBottomAreaInteractor,
|
||||
mDreamingToLockscreenTransitionViewModel,
|
||||
mOccludedToLockscreenTransitionViewModel,
|
||||
mLockscreenToDreamingTransitionViewModel,
|
||||
mLockscreenToOccludedTransitionViewModel,
|
||||
mMainDispatcher,
|
||||
mKeyguardTransitionInteractor,
|
||||
mDumpManager);
|
||||
|
||||
@@ -30,6 +30,7 @@ import com.android.systemui.classifier.FalsingCollectorFake
|
||||
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.KeyguardTransitionInteractor
|
||||
import com.android.systemui.keyguard.ui.viewmodel.KeyguardBouncerViewModel
|
||||
import com.android.systemui.shade.NotificationShadeWindowView.InteractionEventHandler
|
||||
import com.android.systemui.statusbar.LockscreenShadeTransitionController
|
||||
@@ -101,6 +102,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
|
||||
@@ -132,7 +134,8 @@ class NotificationShadeWindowViewControllerTest : SysuiTestCase() {
|
||||
pulsingGestureListener,
|
||||
featureFlags,
|
||||
keyguardBouncerViewModel,
|
||||
keyguardBouncerComponentFactory
|
||||
keyguardTransitionInteractor,
|
||||
keyguardBouncerComponentFactory,
|
||||
)
|
||||
underTest.setupExpandedStatusBar()
|
||||
|
||||
|
||||
@@ -40,6 +40,7 @@ import com.android.systemui.classifier.FalsingCollectorFake;
|
||||
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.KeyguardTransitionInteractor;
|
||||
import com.android.systemui.keyguard.ui.viewmodel.KeyguardBouncerViewModel;
|
||||
import com.android.systemui.statusbar.DragDownHelper;
|
||||
import com.android.systemui.statusbar.LockscreenShadeTransitionController;
|
||||
@@ -93,6 +94,7 @@ public class NotificationShadeWindowViewTest extends SysuiTestCase {
|
||||
@Mock private KeyguardBouncerViewModel mKeyguardBouncerViewModel;
|
||||
@Mock private KeyguardBouncerComponent.Factory mKeyguardBouncerComponentFactory;
|
||||
@Mock private NotificationInsetsController mNotificationInsetsController;
|
||||
@Mock private KeyguardTransitionInteractor mKeyguardTransitionInteractor;
|
||||
|
||||
@Captor private ArgumentCaptor<NotificationShadeWindowView.InteractionEventHandler>
|
||||
mInteractionEventHandlerCaptor;
|
||||
@@ -132,6 +134,7 @@ public class NotificationShadeWindowViewTest extends SysuiTestCase {
|
||||
mPulsingGestureListener,
|
||||
mFeatureFlags,
|
||||
mKeyguardBouncerViewModel,
|
||||
mKeyguardTransitionInteractor,
|
||||
mKeyguardBouncerComponentFactory
|
||||
);
|
||||
mController.setupExpandedStatusBar();
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -42,6 +42,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
|
||||
@@ -90,6 +91,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
|
||||
|
||||
@@ -132,6 +134,7 @@ class UserInteractorTest : SysuiTestCase() {
|
||||
keyguardInteractor =
|
||||
KeyguardInteractor(
|
||||
repository = keyguardRepository,
|
||||
commandQueue = commandQueue,
|
||||
),
|
||||
manager = manager,
|
||||
applicationScope = testCoroutineScope,
|
||||
|
||||
@@ -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) },
|
||||
|
||||
@@ -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 {
|
||||
@@ -169,273 +172,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 =
|
||||
|
||||
Reference in New Issue
Block a user