diff options
25 files changed, 597 insertions, 227 deletions
diff --git a/packages/SystemUI/res-keyguard/values/dimens.xml b/packages/SystemUI/res-keyguard/values/dimens.xml index 6cc5b9d7b7e8..992d143ff66d 100644 --- a/packages/SystemUI/res-keyguard/values/dimens.xml +++ b/packages/SystemUI/res-keyguard/values/dimens.xml @@ -78,7 +78,7 @@ <!-- The vertical margin between the date and the owner info. --> <!-- The translation for disappearing security views after having solved them. --> - <dimen name="disappear_y_translation">-32dp</dimen> + <dimen name="disappear_y_translation">-50dp</dimen> <!-- Dimens for animation for the Bouncer PIN view --> <dimen name="pin_view_trans_y_entry">120dp</dimen> diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java index 29496169e04f..66d5d097ab04 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java @@ -39,7 +39,6 @@ import static java.lang.Integer.max; import android.animation.Animator; import android.animation.AnimatorListenerAdapter; -import android.animation.AnimatorSet; import android.animation.ObjectAnimator; import android.animation.ValueAnimator; import android.app.Activity; @@ -1068,13 +1067,10 @@ public class KeyguardSecurityContainer extends ConstraintLayout { int yTranslation = mResources.getDimensionPixelSize(R.dimen.disappear_y_translation); - AnimatorSet anims = new AnimatorSet(); ObjectAnimator yAnim = ObjectAnimator.ofFloat(mView, View.TRANSLATION_Y, yTranslation); - ObjectAnimator alphaAnim = ObjectAnimator.ofFloat(mView, View.ALPHA, 0f); - - anims.setInterpolator(Interpolators.STANDARD_ACCELERATE); - anims.playTogether(alphaAnim, yAnim); - anims.start(); + yAnim.setInterpolator(Interpolators.STANDARD_ACCELERATE); + yAnim.setDuration(500); + yAnim.start(); } private void setupUserSwitcher() { diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java index b8bb2603fa03..c32b8530d589 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java @@ -636,12 +636,17 @@ public class KeyguardSecurityContainerController extends ViewController<Keyguard public void startAppearAnimation() { if (mCurrentSecurityMode != SecurityMode.None) { - mView.setAlpha(1f); + setAlpha(1f); mView.startAppearAnimation(mCurrentSecurityMode); getCurrentSecurityController().startAppearAnimation(); } } + /** Set the alpha of the security container view */ + public void setAlpha(float alpha) { + mView.setAlpha(alpha); + } + public boolean startDisappearAnimation(Runnable onFinishRunnable) { boolean didRunAnimation = false; diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt index a3b3d0fd0681..76f20d25b0ec 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt @@ -80,6 +80,9 @@ interface KeyguardRepository { */ val isKeyguardShowing: Flow<Boolean> + /** Is the keyguard in a unlocked state? */ + val isKeyguardUnlocked: Flow<Boolean> + /** Is an activity showing over the keyguard? */ val isKeyguardOccluded: Flow<Boolean> @@ -278,6 +281,31 @@ constructor( } .distinctUntilChanged() + override val isKeyguardUnlocked: Flow<Boolean> = + conflatedCallbackFlow { + val callback = + object : KeyguardStateController.Callback { + override fun onUnlockedChanged() { + trySendWithFailureLogging( + keyguardStateController.isUnlocked, + TAG, + "updated isKeyguardUnlocked" + ) + } + } + + keyguardStateController.addCallback(callback) + // Adding the callback does not send an initial update. + trySendWithFailureLogging( + keyguardStateController.isUnlocked, + TAG, + "initial isKeyguardUnlocked" + ) + + awaitClose { keyguardStateController.removeCallback(callback) } + } + .distinctUntilChanged() + override val isKeyguardGoingAway: Flow<Boolean> = conflatedCallbackFlow { val callback = object : KeyguardStateController.Callback { diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepository.kt index 0c4bca616e12..100bc596103d 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepository.kt @@ -68,8 +68,11 @@ interface KeyguardTransitionRepository { /** * Begin a transition from one state to another. Transitions are interruptible, and will issue a * [TransitionStep] with state = [TransitionState.CANCELED] before beginning the next one. + * + * When canceled, there are two options: to continue from the current position of the prior + * transition, or to reset the position. When [resetIfCanceled] == true, it will do the latter. */ - fun startTransition(info: TransitionInfo): UUID? + fun startTransition(info: TransitionInfo, resetIfCanceled: Boolean = false): UUID? /** * Allows manual control of a transition. When calling [startTransition], the consumer must pass @@ -130,7 +133,10 @@ class KeyguardTransitionRepositoryImpl @Inject constructor() : KeyguardTransitio ) } - override fun startTransition(info: TransitionInfo): UUID? { + override fun startTransition( + info: TransitionInfo, + resetIfCanceled: Boolean, + ): UUID? { if (lastStep.from == info.from && lastStep.to == info.to) { Log.i(TAG, "Duplicate call to start the transition, rejecting: $info") return null @@ -138,7 +144,11 @@ class KeyguardTransitionRepositoryImpl @Inject constructor() : KeyguardTransitio val startingValue = if (lastStep.transitionState != TransitionState.FINISHED) { Log.i(TAG, "Transition still active: $lastStep, canceling") - lastStep.value + if (resetIfCanceled) { + 0f + } else { + lastStep.value + } } else { 0f } @@ -227,10 +237,7 @@ class KeyguardTransitionRepositoryImpl @Inject constructor() : KeyguardTransitio } private fun trace(step: TransitionStep, isManual: Boolean) { - if ( - step.transitionState != TransitionState.STARTED && - step.transitionState != TransitionState.FINISHED - ) { + if (step.transitionState == TransitionState.RUNNING) { return } val traceName = @@ -243,7 +250,10 @@ class KeyguardTransitionRepositoryImpl @Inject constructor() : KeyguardTransitio val traceCookie = traceName.hashCode() if (step.transitionState == TransitionState.STARTED) { Trace.beginAsyncSection(traceName, traceCookie) - } else if (step.transitionState == TransitionState.FINISHED) { + } else if ( + step.transitionState == TransitionState.FINISHED || + step.transitionState == TransitionState.CANCELED + ) { Trace.endAsyncSection(traceName, traceCookie) } } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractor.kt index 8715d1f55069..3beac0b1322e 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractor.kt @@ -34,7 +34,6 @@ import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.delay import kotlinx.coroutines.flow.collect import kotlinx.coroutines.flow.combine -import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.launch @@ -57,14 +56,7 @@ constructor( private fun listenForDreamingToLockscreen() { scope.launch { - // Dependending on the dream, either dream state or occluded change will change first, - // so listen for both - combine(keyguardInteractor.isAbleToDream, keyguardInteractor.isKeyguardOccluded) { - isAbleToDream, - isKeyguardOccluded -> - isAbleToDream && isKeyguardOccluded - } - .distinctUntilChanged() + keyguardInteractor.isAbleToDream .sample( combine( keyguardInteractor.dozeTransitionModel, diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt index d01f48970c97..911861ddde47 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt @@ -130,55 +130,59 @@ constructor( shadeRepository.shadeModel .sample( combine( - keyguardTransitionInteractor.finishedKeyguardState, + keyguardTransitionInteractor.startedKeyguardTransitionStep, keyguardInteractor.statusBarState, - ::Pair + keyguardInteractor.isKeyguardUnlocked, + ::toTriple ), - ::toTriple + ::toQuad ) - .collect { (shadeModel, keyguardState, statusBarState) -> + .collect { (shadeModel, keyguardState, statusBarState, isKeyguardUnlocked) -> val id = transitionId if (id != null) { - // An existing `id` means a transition is started, and calls to - // `updateTransition` will control it until FINISHED or CANCELED - var nextState = - if (shadeModel.expansionAmount == 0f) { - TransitionState.FINISHED - } else if (shadeModel.expansionAmount == 1f) { - TransitionState.CANCELED - } else { - TransitionState.RUNNING - } - keyguardTransitionRepository.updateTransition( - id, - 1f - shadeModel.expansionAmount, - nextState, - ) + if (keyguardState.to == KeyguardState.PRIMARY_BOUNCER) { + // An existing `id` means a transition is started, and calls to + // `updateTransition` will control it until FINISHED or CANCELED + var nextState = + if (shadeModel.expansionAmount == 0f) { + TransitionState.FINISHED + } else if (shadeModel.expansionAmount == 1f) { + TransitionState.CANCELED + } else { + TransitionState.RUNNING + } + keyguardTransitionRepository.updateTransition( + id, + 1f - shadeModel.expansionAmount, + nextState, + ) - if ( - nextState == TransitionState.CANCELED || - nextState == TransitionState.FINISHED - ) { - transitionId = null - } + if ( + nextState == TransitionState.CANCELED || + nextState == TransitionState.FINISHED + ) { + transitionId = null + } - // If canceled, just put the state back - if (nextState == TransitionState.CANCELED) { - keyguardTransitionRepository.startTransition( - TransitionInfo( - ownerName = name, - from = KeyguardState.PRIMARY_BOUNCER, - to = KeyguardState.LOCKSCREEN, - animator = getAnimator(0.milliseconds) + // If canceled, just put the state back + if (nextState == TransitionState.CANCELED) { + keyguardTransitionRepository.startTransition( + TransitionInfo( + ownerName = name, + from = KeyguardState.PRIMARY_BOUNCER, + to = KeyguardState.LOCKSCREEN, + animator = getAnimator(0.milliseconds) + ) ) - ) + } } } else { // TODO (b/251849525): Remove statusbarstate check when that state is // integrated into KeyguardTransitionRepository if ( - keyguardState == KeyguardState.LOCKSCREEN && + keyguardState.to == KeyguardState.LOCKSCREEN && shadeModel.isUserDragging && + !isKeyguardUnlocked && statusBarState == KEYGUARD ) { transitionId = diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractor.kt index b59b413d7a40..94961cbf4240 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractor.kt @@ -17,6 +17,9 @@ package com.android.systemui.keyguard.domain.interactor import android.animation.ValueAnimator +import com.android.keyguard.KeyguardSecurityModel +import com.android.keyguard.KeyguardSecurityModel.SecurityMode.Password +import com.android.keyguard.KeyguardUpdateMonitor import com.android.systemui.animation.Interpolators import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application @@ -26,6 +29,8 @@ import com.android.systemui.keyguard.shared.model.TransitionInfo import com.android.systemui.keyguard.shared.model.WakefulnessState import com.android.systemui.util.kotlin.sample import javax.inject.Inject +import kotlin.time.Duration +import kotlin.time.Duration.Companion.milliseconds import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.combine import kotlinx.coroutines.launch @@ -37,7 +42,8 @@ constructor( @Application private val scope: CoroutineScope, private val keyguardInteractor: KeyguardInteractor, private val keyguardTransitionRepository: KeyguardTransitionRepository, - private val keyguardTransitionInteractor: KeyguardTransitionInteractor + private val keyguardTransitionInteractor: KeyguardTransitionInteractor, + private val keyguardSecurityModel: KeyguardSecurityModel, ) : TransitionInteractor(FromPrimaryBouncerTransitionInteractor::class.simpleName!!) { override fun start() { @@ -93,31 +99,47 @@ constructor( private fun listenForPrimaryBouncerToGone() { scope.launch { keyguardInteractor.isKeyguardGoingAway - .sample(keyguardTransitionInteractor.finishedKeyguardState) { a, b -> Pair(a, b) } - .collect { pair -> - val (isKeyguardGoingAway, keyguardState) = pair - if (isKeyguardGoingAway && keyguardState == KeyguardState.PRIMARY_BOUNCER) { + .sample(keyguardTransitionInteractor.startedKeyguardTransitionStep, ::Pair) + .collect { (isKeyguardGoingAway, lastStartedTransitionStep) -> + if ( + isKeyguardGoingAway && + lastStartedTransitionStep.to == KeyguardState.PRIMARY_BOUNCER + ) { + val securityMode = + keyguardSecurityModel.getSecurityMode( + KeyguardUpdateMonitor.getCurrentUser() + ) + // IME for password requires a slightly faster animation + val duration = + if (securityMode == Password) { + TO_GONE_SHORT_DURATION + } else { + TO_GONE_DURATION + } keyguardTransitionRepository.startTransition( TransitionInfo( ownerName = name, from = KeyguardState.PRIMARY_BOUNCER, to = KeyguardState.GONE, - animator = getAnimator(), - ) + animator = getAnimator(duration), + ), + resetIfCanceled = true, ) } } } } - 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 = 300L + private val DEFAULT_DURATION = 300.milliseconds + val TO_GONE_DURATION = 250.milliseconds + val TO_GONE_SHORT_DURATION = 200.milliseconds } } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt index d25aff0add86..ec99049b42e3 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt @@ -33,7 +33,9 @@ import com.android.systemui.keyguard.shared.model.DozeStateModel.Companion.isDoz 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.keyguard.shared.model.WakefulnessModel.Companion.isWakingOrStartingToWake import com.android.systemui.statusbar.CommandQueue +import com.android.systemui.util.kotlin.sample import javax.inject.Inject import kotlinx.coroutines.channels.awaitClose import kotlinx.coroutines.delay @@ -95,6 +97,9 @@ constructor( awaitClose { commandQueue.removeCallback(callback) } } + /** The device wake/sleep state */ + val wakefulnessModel: Flow<WakefulnessModel> = repository.wakefulness + /** * Dozing and dreaming have overlapping events. If the doze state remains in FINISH, it means * that doze mode is not running and DREAMING is ok to commence. @@ -109,6 +114,12 @@ constructor( isDreaming && isDozeOff(dozeTransitionModel.to) } ) + .sample( + wakefulnessModel, + { isAbleToDream, wakefulnessModel -> + isAbleToDream && isWakingOrStartingToWake(wakefulnessModel) + } + ) .flatMapLatest { isAbleToDream -> flow { delay(50) @@ -119,6 +130,8 @@ constructor( /** Whether the keyguard is showing or not. */ val isKeyguardShowing: Flow<Boolean> = repository.isKeyguardShowing + /** Whether the keyguard is unlocked or not. */ + val isKeyguardUnlocked: Flow<Boolean> = repository.isKeyguardUnlocked /** Whether the keyguard is occluded (covered by an activity). */ val isKeyguardOccluded: Flow<Boolean> = repository.isKeyguardOccluded /** Whether the keyguard is going away. */ @@ -127,8 +140,6 @@ constructor( val primaryBouncerShowing: Flow<Boolean> = bouncerRepository.primaryBouncerVisible /** Whether the alternate bouncer is showing or not. */ val alternateBouncerShowing: Flow<Boolean> = bouncerRepository.alternateBouncerVisible - /** The device wake/sleep state */ - val wakefulnessModel: Flow<WakefulnessModel> = repository.wakefulness /** Observable for the [StatusBarState] */ val statusBarState: Flow<StatusBarState> = repository.statusBarState /** diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionAuditLogger.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionAuditLogger.kt index 51b02779a89f..e650b9fc0e47 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionAuditLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionAuditLogger.kt @@ -61,7 +61,15 @@ constructor( } scope.launch { - keyguardInteractor.isDreaming.collect { logger.log(TAG, VERBOSE, "isDreaming", it) } + keyguardInteractor.isAbleToDream.collect { + logger.log(TAG, VERBOSE, "isAbleToDream", it) + } + } + + scope.launch { + keyguardInteractor.isKeyguardOccluded.collect { + logger.log(TAG, VERBOSE, "isOccluded", it) + } } scope.launch { diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt index 1b7da5b65a03..3c0ec350c5c5 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt @@ -78,6 +78,10 @@ constructor( val occludedToLockscreenTransition: Flow<TransitionStep> = repository.transition(OCCLUDED, LOCKSCREEN) + /** PRIMARY_BOUNCER->GONE transition information. */ + val primaryBouncerToGoneTransition: Flow<TransitionStep> = + repository.transition(PRIMARY_BOUNCER, GONE) + /** * AOD<->LOCKSCREEN transition information, mapped to dozeAmount range of AOD (1f) <-> * Lockscreen (0f). diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/KeyguardTransitionAnimationFlow.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/KeyguardTransitionAnimationFlow.kt index ca1e27c9d19c..38b9d508f81c 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/KeyguardTransitionAnimationFlow.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/KeyguardTransitionAnimationFlow.kt @@ -47,6 +47,7 @@ class KeyguardTransitionAnimationFlow( duration: Duration, onStep: (Float) -> Float, startTime: Duration = 0.milliseconds, + onStart: (() -> Unit)? = null, onCancel: (() -> Float)? = null, onFinish: (() -> Float)? = null, interpolator: Interpolator = LINEAR, @@ -73,6 +74,7 @@ class KeyguardTransitionAnimationFlow( // the ViewModels of the last update STARTED -> { isComplete = false + onStart?.invoke() max(0f, min(1f, value)) } // Always send a final value of 1. Because of rounding, [value] may never be diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBouncerViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBouncerViewBinder.kt index 7db567b2a0e9..2337ffc35fa6 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBouncerViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBouncerViewBinder.kt @@ -31,6 +31,7 @@ import com.android.settingslib.Utils import com.android.systemui.keyguard.data.BouncerViewDelegate import com.android.systemui.keyguard.shared.constants.KeyguardBouncerConstants.EXPANSION_VISIBLE import com.android.systemui.keyguard.ui.viewmodel.KeyguardBouncerViewModel +import com.android.systemui.keyguard.ui.viewmodel.PrimaryBouncerToGoneTransitionViewModel import com.android.systemui.lifecycle.repeatWhenAttached import com.android.systemui.plugins.ActivityStarter import kotlinx.coroutines.awaitCancellation @@ -44,6 +45,7 @@ object KeyguardBouncerViewBinder { fun bind( view: ViewGroup, viewModel: KeyguardBouncerViewModel, + primaryBouncerToGoneTransitionViewModel: PrimaryBouncerToGoneTransitionViewModel, componentFactory: KeyguardBouncerComponent.Factory ) { // Builds the KeyguardSecurityContainerController from bouncer view group. @@ -145,6 +147,12 @@ object KeyguardBouncerViewBinder { } launch { + primaryBouncerToGoneTransitionViewModel.bouncerAlpha.collect { alpha -> + securityContainerController.setAlpha(alpha) + } + } + + launch { viewModel.bouncerExpansionAmount .filter { it == EXPANSION_VISIBLE } .collect { diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGoneTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGoneTransitionViewModel.kt new file mode 100644 index 000000000000..92038e24edf3 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGoneTransitionViewModel.kt @@ -0,0 +1,69 @@ +/* + * 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.FromPrimaryBouncerTransitionInteractor.Companion.TO_GONE_DURATION +import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor +import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow +import com.android.systemui.statusbar.SysuiStatusBarStateController +import javax.inject.Inject +import kotlin.time.Duration.Companion.milliseconds +import kotlinx.coroutines.flow.Flow + +/** + * Breaks down PRIMARY_BOUNCER->GONE transition into discrete steps for corresponding views to + * consume. + */ +@SysUISingleton +class PrimaryBouncerToGoneTransitionViewModel +@Inject +constructor( + private val interactor: KeyguardTransitionInteractor, + private val statusBarStateController: SysuiStatusBarStateController, +) { + private val transitionAnimation = + KeyguardTransitionAnimationFlow( + transitionDuration = TO_GONE_DURATION, + transitionFlow = interactor.primaryBouncerToGoneTransition, + ) + + private var leaveShadeOpen: Boolean = false + + /** Bouncer container alpha */ + val bouncerAlpha: Flow<Float> = + transitionAnimation.createFlow( + duration = 200.milliseconds, + onStep = { 1f - it }, + ) + + /** Scrim behind alpha */ + val scrimBehindAlpha: Flow<Float> = + transitionAnimation.createFlow( + duration = TO_GONE_DURATION, + interpolator = EMPHASIZED_ACCELERATE, + onStart = { leaveShadeOpen = statusBarStateController.leaveOpenOnKeyguardHide() }, + onStep = { + if (leaveShadeOpen) { + 1f + } else { + 1f - it + } + }, + ) +} diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java index 87350b465895..c130b3913b64 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java +++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java @@ -45,6 +45,7 @@ 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.keyguard.ui.viewmodel.PrimaryBouncerToGoneTransitionViewModel; import com.android.systemui.statusbar.DragDownHelper; import com.android.systemui.statusbar.LockscreenShadeTransitionController; import com.android.systemui.statusbar.NotificationInsetsController; @@ -133,7 +134,8 @@ public class NotificationShadeWindowViewController { KeyguardBouncerViewModel keyguardBouncerViewModel, KeyguardBouncerComponent.Factory keyguardBouncerComponentFactory, AlternateBouncerInteractor alternateBouncerInteractor, - KeyguardTransitionInteractor keyguardTransitionInteractor + KeyguardTransitionInteractor keyguardTransitionInteractor, + PrimaryBouncerToGoneTransitionViewModel primaryBouncerToGoneTransitionViewModel ) { mLockscreenShadeTransitionController = transitionController; mFalsingCollector = falsingCollector; @@ -160,6 +162,7 @@ public class NotificationShadeWindowViewController { KeyguardBouncerViewBinder.bind( mView.findViewById(R.id.keyguard_bouncer_container), keyguardBouncerViewModel, + primaryBouncerToGoneTransitionViewModel, keyguardBouncerComponentFactory); collectFlow(mView, keyguardTransitionInteractor.getLockscreenToDreamingTransition(), diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java index be791f9e2242..b5d51ce2b9ee 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java @@ -3695,6 +3695,12 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { @Override public void notifyBiometricAuthModeChanged() { mDozeServiceHost.updateDozing(); + if (mBiometricUnlockController.getMode() + == BiometricUnlockController.MODE_DISMISS_BOUNCER) { + // Don't update the scrim controller at this time, in favor of the transition repository + // updating the scrim + return; + } updateScrimController(); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java index 8e0ec284c840..8fd967594198 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java @@ -16,6 +16,8 @@ package com.android.systemui.statusbar.phone; +import static com.android.systemui.util.kotlin.JavaAdapterKt.collectFlow; + import static java.lang.Float.isNaN; import android.animation.Animator; @@ -53,9 +55,14 @@ import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.dock.DockManager; import com.android.systemui.keyguard.KeyguardUnlockAnimationController; +import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor; import com.android.systemui.keyguard.shared.constants.KeyguardBouncerConstants; +import com.android.systemui.keyguard.shared.model.TransitionState; +import com.android.systemui.keyguard.shared.model.TransitionStep; +import com.android.systemui.keyguard.ui.viewmodel.PrimaryBouncerToGoneTransitionViewModel; import com.android.systemui.scrim.ScrimView; import com.android.systemui.shade.NotificationPanelViewController; +import com.android.systemui.statusbar.SysuiStatusBarStateController; import com.android.systemui.statusbar.notification.stack.ViewState; import com.android.systemui.statusbar.policy.ConfigurationController; import com.android.systemui.statusbar.policy.KeyguardStateController; @@ -71,6 +78,8 @@ import java.util.function.Consumer; import javax.inject.Inject; +import kotlinx.coroutines.CoroutineDispatcher; + /** * Controls both the scrim behind the notifications and in front of the notifications (when a * security method gets shown). @@ -197,6 +206,7 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, Dump private final ScreenOffAnimationController mScreenOffAnimationController; private final KeyguardUnlockAnimationController mKeyguardUnlockAnimationController; private final StatusBarKeyguardViewManager mStatusBarKeyguardViewManager; + private final SysuiStatusBarStateController mStatusBarStateController; private GradientColors mColors; private boolean mNeedsDrawableColorUpdate; @@ -251,6 +261,20 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, Dump private boolean mWakeLockHeld; private boolean mKeyguardOccluded; + private KeyguardTransitionInteractor mKeyguardTransitionInteractor; + private CoroutineDispatcher mMainDispatcher; + private boolean mIsBouncerToGoneTransitionRunning = false; + private PrimaryBouncerToGoneTransitionViewModel mPrimaryBouncerToGoneTransitionViewModel; + private final Consumer<Float> mScrimAlphaConsumer = + (Float alpha) -> { + mScrimInFront.setViewAlpha(mInFrontAlpha); + mNotificationsScrim.setViewAlpha(mNotificationsAlpha); + mBehindAlpha = alpha; + mScrimBehind.setViewAlpha(alpha); + }; + + Consumer<TransitionStep> mPrimaryBouncerToGoneTransition; + @Inject public ScrimController( LightBarController lightBarController, @@ -265,13 +289,18 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, Dump @Main Executor mainExecutor, ScreenOffAnimationController screenOffAnimationController, KeyguardUnlockAnimationController keyguardUnlockAnimationController, - StatusBarKeyguardViewManager statusBarKeyguardViewManager) { + StatusBarKeyguardViewManager statusBarKeyguardViewManager, + PrimaryBouncerToGoneTransitionViewModel primaryBouncerToGoneTransitionViewModel, + KeyguardTransitionInteractor keyguardTransitionInteractor, + SysuiStatusBarStateController sysuiStatusBarStateController, + @Main CoroutineDispatcher mainDispatcher) { mScrimStateListener = lightBarController::setScrimState; mDefaultScrimAlpha = BUSY_SCRIM_ALPHA; mKeyguardStateController = keyguardStateController; mDarkenWhileDragging = !mKeyguardStateController.canDismissLockScreen(); mKeyguardUpdateMonitor = keyguardUpdateMonitor; + mStatusBarStateController = sysuiStatusBarStateController; mKeyguardVisibilityCallback = new KeyguardVisibilityCallback(); mHandler = handler; mMainExecutor = mainExecutor; @@ -304,6 +333,9 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, Dump } }); mColors = new GradientColors(); + mPrimaryBouncerToGoneTransitionViewModel = primaryBouncerToGoneTransitionViewModel; + mKeyguardTransitionInteractor = keyguardTransitionInteractor; + mMainDispatcher = mainDispatcher; } /** @@ -343,6 +375,33 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, Dump for (ScrimState state : ScrimState.values()) { state.prepare(state); } + + // Directly control transition to UNLOCKED scrim state from PRIMARY_BOUNCER, and make sure + // to report back that keyguard has faded away. This fixes cases where the scrim state was + // rapidly switching on unlock, due to shifts in state in CentralSurfacesImpl + mPrimaryBouncerToGoneTransition = + (TransitionStep step) -> { + TransitionState state = step.getTransitionState(); + + mIsBouncerToGoneTransitionRunning = state == TransitionState.RUNNING; + + if (state == TransitionState.STARTED) { + setExpansionAffectsAlpha(false); + transitionTo(ScrimState.UNLOCKED); + } + + if (state == TransitionState.FINISHED || state == TransitionState.CANCELED) { + setExpansionAffectsAlpha(true); + if (mKeyguardStateController.isKeyguardFadingAway()) { + mStatusBarKeyguardViewManager.onKeyguardFadedAway(); + } + } + }; + + collectFlow(behindScrim, mKeyguardTransitionInteractor.getPrimaryBouncerToGoneTransition(), + mPrimaryBouncerToGoneTransition, mMainDispatcher); + collectFlow(behindScrim, mPrimaryBouncerToGoneTransitionViewModel.getScrimBehindAlpha(), + mScrimAlphaConsumer, mMainDispatcher); } /** @@ -365,6 +424,11 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, Dump } public void transitionTo(ScrimState state, Callback callback) { + if (mIsBouncerToGoneTransitionRunning) { + Log.i(TAG, "Skipping transition to: " + state + + " while mIsBouncerToGoneTransitionRunning"); + return; + } if (state == mState) { // Call the callback anyway, unless it's already enqueued if (callback != null && mCallback != callback) { @@ -1036,7 +1100,8 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, Dump mBehindAlpha = 1; } // Prevent notification scrim flicker when transitioning away from keyguard. - if (mKeyguardStateController.isKeyguardGoingAway()) { + if (mKeyguardStateController.isKeyguardGoingAway() + && !mStatusBarStateController.leaveOpenOnKeyguardHide()) { mNotificationsAlpha = 0; } @@ -1125,7 +1190,9 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, Dump Trace.traceCounter(Trace.TRACE_TAG_APP, getScrimName(scrimView) + "_tint", Color.alpha(tint)); scrimView.setTint(tint); - scrimView.setViewAlpha(alpha); + if (!mIsBouncerToGoneTransitionRunning) { + scrimView.setViewAlpha(alpha); + } } else { scrim.setAlpha(alpha); } @@ -1473,6 +1540,9 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, Dump } public void setKeyguardOccluded(boolean keyguardOccluded) { + if (mKeyguardOccluded == keyguardOccluded) { + return; + } mKeyguardOccluded = keyguardOccluded; updateScrims(); } diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt index 0469e77ca991..0e6f8d4e0720 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt @@ -219,6 +219,29 @@ class KeyguardRepositoryImplTest : SysuiTestCase() { } @Test + fun isKeyguardUnlocked() = + runTest(UnconfinedTestDispatcher()) { + whenever(keyguardStateController.isUnlocked).thenReturn(false) + var latest: Boolean? = null + val job = underTest.isKeyguardUnlocked.onEach { latest = it }.launchIn(this) + + assertThat(latest).isFalse() + + val captor = argumentCaptor<KeyguardStateController.Callback>() + verify(keyguardStateController).addCallback(captor.capture()) + + whenever(keyguardStateController.isUnlocked).thenReturn(true) + captor.value.onUnlockedChanged() + assertThat(latest).isTrue() + + whenever(keyguardStateController.isUnlocked).thenReturn(false) + captor.value.onUnlockedChanged() + assertThat(latest).isFalse() + + job.cancel() + } + + @Test fun isDozing() = runTest(UnconfinedTestDispatcher()) { var latest: Boolean? = null diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt index ae7a928cdb2c..fe9098fa5c25 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt @@ -19,6 +19,8 @@ package com.android.systemui.keyguard.domain.interactor import android.animation.ValueAnimator import androidx.test.filters.FlakyTest import androidx.test.filters.SmallTest +import com.android.keyguard.KeyguardSecurityModel +import com.android.keyguard.KeyguardSecurityModel.SecurityMode.PIN import com.android.systemui.SysuiTestCase import com.android.systemui.animation.Interpolators import com.android.systemui.flags.FakeFeatureFlags @@ -40,6 +42,7 @@ 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.whenever import com.android.systemui.util.mockito.withArgCaptor import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.cancelChildren @@ -51,6 +54,8 @@ import org.junit.Before import org.junit.Test import org.junit.runner.RunWith import org.junit.runners.JUnit4 +import org.mockito.ArgumentMatchers.anyBoolean +import org.mockito.ArgumentMatchers.anyInt import org.mockito.Mock import org.mockito.Mockito.reset import org.mockito.Mockito.verify @@ -77,6 +82,7 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() { // Used to verify transition requests for test output @Mock private lateinit var mockTransitionRepository: KeyguardTransitionRepository @Mock private lateinit var commandQueue: CommandQueue + @Mock private lateinit var keyguardSecurityModel: KeyguardSecurityModel private lateinit var fromLockscreenTransitionInteractor: FromLockscreenTransitionInteractor private lateinit var fromDreamingTransitionInteractor: FromDreamingTransitionInteractor @@ -102,6 +108,8 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() { transitionRepository = KeyguardTransitionRepositoryImpl() runner = KeyguardTransitionRunner(transitionRepository) + whenever(keyguardSecurityModel.getSecurityMode(anyInt())).thenReturn(PIN) + val featureFlags = FakeFeatureFlags().apply { set(Flags.FACE_AUTH_REFACTOR, true) } fromLockscreenTransitionInteractor = FromLockscreenTransitionInteractor( @@ -173,16 +181,17 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() { keyguardInteractor = createKeyguardInteractor(featureFlags), keyguardTransitionRepository = mockTransitionRepository, keyguardTransitionInteractor = KeyguardTransitionInteractor(transitionRepository), + keyguardSecurityModel = keyguardSecurityModel, ) fromPrimaryBouncerTransitionInteractor.start() } @Test - fun `DREAMING to LOCKSCREEN - dreaming state changes first`() = + fun `DREAMING to LOCKSCREEN`() = testScope.runTest { - // GIVEN a device is dreaming and occluded + // GIVEN a device is dreaming keyguardRepository.setDreamingWithOverlay(true) - keyguardRepository.setKeyguardOccluded(true) + keyguardRepository.setWakefulnessModel(startingToWake()) runCurrent() // GIVEN a prior transition has run to DREAMING @@ -215,56 +224,7 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() { val info = withArgCaptor<TransitionInfo> { - verify(mockTransitionRepository).startTransition(capture()) - } - // THEN a transition to BOUNCER should occur - assertThat(info.ownerName).isEqualTo("FromDreamingTransitionInteractor") - assertThat(info.from).isEqualTo(KeyguardState.DREAMING) - assertThat(info.to).isEqualTo(KeyguardState.LOCKSCREEN) - assertThat(info.animator).isNotNull() - - coroutineContext.cancelChildren() - } - - @Test - fun `DREAMING to LOCKSCREEN - occluded state changes first`() = - testScope.runTest { - // GIVEN a device is dreaming and occluded - keyguardRepository.setDreamingWithOverlay(true) - keyguardRepository.setKeyguardOccluded(true) - runCurrent() - - // GIVEN a prior transition has run to DREAMING - runner.startTransition( - testScope, - TransitionInfo( - ownerName = "", - from = KeyguardState.LOCKSCREEN, - to = KeyguardState.DREAMING, - animator = - ValueAnimator().apply { - duration = 10 - interpolator = Interpolators.LINEAR - }, - ) - ) - runCurrent() - reset(mockTransitionRepository) - - // WHEN doze is complete - keyguardRepository.setDozeTransitionModel( - DozeTransitionModel(from = DozeStateModel.DOZE, to = DozeStateModel.FINISH) - ) - // AND occluded has stopped - keyguardRepository.setKeyguardOccluded(false) - advanceUntilIdle() - // AND then dreaming has stopped - keyguardRepository.setDreamingWithOverlay(false) - advanceUntilIdle() - - val info = - withArgCaptor<TransitionInfo> { - verify(mockTransitionRepository).startTransition(capture()) + verify(mockTransitionRepository).startTransition(capture(), anyBoolean()) } // THEN a transition to BOUNCER should occur assertThat(info.ownerName).isEqualTo("FromDreamingTransitionInteractor") @@ -304,7 +264,7 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() { val info = withArgCaptor<TransitionInfo> { - verify(mockTransitionRepository).startTransition(capture()) + verify(mockTransitionRepository).startTransition(capture(), anyBoolean()) } // THEN a transition to PRIMARY_BOUNCER should occur assertThat(info.ownerName).isEqualTo("FromLockscreenTransitionInteractor") @@ -345,7 +305,7 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() { val info = withArgCaptor<TransitionInfo> { - verify(mockTransitionRepository).startTransition(capture()) + verify(mockTransitionRepository).startTransition(capture(), anyBoolean()) } // THEN a transition to DOZING should occur assertThat(info.ownerName).isEqualTo("FromOccludedTransitionInteractor") @@ -386,7 +346,7 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() { val info = withArgCaptor<TransitionInfo> { - verify(mockTransitionRepository).startTransition(capture()) + verify(mockTransitionRepository).startTransition(capture(), anyBoolean()) } // THEN a transition to DOZING should occur assertThat(info.ownerName).isEqualTo("FromOccludedTransitionInteractor") @@ -427,7 +387,7 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() { val info = withArgCaptor<TransitionInfo> { - verify(mockTransitionRepository).startTransition(capture()) + verify(mockTransitionRepository).startTransition(capture(), anyBoolean()) } // THEN a transition to DOZING should occur assertThat(info.ownerName).isEqualTo("FromLockscreenTransitionInteractor") @@ -468,7 +428,7 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() { val info = withArgCaptor<TransitionInfo> { - verify(mockTransitionRepository).startTransition(capture()) + verify(mockTransitionRepository).startTransition(capture(), anyBoolean()) } // THEN a transition to DOZING should occur assertThat(info.ownerName).isEqualTo("FromLockscreenTransitionInteractor") @@ -505,7 +465,7 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() { val info = withArgCaptor<TransitionInfo> { - verify(mockTransitionRepository).startTransition(capture()) + verify(mockTransitionRepository).startTransition(capture(), anyBoolean()) } // THEN a transition to DOZING should occur assertThat(info.ownerName).isEqualTo("FromDozingTransitionInteractor") @@ -542,7 +502,7 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() { val info = withArgCaptor<TransitionInfo> { - verify(mockTransitionRepository).startTransition(capture()) + verify(mockTransitionRepository).startTransition(capture(), anyBoolean()) } // THEN a transition to DOZING should occur assertThat(info.ownerName).isEqualTo("FromDozingTransitionInteractor") @@ -583,7 +543,7 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() { val info = withArgCaptor<TransitionInfo> { - verify(mockTransitionRepository).startTransition(capture()) + verify(mockTransitionRepository).startTransition(capture(), anyBoolean()) } // THEN a transition to DOZING should occur assertThat(info.ownerName).isEqualTo("FromGoneTransitionInteractor") @@ -624,7 +584,7 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() { val info = withArgCaptor<TransitionInfo> { - verify(mockTransitionRepository).startTransition(capture()) + verify(mockTransitionRepository).startTransition(capture(), anyBoolean()) } // THEN a transition to AOD should occur assertThat(info.ownerName).isEqualTo("FromGoneTransitionInteractor") @@ -661,7 +621,7 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() { val info = withArgCaptor<TransitionInfo> { - verify(mockTransitionRepository).startTransition(capture()) + verify(mockTransitionRepository).startTransition(capture(), anyBoolean()) } // THEN a transition to AOD should occur assertThat(info.ownerName).isEqualTo("FromGoneTransitionInteractor") @@ -677,6 +637,7 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() { testScope.runTest { // GIVEN a device that is not dreaming or dozing keyguardRepository.setDreamingWithOverlay(false) + keyguardRepository.setWakefulnessModel(startingToWake()) keyguardRepository.setDozeTransitionModel( DozeTransitionModel(from = DozeStateModel.DOZE, to = DozeStateModel.FINISH) ) @@ -704,7 +665,7 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() { val info = withArgCaptor<TransitionInfo> { - verify(mockTransitionRepository).startTransition(capture()) + verify(mockTransitionRepository).startTransition(capture(), anyBoolean()) } // THEN a transition to DREAMING should occur assertThat(info.ownerName).isEqualTo("FromGoneTransitionInteractor") @@ -741,7 +702,7 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() { val info = withArgCaptor<TransitionInfo> { - verify(mockTransitionRepository).startTransition(capture()) + verify(mockTransitionRepository).startTransition(capture(), anyBoolean()) } // THEN a transition to PRIMARY_BOUNCER should occur assertThat(info.ownerName).isEqualTo("FromAlternateBouncerTransitionInteractor") @@ -784,7 +745,7 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() { val info = withArgCaptor<TransitionInfo> { - verify(mockTransitionRepository).startTransition(capture()) + verify(mockTransitionRepository).startTransition(capture(), anyBoolean()) } // THEN a transition to AOD should occur assertThat(info.ownerName).isEqualTo("FromAlternateBouncerTransitionInteractor") @@ -828,7 +789,7 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() { val info = withArgCaptor<TransitionInfo> { - verify(mockTransitionRepository).startTransition(capture()) + verify(mockTransitionRepository).startTransition(capture(), anyBoolean()) } // THEN a transition to DOZING should occur assertThat(info.ownerName).isEqualTo("FromAlternateBouncerTransitionInteractor") @@ -870,7 +831,7 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() { val info = withArgCaptor<TransitionInfo> { - verify(mockTransitionRepository).startTransition(capture()) + verify(mockTransitionRepository).startTransition(capture(), anyBoolean()) } // THEN a transition to LOCKSCREEN should occur assertThat(info.ownerName).isEqualTo("FromAlternateBouncerTransitionInteractor") @@ -912,7 +873,7 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() { val info = withArgCaptor<TransitionInfo> { - verify(mockTransitionRepository).startTransition(capture()) + verify(mockTransitionRepository).startTransition(capture(), anyBoolean()) } // THEN a transition to AOD should occur assertThat(info.ownerName).isEqualTo("FromPrimaryBouncerTransitionInteractor") @@ -954,7 +915,7 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() { val info = withArgCaptor<TransitionInfo> { - verify(mockTransitionRepository).startTransition(capture()) + verify(mockTransitionRepository).startTransition(capture(), anyBoolean()) } // THEN a transition to DOZING should occur assertThat(info.ownerName).isEqualTo("FromPrimaryBouncerTransitionInteractor") @@ -995,7 +956,7 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() { val info = withArgCaptor<TransitionInfo> { - verify(mockTransitionRepository).startTransition(capture()) + verify(mockTransitionRepository).startTransition(capture(), anyBoolean()) } // THEN a transition to LOCKSCREEN should occur assertThat(info.ownerName).isEqualTo("FromPrimaryBouncerTransitionInteractor") diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGoneTransitionViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGoneTransitionViewModelTest.kt new file mode 100644 index 000000000000..2a91799741b7 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGoneTransitionViewModelTest.kt @@ -0,0 +1,109 @@ +/* + * 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.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository +import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor +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.statusbar.SysuiStatusBarStateController +import com.android.systemui.util.mockito.whenever +import com.google.common.collect.Range +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.mockito.Mock +import org.mockito.MockitoAnnotations + +@SmallTest +@RunWith(AndroidJUnit4::class) +class PrimaryBouncerToGoneTransitionViewModelTest : SysuiTestCase() { + private lateinit var underTest: PrimaryBouncerToGoneTransitionViewModel + private lateinit var repository: FakeKeyguardTransitionRepository + @Mock private lateinit var statusBarStateController: SysuiStatusBarStateController + + @Before + fun setUp() { + MockitoAnnotations.initMocks(this) + repository = FakeKeyguardTransitionRepository() + val interactor = KeyguardTransitionInteractor(repository) + underTest = PrimaryBouncerToGoneTransitionViewModel(interactor, statusBarStateController) + } + + @Test + fun scrimBehindAlpha_leaveShadeOpen() = + runTest(UnconfinedTestDispatcher()) { + val values = mutableListOf<Float>() + + val job = underTest.scrimBehindAlpha.onEach { values.add(it) }.launchIn(this) + + whenever(statusBarStateController.leaveOpenOnKeyguardHide()).thenReturn(true) + + repository.sendTransitionStep(step(0f, TransitionState.STARTED)) + repository.sendTransitionStep(step(0.3f)) + repository.sendTransitionStep(step(0.6f)) + repository.sendTransitionStep(step(1f)) + + assertThat(values.size).isEqualTo(4) + values.forEach { assertThat(it).isEqualTo(1f) } + + job.cancel() + } + + @Test + fun scrimBehindAlpha_doNotLeaveShadeOpen() = + runTest(UnconfinedTestDispatcher()) { + val values = mutableListOf<Float>() + + val job = underTest.scrimBehindAlpha.onEach { values.add(it) }.launchIn(this) + + whenever(statusBarStateController.leaveOpenOnKeyguardHide()).thenReturn(false) + + repository.sendTransitionStep(step(0f, TransitionState.STARTED)) + repository.sendTransitionStep(step(0.3f)) + repository.sendTransitionStep(step(0.6f)) + repository.sendTransitionStep(step(1f)) + + assertThat(values.size).isEqualTo(4) + values.forEach { assertThat(it).isIn(Range.closed(0f, 1f)) } + assertThat(values[3]).isEqualTo(0f) + + job.cancel() + } + + private fun step( + value: Float, + state: TransitionState = TransitionState.RUNNING + ): TransitionStep { + return TransitionStep( + from = KeyguardState.PRIMARY_BOUNCER, + to = KeyguardState.GONE, + value = value, + transitionState = state, + ownerName = "PrimaryBouncerToGoneTransitionViewModelTest" + ) + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt index 0a401b09b6cf..82a57438052f 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt @@ -33,6 +33,7 @@ import com.android.systemui.keyguard.domain.interactor.AlternateBouncerInteracto import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor import com.android.systemui.keyguard.shared.model.TransitionStep import com.android.systemui.keyguard.ui.viewmodel.KeyguardBouncerViewModel +import com.android.systemui.keyguard.ui.viewmodel.PrimaryBouncerToGoneTransitionViewModel import com.android.systemui.shade.NotificationShadeWindowView.InteractionEventHandler import com.android.systemui.statusbar.LockscreenShadeTransitionController import com.android.systemui.statusbar.NotificationInsetsController @@ -65,48 +66,32 @@ import org.mockito.MockitoAnnotations @RunWith(AndroidTestingRunner::class) @RunWithLooper(setAsMainLooper = true) class NotificationShadeWindowViewControllerTest : SysuiTestCase() { - @Mock - private lateinit var view: NotificationShadeWindowView - @Mock - private lateinit var sysuiStatusBarStateController: SysuiStatusBarStateController - @Mock - private lateinit var centralSurfaces: CentralSurfaces - @Mock - private lateinit var dockManager: DockManager - @Mock - private lateinit var notificationPanelViewController: NotificationPanelViewController - @Mock - private lateinit var notificationShadeDepthController: NotificationShadeDepthController - @Mock - private lateinit var notificationShadeWindowController: NotificationShadeWindowController - @Mock - private lateinit var keyguardUnlockAnimationController: KeyguardUnlockAnimationController - @Mock - private lateinit var ambientState: AmbientState - @Mock - private lateinit var keyguardBouncerViewModel: KeyguardBouncerViewModel - @Mock - private lateinit var stackScrollLayoutController: NotificationStackScrollLayoutController - @Mock - private lateinit var statusBarKeyguardViewManager: StatusBarKeyguardViewManager - @Mock - private lateinit var statusBarWindowStateController: StatusBarWindowStateController + @Mock private lateinit var view: NotificationShadeWindowView + @Mock private lateinit var sysuiStatusBarStateController: SysuiStatusBarStateController + @Mock private lateinit var centralSurfaces: CentralSurfaces + @Mock private lateinit var dockManager: DockManager + @Mock private lateinit var notificationPanelViewController: NotificationPanelViewController + @Mock private lateinit var notificationShadeDepthController: NotificationShadeDepthController + @Mock private lateinit var notificationShadeWindowController: NotificationShadeWindowController + @Mock private lateinit var keyguardUnlockAnimationController: KeyguardUnlockAnimationController + @Mock private lateinit var ambientState: AmbientState + @Mock private lateinit var keyguardBouncerViewModel: KeyguardBouncerViewModel + @Mock private lateinit var stackScrollLayoutController: NotificationStackScrollLayoutController + @Mock private lateinit var statusBarKeyguardViewManager: StatusBarKeyguardViewManager + @Mock private lateinit var statusBarWindowStateController: StatusBarWindowStateController @Mock private lateinit var lockscreenShadeTransitionController: LockscreenShadeTransitionController - @Mock - private lateinit var lockIconViewController: LockIconViewController - @Mock - private lateinit var phoneStatusBarViewController: PhoneStatusBarViewController - @Mock - private lateinit var pulsingGestureListener: PulsingGestureListener - @Mock - private lateinit var notificationInsetsController: NotificationInsetsController - @Mock - private lateinit var alternateBouncerInteractor: AlternateBouncerInteractor + @Mock private lateinit var lockIconViewController: LockIconViewController + @Mock private lateinit var phoneStatusBarViewController: PhoneStatusBarViewController + @Mock private lateinit var pulsingGestureListener: PulsingGestureListener + @Mock private lateinit var notificationInsetsController: NotificationInsetsController + @Mock private lateinit var alternateBouncerInteractor: AlternateBouncerInteractor @Mock lateinit var keyguardBouncerComponentFactory: KeyguardBouncerComponent.Factory @Mock lateinit var keyguardBouncerComponent: KeyguardBouncerComponent @Mock lateinit var keyguardSecurityContainerController: KeyguardSecurityContainerController @Mock lateinit var keyguardTransitionInteractor: KeyguardTransitionInteractor + @Mock + lateinit var primaryBouncerToGoneTransitionViewModel: PrimaryBouncerToGoneTransitionViewModel private lateinit var interactionEventHandlerCaptor: ArgumentCaptor<InteractionEventHandler> private lateinit var interactionEventHandler: InteractionEventHandler @@ -118,43 +103,44 @@ class NotificationShadeWindowViewControllerTest : SysuiTestCase() { MockitoAnnotations.initMocks(this) whenever(view.bottom).thenReturn(VIEW_BOTTOM) whenever(view.findViewById<ViewGroup>(R.id.keyguard_bouncer_container)) - .thenReturn(mock(ViewGroup::class.java)) + .thenReturn(mock(ViewGroup::class.java)) whenever(keyguardBouncerComponentFactory.create(any(ViewGroup::class.java))) - .thenReturn(keyguardBouncerComponent) + .thenReturn(keyguardBouncerComponent) whenever(keyguardBouncerComponent.securityContainerController) - .thenReturn(keyguardSecurityContainerController) + .thenReturn(keyguardSecurityContainerController) whenever(keyguardTransitionInteractor.lockscreenToDreamingTransition) - .thenReturn(emptyFlow<TransitionStep>()) - underTest = NotificationShadeWindowViewController( - lockscreenShadeTransitionController, - FalsingCollectorFake(), - sysuiStatusBarStateController, - dockManager, - notificationShadeDepthController, - view, - notificationPanelViewController, - ShadeExpansionStateManager(), - stackScrollLayoutController, - statusBarKeyguardViewManager, - statusBarWindowStateController, - lockIconViewController, - centralSurfaces, - notificationShadeWindowController, - keyguardUnlockAnimationController, - notificationInsetsController, - ambientState, - pulsingGestureListener, - keyguardBouncerViewModel, - keyguardBouncerComponentFactory, - alternateBouncerInteractor, - keyguardTransitionInteractor, - ) + .thenReturn(emptyFlow<TransitionStep>()) + underTest = + NotificationShadeWindowViewController( + lockscreenShadeTransitionController, + FalsingCollectorFake(), + sysuiStatusBarStateController, + dockManager, + notificationShadeDepthController, + view, + notificationPanelViewController, + ShadeExpansionStateManager(), + stackScrollLayoutController, + statusBarKeyguardViewManager, + statusBarWindowStateController, + lockIconViewController, + centralSurfaces, + notificationShadeWindowController, + keyguardUnlockAnimationController, + notificationInsetsController, + ambientState, + pulsingGestureListener, + keyguardBouncerViewModel, + keyguardBouncerComponentFactory, + alternateBouncerInteractor, + keyguardTransitionInteractor, + primaryBouncerToGoneTransitionViewModel, + ) underTest.setupExpandedStatusBar() - interactionEventHandlerCaptor = - ArgumentCaptor.forClass(InteractionEventHandler::class.java) + interactionEventHandlerCaptor = ArgumentCaptor.forClass(InteractionEventHandler::class.java) verify(view).setInteractionEventHandler(interactionEventHandlerCaptor.capture()) - interactionEventHandler = interactionEventHandlerCaptor.value + interactionEventHandler = interactionEventHandlerCaptor.value } // Note: So far, these tests only cover interactions with the status bar view controller. More @@ -184,14 +170,11 @@ class NotificationShadeWindowViewControllerTest : SysuiTestCase() { @Test fun handleDispatchTouchEvent_downTouchBelowViewThenAnotherTouch_sendsTouchToSb() { underTest.setStatusBarViewController(phoneStatusBarViewController) - val downEvBelow = MotionEvent.obtain( - 0L, 0L, MotionEvent.ACTION_DOWN, 0f, VIEW_BOTTOM + 4f, 0 - ) + val downEvBelow = + MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_DOWN, 0f, VIEW_BOTTOM + 4f, 0) interactionEventHandler.handleDispatchTouchEvent(downEvBelow) - val nextEvent = MotionEvent.obtain( - 0L, 0L, MotionEvent.ACTION_MOVE, 0f, VIEW_BOTTOM + 5f, 0 - ) + val nextEvent = MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_MOVE, 0f, VIEW_BOTTOM + 5f, 0) whenever(phoneStatusBarViewController.sendTouchToView(nextEvent)).thenReturn(true) val returnVal = interactionEventHandler.handleDispatchTouchEvent(nextEvent) diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.java index 5d719790386a..faa6221b675c 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.java @@ -46,6 +46,7 @@ import com.android.systemui.keyguard.KeyguardUnlockAnimationController; import com.android.systemui.keyguard.domain.interactor.AlternateBouncerInteractor; import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor; import com.android.systemui.keyguard.ui.viewmodel.KeyguardBouncerViewModel; +import com.android.systemui.keyguard.ui.viewmodel.PrimaryBouncerToGoneTransitionViewModel; import com.android.systemui.statusbar.DragDownHelper; import com.android.systemui.statusbar.LockscreenShadeTransitionController; import com.android.systemui.statusbar.NotificationInsetsController; @@ -101,6 +102,7 @@ public class NotificationShadeWindowViewTest extends SysuiTestCase { @Mock private NotificationInsetsController mNotificationInsetsController; @Mock private AlternateBouncerInteractor mAlternateBouncerInteractor; @Mock private KeyguardTransitionInteractor mKeyguardTransitionInteractor; + @Mock private PrimaryBouncerToGoneTransitionViewModel mPrimaryBouncerToGoneTransitionViewModel; @Captor private ArgumentCaptor<NotificationShadeWindowView.InteractionEventHandler> mInteractionEventHandlerCaptor; @@ -150,7 +152,8 @@ public class NotificationShadeWindowViewTest extends SysuiTestCase { mKeyguardBouncerViewModel, mKeyguardBouncerComponentFactory, mAlternateBouncerInteractor, - mKeyguardTransitionInteractor + mKeyguardTransitionInteractor, + mPrimaryBouncerToGoneTransitionViewModel ); mController.setupExpandedStatusBar(); mController.setDragDownHelper(mDragDownHelper); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java index dc5a0472f49e..e1fba816382c 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java @@ -24,6 +24,8 @@ import static com.android.systemui.statusbar.phone.ScrimState.SHADE_LOCKED; import static com.google.common.truth.Truth.assertThat; +import static kotlinx.coroutines.flow.FlowKt.emptyFlow; + import static org.junit.Assert.assertEquals; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyFloat; @@ -58,8 +60,14 @@ import com.android.systemui.SysuiTestCase; import com.android.systemui.animation.ShadeInterpolation; import com.android.systemui.dock.DockManager; import com.android.systemui.keyguard.KeyguardUnlockAnimationController; +import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor; import com.android.systemui.keyguard.shared.constants.KeyguardBouncerConstants; +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.PrimaryBouncerToGoneTransitionViewModel; import com.android.systemui.scrim.ScrimView; +import com.android.systemui.statusbar.SysuiStatusBarStateController; import com.android.systemui.statusbar.policy.FakeConfigurationController; import com.android.systemui.statusbar.policy.KeyguardStateController; import com.android.systemui.util.concurrency.FakeExecutor; @@ -85,8 +93,10 @@ import java.util.HashMap; import java.util.HashSet; import java.util.Map; +import kotlinx.coroutines.CoroutineDispatcher; + @RunWith(AndroidTestingRunner.class) -@TestableLooper.RunWithLooper +@TestableLooper.RunWithLooper(setAsMainLooper = true) @SmallTest public class ScrimControllerTest extends SysuiTestCase { @@ -115,6 +125,11 @@ public class ScrimControllerTest extends SysuiTestCase { @Mock private DockManager mDockManager; @Mock private ScreenOffAnimationController mScreenOffAnimationController; @Mock private KeyguardUnlockAnimationController mKeyguardUnlockAnimationController; + @Mock private PrimaryBouncerToGoneTransitionViewModel mPrimaryBouncerToGoneTransitionViewModel; + @Mock private KeyguardTransitionInteractor mKeyguardTransitionInteractor; + @Mock private CoroutineDispatcher mMainDispatcher; + @Mock private SysuiStatusBarStateController mSysuiStatusBarStateController; + // TODO(b/204991468): Use a real PanelExpansionStateManager object once this bug is fixed. (The // event-dispatch-on-registration pattern caused some of these unit tests to fail.) @Mock private StatusBarKeyguardViewManager mStatusBarKeyguardViewManager; @@ -225,13 +240,22 @@ public class ScrimControllerTest extends SysuiTestCase { when(mDelayedWakeLockBuilder.build()).thenReturn(mWakeLock); when(mDockManager.isDocked()).thenReturn(false); + when(mKeyguardTransitionInteractor.getPrimaryBouncerToGoneTransition()) + .thenReturn(emptyFlow()); + when(mPrimaryBouncerToGoneTransitionViewModel.getScrimBehindAlpha()) + .thenReturn(emptyFlow()); + mScrimController = new ScrimController(mLightBarController, mDozeParameters, mAlarmManager, mKeyguardStateController, mDelayedWakeLockBuilder, new FakeHandler(mLooper.getLooper()), mKeyguardUpdateMonitor, mDockManager, mConfigurationController, new FakeExecutor(new FakeSystemClock()), mScreenOffAnimationController, mKeyguardUnlockAnimationController, - mStatusBarKeyguardViewManager); + mStatusBarKeyguardViewManager, + mPrimaryBouncerToGoneTransitionViewModel, + mKeyguardTransitionInteractor, + mSysuiStatusBarStateController, + mMainDispatcher); mScrimController.setScrimVisibleListener(visible -> mScrimVisibility = visible); mScrimController.attachViews(mScrimBehind, mNotificationsScrim, mScrimInFront); mScrimController.setAnimatorListener(mAnimatorListener); @@ -861,7 +885,11 @@ public class ScrimControllerTest extends SysuiTestCase { mDockManager, mConfigurationController, new FakeExecutor(new FakeSystemClock()), mScreenOffAnimationController, mKeyguardUnlockAnimationController, - mStatusBarKeyguardViewManager); + mStatusBarKeyguardViewManager, + mPrimaryBouncerToGoneTransitionViewModel, + mKeyguardTransitionInteractor, + mSysuiStatusBarStateController, + mMainDispatcher); mScrimController.setScrimVisibleListener(visible -> mScrimVisibility = visible); mScrimController.attachViews(mScrimBehind, mNotificationsScrim, mScrimInFront); mScrimController.setAnimatorListener(mAnimatorListener); @@ -1629,6 +1657,28 @@ public class ScrimControllerTest extends SysuiTestCase { assertScrimAlpha(mScrimBehind, 0); } + @Test + public void ignoreTransitionRequestWhileKeyguardTransitionRunning() { + mScrimController.transitionTo(ScrimState.UNLOCKED); + mScrimController.mPrimaryBouncerToGoneTransition.accept( + new TransitionStep(KeyguardState.PRIMARY_BOUNCER, KeyguardState.GONE, 0f, + TransitionState.RUNNING, "ScrimControllerTest")); + + // This request should not happen + mScrimController.transitionTo(ScrimState.BOUNCER); + assertThat(mScrimController.getState()).isEqualTo(ScrimState.UNLOCKED); + } + + @Test + public void primaryBouncerToGoneOnFinishCallsKeyguardFadedAway() { + when(mKeyguardStateController.isKeyguardFadingAway()).thenReturn(true); + mScrimController.mPrimaryBouncerToGoneTransition.accept( + new TransitionStep(KeyguardState.PRIMARY_BOUNCER, KeyguardState.GONE, 0f, + TransitionState.FINISHED, "ScrimControllerTest")); + + verify(mStatusBarKeyguardViewManager).onKeyguardFadedAway(); + } + private void assertAlphaAfterExpansion(ScrimView scrim, float expectedAlpha, float expansion) { mScrimController.setRawPanelExpansionFraction(expansion); finishAnimationsImmediately(); diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt index 1a371c73550c..194ed02712b2 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt @@ -47,6 +47,9 @@ class FakeKeyguardRepository : KeyguardRepository { private val _isKeyguardShowing = MutableStateFlow(false) override val isKeyguardShowing: Flow<Boolean> = _isKeyguardShowing + private val _isKeyguardUnlocked = MutableStateFlow(false) + override val isKeyguardUnlocked: Flow<Boolean> = _isKeyguardUnlocked + private val _isKeyguardOccluded = MutableStateFlow(false) override val isKeyguardOccluded: Flow<Boolean> = _isKeyguardOccluded diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardTransitionRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardTransitionRepository.kt index eac1bd145033..16442bb525b6 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardTransitionRepository.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardTransitionRepository.kt @@ -37,7 +37,7 @@ class FakeKeyguardTransitionRepository : KeyguardTransitionRepository { _transitions.emit(step) } - override fun startTransition(info: TransitionInfo): UUID? { + override fun startTransition(info: TransitionInfo, resetIfCanceled: Boolean): UUID? { return null } |