[DO NOT MERGE] Transitions: LOCKSCREEN->DREAMING am: d991f01a22
Original change: https://googleplex-android-review.googlesource.com/c/platform/frameworks/base/+/20835498 Change-Id: I5e0de8a6e93bf5fe6b4a20a39fdd52834afe898b Signed-off-by: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
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) {
|
||||
|
||||
@@ -43,6 +43,7 @@ import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
|
||||
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
|
||||
import com.android.systemui.plugins.ActivityStarter
|
||||
import com.android.systemui.qs.user.UserSwitchDialogController
|
||||
import com.android.systemui.statusbar.CommandQueue
|
||||
import com.android.systemui.statusbar.policy.DeviceProvisionedController
|
||||
import com.android.systemui.telephony.data.repository.FakeTelephonyRepository
|
||||
import com.android.systemui.telephony.domain.interactor.TelephonyInteractor
|
||||
@@ -93,6 +94,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
|
||||
|
||||
@@ -136,6 +138,7 @@ class UserInteractorTest : SysuiTestCase() {
|
||||
keyguardInteractor =
|
||||
KeyguardInteractor(
|
||||
repository = keyguardRepository,
|
||||
commandQueue = commandQueue,
|
||||
),
|
||||
manager = manager,
|
||||
applicationScope = testScope.backgroundScope,
|
||||
|
||||
@@ -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