diff options
26 files changed, 649 insertions, 96 deletions
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 ca25282ec2f0..9d5d8bbd4f40 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 @@ -17,6 +17,7 @@ package com.android.systemui.keyguard.data.repository import com.android.keyguard.KeyguardUpdateMonitor +import com.android.keyguard.KeyguardUpdateMonitorCallback import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow import com.android.systemui.common.shared.model.Position @@ -69,6 +70,9 @@ interface KeyguardRepository { */ val isKeyguardShowing: Flow<Boolean> + /** Observable for the signal that keyguard is about to go away. */ + val isKeyguardGoingAway: Flow<Boolean> + /** Observable for whether the bouncer is showing. */ val isBouncerShowing: Flow<Boolean> @@ -85,6 +89,14 @@ interface KeyguardRepository { val isDozing: Flow<Boolean> /** + * Observable for whether the device is dreaming. + * + * Dozing/AOD is a specific type of dream, but it is also possible for other non-systemui dreams + * to be active, such as screensavers. + */ + val isDreaming: Flow<Boolean> + + /** * Observable for the amount of doze we are currently in. * * While in doze state, this amount can change - driving a cycle of animations designed to avoid @@ -136,12 +148,12 @@ interface KeyguardRepository { class KeyguardRepositoryImpl @Inject constructor( - statusBarStateController: StatusBarStateController, - dozeHost: DozeHost, - wakefulnessLifecycle: WakefulnessLifecycle, - biometricUnlockController: BiometricUnlockController, - private val keyguardStateController: KeyguardStateController, - private val keyguardUpdateMonitor: KeyguardUpdateMonitor, + statusBarStateController: StatusBarStateController, + dozeHost: DozeHost, + wakefulnessLifecycle: WakefulnessLifecycle, + biometricUnlockController: BiometricUnlockController, + private val keyguardStateController: KeyguardStateController, + private val keyguardUpdateMonitor: KeyguardUpdateMonitor, ) : KeyguardRepository { private val _animateBottomAreaDozingTransitions = MutableStateFlow(false) override val animateBottomAreaDozingTransitions = @@ -176,6 +188,29 @@ constructor( awaitClose { keyguardStateController.removeCallback(callback) } } + override val isKeyguardGoingAway: Flow<Boolean> = conflatedCallbackFlow { + val callback = + object : KeyguardStateController.Callback { + override fun onKeyguardGoingAwayChanged() { + trySendWithFailureLogging( + keyguardStateController.isKeyguardGoingAway, + TAG, + "updated isKeyguardGoingAway" + ) + } + } + + keyguardStateController.addCallback(callback) + // Adding the callback does not send an initial update. + trySendWithFailureLogging( + keyguardStateController.isKeyguardGoingAway, + TAG, + "initial isKeyguardGoingAway" + ) + + awaitClose { keyguardStateController.removeCallback(callback) } + } + override val isBouncerShowing: Flow<Boolean> = conflatedCallbackFlow { val callback = object : KeyguardStateController.Callback { @@ -218,6 +253,25 @@ constructor( } .distinctUntilChanged() + override val isDreaming: Flow<Boolean> = + conflatedCallbackFlow { + val callback = + object : KeyguardUpdateMonitorCallback() { + override fun onDreamingStateChanged(isDreaming: Boolean) { + trySendWithFailureLogging(isDreaming, TAG, "updated isDreaming") + } + } + keyguardUpdateMonitor.registerCallback(callback) + trySendWithFailureLogging( + keyguardUpdateMonitor.isDreaming, + TAG, + "initial isDreaming", + ) + + awaitClose { keyguardUpdateMonitor.removeCallback(callback) } + } + .distinctUntilChanged() + override val dozeAmount: Flow<Float> = conflatedCallbackFlow { val callback = object : StatusBarStateController.StateListener { 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 e3d1a27dad2b..bce7d92cd8fb 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 @@ -94,11 +94,13 @@ class KeyguardTransitionRepositoryImpl @Inject constructor() : KeyguardTransitio */ private val _transitions = MutableSharedFlow<TransitionStep>( + replay = 2, extraBufferCapacity = 10, - onBufferOverflow = BufferOverflow.DROP_OLDEST + onBufferOverflow = BufferOverflow.DROP_OLDEST, ) override val transitions = _transitions.asSharedFlow().distinctUntilChanged() private var lastStep: TransitionStep = TransitionStep() + private var lastAnimator: ValueAnimator? = null /* * When manual control of the transition is requested, a unique [UUID] is used as the handle @@ -106,19 +108,39 @@ class KeyguardTransitionRepositoryImpl @Inject constructor() : KeyguardTransitio */ private var updateTransitionId: UUID? = null + init { + // Seed with transitions signaling a boot into lockscreen state + emitTransition( + TransitionStep( + KeyguardState.OFF, + KeyguardState.LOCKSCREEN, + 0f, + TransitionState.STARTED, + ) + ) + emitTransition( + TransitionStep( + KeyguardState.OFF, + KeyguardState.LOCKSCREEN, + 1f, + TransitionState.FINISHED, + ) + ) + } + override fun startTransition(info: TransitionInfo): UUID? { if (lastStep.transitionState != TransitionState.FINISHED) { - // Open questions: - // * Queue of transitions? buffer of 1? - // * Are transitions cancellable if a new one is triggered? - // * What validation does this need to do? - Log.wtf(TAG, "Transition still active: $lastStep") - return null + Log.i(TAG, "Transition still active: $lastStep, canceling") } + val startingValue = 1f - lastStep.value + lastAnimator?.cancel() + lastAnimator = info.animator + info.animator?.let { animator -> // An animator was provided, so use it to run the transition - animator.setFloatValues(0f, 1f) + animator.setFloatValues(startingValue, 1f) + animator.duration = ((1f - startingValue) * animator.duration).toLong() val updateListener = object : AnimatorUpdateListener { override fun onAnimationUpdate(animation: ValueAnimator) { @@ -134,15 +156,24 @@ class KeyguardTransitionRepositoryImpl @Inject constructor() : KeyguardTransitio val adapter = object : AnimatorListenerAdapter() { override fun onAnimationStart(animation: Animator) { - emitTransition(TransitionStep(info, 0f, TransitionState.STARTED)) + emitTransition(TransitionStep(info, startingValue, TransitionState.STARTED)) } override fun onAnimationCancel(animation: Animator) { - Log.i(TAG, "Cancelling transition: $info") + endAnimation(animation, lastStep.value, TransitionState.CANCELED) } override fun onAnimationEnd(animation: Animator) { - emitTransition(TransitionStep(info, 1f, TransitionState.FINISHED)) + endAnimation(animation, 1f, TransitionState.FINISHED) + } + + private fun endAnimation( + animation: Animator, + value: Float, + state: TransitionState + ) { + emitTransition(TransitionStep(info, value, state)) animator.removeListener(this) animator.removeUpdateListener(updateListener) + lastAnimator = null } } animator.addListener(adapter) diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/AodLockscreenTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/AodLockscreenTransitionInteractor.kt index 0aeff7fc69fd..e5521c76705d 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/AodLockscreenTransitionInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/AodLockscreenTransitionInteractor.kt @@ -20,10 +20,11 @@ import android.animation.ValueAnimator import com.android.systemui.animation.Interpolators import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application -import com.android.systemui.keyguard.data.repository.KeyguardRepository import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository import com.android.systemui.keyguard.shared.model.KeyguardState import com.android.systemui.keyguard.shared.model.TransitionInfo +import com.android.systemui.keyguard.shared.model.WakefulnessModel.Companion.isSleepingOrStartingToSleep +import com.android.systemui.keyguard.shared.model.WakefulnessModel.Companion.isWakingOrStartingToWake import com.android.systemui.util.kotlin.sample import javax.inject.Inject import kotlinx.coroutines.CoroutineScope @@ -35,18 +36,30 @@ class AodLockscreenTransitionInteractor @Inject constructor( @Application private val scope: CoroutineScope, - private val keyguardRepository: KeyguardRepository, + private val keyguardInteractor: KeyguardInteractor, private val keyguardTransitionRepository: KeyguardTransitionRepository, private val keyguardTransitionInteractor: KeyguardTransitionInteractor, ) : TransitionInteractor("AOD<->LOCKSCREEN") { override fun start() { scope.launch { - keyguardRepository.isDozing - .sample(keyguardTransitionInteractor.finishedKeyguardState, { a, b -> Pair(a, b) }) + /* + * Listening to the startedKeyguardTransitionStep (last started step) allows this code + * to interrupt an active transition, as long as they were either going to LOCKSCREEN or + * AOD state. One example is when the user presses the power button in the middle of an + * active transition. + */ + keyguardInteractor.wakefulnessState + .sample( + keyguardTransitionInteractor.startedKeyguardTransitionStep, + { a, b -> Pair(a, b) } + ) .collect { pair -> - val (isDozing, keyguardState) = pair - if (isDozing && keyguardState == KeyguardState.LOCKSCREEN) { + val (wakefulnessState, lastStartedStep) = pair + if ( + isSleepingOrStartingToSleep(wakefulnessState) && + lastStartedStep.to == KeyguardState.LOCKSCREEN + ) { keyguardTransitionRepository.startTransition( TransitionInfo( name, @@ -55,7 +68,10 @@ constructor( getAnimator(), ) ) - } else if (!isDozing && keyguardState == KeyguardState.AOD) { + } else if ( + isWakingOrStartingToWake(wakefulnessState) && + lastStartedStep.to == KeyguardState.AOD + ) { keyguardTransitionRepository.startTransition( TransitionInfo( name, diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/BouncerToGoneTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/BouncerToGoneTransitionInteractor.kt new file mode 100644 index 000000000000..dd2967334307 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/BouncerToGoneTransitionInteractor.kt @@ -0,0 +1,81 @@ +/* + * Copyright (C) 2022 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.animation.ValueAnimator +import com.android.systemui.animation.Interpolators +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.dagger.qualifiers.Application +import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository +import com.android.systemui.keyguard.shared.model.KeyguardState +import com.android.systemui.keyguard.shared.model.TransitionInfo +import com.android.systemui.shade.data.repository.ShadeRepository +import com.android.systemui.util.kotlin.sample +import java.util.UUID +import javax.inject.Inject +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.flow.collect +import kotlinx.coroutines.launch + +@SysUISingleton +class BouncerToGoneTransitionInteractor +@Inject +constructor( + @Application private val scope: CoroutineScope, + private val keyguardInteractor: KeyguardInteractor, + private val shadeRepository: ShadeRepository, + private val keyguardTransitionRepository: KeyguardTransitionRepository, + private val keyguardTransitionInteractor: KeyguardTransitionInteractor +) : TransitionInteractor("BOUNCER->GONE") { + + private var transitionId: UUID? = null + + override fun start() { + listenForKeyguardGoingAway() + } + + private fun listenForKeyguardGoingAway() { + scope.launch { + keyguardInteractor.isKeyguardGoingAway + .sample(keyguardTransitionInteractor.finishedKeyguardState, { a, b -> Pair(a, b) }) + .collect { pair -> + val (isKeyguardGoingAway, keyguardState) = pair + if (isKeyguardGoingAway && keyguardState == KeyguardState.BOUNCER) { + keyguardTransitionRepository.startTransition( + TransitionInfo( + ownerName = name, + from = KeyguardState.BOUNCER, + to = KeyguardState.GONE, + animator = getAnimator(), + ) + ) + } + } + } + } + + private fun getAnimator(): ValueAnimator { + return ValueAnimator().apply { + setInterpolator(Interpolators.LINEAR) + setDuration(TRANSITION_DURATION_MS) + } + } + + companion object { + private const val TRANSITION_DURATION_MS = 300L + } +} diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/DreamingLockscreenTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/DreamingLockscreenTransitionInteractor.kt new file mode 100644 index 000000000000..c44cda42c68d --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/DreamingLockscreenTransitionInteractor.kt @@ -0,0 +1,81 @@ +/* + * Copyright (C) 2022 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.animation.ValueAnimator +import com.android.systemui.animation.Interpolators +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.dagger.qualifiers.Application +import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository +import com.android.systemui.keyguard.shared.model.KeyguardState +import com.android.systemui.keyguard.shared.model.TransitionInfo +import com.android.systemui.util.kotlin.sample +import javax.inject.Inject +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.flow.collect +import kotlinx.coroutines.launch + +@SysUISingleton +class DreamingLockscreenTransitionInteractor +@Inject +constructor( + @Application private val scope: CoroutineScope, + private val keyguardInteractor: KeyguardInteractor, + private val keyguardTransitionRepository: KeyguardTransitionRepository, + private val keyguardTransitionInteractor: KeyguardTransitionInteractor, +) : TransitionInteractor("DREAMING<->LOCKSCREEN") { + + override fun start() { + scope.launch { + keyguardInteractor.isDreaming + .sample(keyguardTransitionInteractor.finishedKeyguardState, { a, b -> Pair(a, b) }) + .collect { pair -> + val (isDreaming, keyguardState) = pair + if (isDreaming && keyguardState == KeyguardState.LOCKSCREEN) { + keyguardTransitionRepository.startTransition( + TransitionInfo( + name, + KeyguardState.LOCKSCREEN, + KeyguardState.DREAMING, + getAnimator(), + ) + ) + } else if (!isDreaming && keyguardState == KeyguardState.DREAMING) { + keyguardTransitionRepository.startTransition( + TransitionInfo( + name, + KeyguardState.DREAMING, + KeyguardState.LOCKSCREEN, + getAnimator(), + ) + ) + } + } + } + } + + private fun getAnimator(): ValueAnimator { + return ValueAnimator().apply { + setInterpolator(Interpolators.LINEAR) + setDuration(TRANSITION_DURATION_MS) + } + } + + companion object { + private const val TRANSITION_DURATION_MS = 500L + } +} diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/DreamingToAodTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/DreamingToAodTransitionInteractor.kt new file mode 100644 index 000000000000..9e2b7241ade2 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/DreamingToAodTransitionInteractor.kt @@ -0,0 +1,76 @@ +/* + * Copyright (C) 2022 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.animation.ValueAnimator +import com.android.systemui.animation.Interpolators +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.dagger.qualifiers.Application +import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository +import com.android.systemui.keyguard.shared.model.KeyguardState +import com.android.systemui.keyguard.shared.model.TransitionInfo +import com.android.systemui.keyguard.shared.model.WakefulnessModel.Companion.isSleepingOrStartingToSleep +import com.android.systemui.util.kotlin.sample +import javax.inject.Inject +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.flow.collect +import kotlinx.coroutines.launch + +@SysUISingleton +class DreamingToAodTransitionInteractor +@Inject +constructor( + @Application private val scope: CoroutineScope, + private val keyguardInteractor: KeyguardInteractor, + private val keyguardTransitionRepository: KeyguardTransitionRepository, + private val keyguardTransitionInteractor: KeyguardTransitionInteractor, +) : TransitionInteractor("DREAMING->AOD") { + + override fun start() { + scope.launch { + keyguardInteractor.wakefulnessState + .sample(keyguardTransitionInteractor.finishedKeyguardState, { a, b -> Pair(a, b) }) + .collect { pair -> + val (wakefulnessState, keyguardState) = pair + if ( + isSleepingOrStartingToSleep(wakefulnessState) && + keyguardState == KeyguardState.DREAMING + ) { + keyguardTransitionRepository.startTransition( + TransitionInfo( + name, + KeyguardState.DREAMING, + KeyguardState.AOD, + getAnimator(), + ) + ) + } + } + } + } + + private fun getAnimator(): ValueAnimator { + return ValueAnimator().apply { + setInterpolator(Interpolators.LINEAR) + setDuration(TRANSITION_DURATION_MS) + } + } + + companion object { + private const val TRANSITION_DURATION_MS = 300L + } +} 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 614ff8d930d8..5a1c70264ee4 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 @@ -41,8 +41,15 @@ constructor( val dozeAmount: Flow<Float> = repository.dozeAmount /** Whether the system is in doze mode. */ val isDozing: Flow<Boolean> = repository.isDozing + /** + * Whether the system is dreaming. [isDreaming] will be always be true when [isDozing] is true, + * but not vice-versa. + */ + val isDreaming: Flow<Boolean> = repository.isDreaming /** Whether the keyguard is showing or not. */ val isKeyguardShowing: Flow<Boolean> = repository.isKeyguardShowing + /** Whether the keyguard is going away. */ + val isKeyguardGoingAway: Flow<Boolean> = repository.isKeyguardGoingAway /** Whether the bouncer is showing or not. */ val isBouncerShowing: Flow<Boolean> = repository.isBouncerShowing /** The device wake/sleep state */ 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 57fb4a114700..58a8093d49d2 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 @@ -41,12 +41,24 @@ constructor( } scope.launch { + keyguardInteractor.isBouncerShowing.collect { logger.v("Bouncer showing", it) } + } + + scope.launch { keyguardInteractor.isDozing.collect { logger.v("isDozing", it) } } + + scope.launch { interactor.finishedKeyguardTransitionStep.collect { logger.i("Finished transition", it) } } scope.launch { + interactor.canceledKeyguardTransitionStep.collect { + logger.i("Canceled transition", it) + } + } + + scope.launch { interactor.startedKeyguardTransitionStep.collect { logger.i("Started transition", it) } } } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionCoreStartable.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionCoreStartable.kt index a7c6d4450336..43dd358e4808 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionCoreStartable.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionCoreStartable.kt @@ -42,6 +42,9 @@ constructor( is GoneAodTransitionInteractor -> Log.d(TAG, "Started $it") is LockscreenGoneTransitionInteractor -> Log.d(TAG, "Started $it") is AodToGoneTransitionInteractor -> Log.d(TAG, "Started $it") + is BouncerToGoneTransitionInteractor -> Log.d(TAG, "Started $it") + is DreamingLockscreenTransitionInteractor -> Log.d(TAG, "Started $it") + is DreamingToAodTransitionInteractor -> Log.d(TAG, "Started $it") } it.start() } 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 749183e241e5..54a4f493d21d 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 @@ -57,6 +57,14 @@ constructor( lockscreenToAodTransition, ) + /* The last [TransitionStep] with a [TransitionState] of STARTED */ + val startedKeyguardTransitionStep: Flow<TransitionStep> = + repository.transitions.filter { step -> step.transitionState == TransitionState.STARTED } + + /* The last [TransitionStep] with a [TransitionState] of CANCELED */ + val canceledKeyguardTransitionStep: Flow<TransitionStep> = + repository.transitions.filter { step -> step.transitionState == TransitionState.CANCELED } + /* The last [TransitionStep] with a [TransitionState] of FINISHED */ val finishedKeyguardTransitionStep: Flow<TransitionStep> = repository.transitions.filter { step -> step.transitionState == TransitionState.FINISHED } @@ -64,8 +72,4 @@ constructor( /* The last completed [KeyguardState] transition */ val finishedKeyguardState: Flow<KeyguardState> = finishedKeyguardTransitionStep.map { step -> step.to } - - /* The last [TransitionStep] with a [TransitionState] of STARTED */ - val startedKeyguardTransitionStep: Flow<TransitionStep> = - repository.transitions.filter { step -> step.transitionState == TransitionState.STARTED } } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/LockscreenBouncerTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/LockscreenBouncerTransitionInteractor.kt index fd4814d2bc94..cca2d566556e 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/LockscreenBouncerTransitionInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/LockscreenBouncerTransitionInteractor.kt @@ -56,10 +56,20 @@ constructor( private fun listenForBouncerHiding() { scope.launch { keyguardInteractor.isBouncerShowing - .sample(keyguardInteractor.wakefulnessState, { a, b -> Pair(a, b) }) - .collect { pair -> - val (isBouncerShowing, wakefulnessState) = pair - if (!isBouncerShowing) { + .sample( + combine( + keyguardInteractor.wakefulnessState, + keyguardTransitionInteractor.startedKeyguardTransitionStep, + ) { a, b -> + Pair(a, b) + }, + { a, bc -> Triple(a, bc.first, bc.second) } + ) + .collect { triple -> + val (isBouncerShowing, wakefulnessState, lastStartedTransitionStep) = triple + if ( + !isBouncerShowing && lastStartedTransitionStep.to == KeyguardState.BOUNCER + ) { val to = if ( wakefulnessState == WakefulnessModel.STARTING_TO_SLEEP || @@ -90,10 +100,10 @@ constructor( combine( keyguardTransitionInteractor.finishedKeyguardState, keyguardInteractor.statusBarState, - ) { keyguardState, statusBarState -> - Pair(keyguardState, statusBarState) + ) { a, b -> + Pair(a, b) }, - { shadeModel, pair -> Triple(shadeModel, pair.first, pair.second) } + { a, bc -> Triple(a, bc.first, bc.second) } ) .collect { triple -> val (shadeModel, keyguardState, statusBarState) = triple @@ -116,8 +126,7 @@ constructor( ) } else { // TODO (b/251849525): Remove statusbarstate check when that state is - // integrated - // into KeyguardTransitionRepository + // integrated into KeyguardTransitionRepository if ( keyguardState == KeyguardState.LOCKSCREEN && shadeModel.isUserDragging && diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/LockscreenGoneTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/LockscreenGoneTransitionInteractor.kt index 6c1adbd68ef2..4100f7a8413a 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/LockscreenGoneTransitionInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/LockscreenGoneTransitionInteractor.kt @@ -23,6 +23,7 @@ import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository import com.android.systemui.keyguard.shared.model.KeyguardState import com.android.systemui.keyguard.shared.model.TransitionInfo +import com.android.systemui.util.kotlin.sample import javax.inject.Inject import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.collect @@ -34,23 +35,27 @@ class LockscreenGoneTransitionInteractor constructor( @Application private val scope: CoroutineScope, private val keyguardInteractor: KeyguardInteractor, + private val keyguardTransitionInteractor: KeyguardTransitionInteractor, private val keyguardTransitionRepository: KeyguardTransitionRepository, ) : TransitionInteractor("LOCKSCREEN->GONE") { override fun start() { scope.launch { - keyguardInteractor.isKeyguardShowing.collect { isShowing -> - if (!isShowing) { - keyguardTransitionRepository.startTransition( - TransitionInfo( - name, - KeyguardState.LOCKSCREEN, - KeyguardState.GONE, - getAnimator(), + keyguardInteractor.isKeyguardGoingAway + .sample(keyguardTransitionInteractor.finishedKeyguardState, { a, b -> Pair(a, b) }) + .collect { pair -> + val (isKeyguardGoingAway, keyguardState) = pair + if (!isKeyguardGoingAway && keyguardState == KeyguardState.LOCKSCREEN) { + keyguardTransitionRepository.startTransition( + TransitionInfo( + name, + KeyguardState.LOCKSCREEN, + KeyguardState.GONE, + getAnimator(), + ) ) - ) + } } - } } } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerInteractor.kt index c22f4e7a2634..fcd653b33a6f 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerInteractor.kt @@ -210,9 +210,12 @@ constructor( expansion == KeyguardBouncer.EXPANSION_HIDDEN && oldExpansion != KeyguardBouncer.EXPANSION_HIDDEN ) { - repository.setPrimaryVisible(false) - repository.setPrimaryShow(null) - falsingCollector.onBouncerHidden() + /* + * There are cases where #hide() was not invoked, such as when + * NotificationPanelViewController controls the hide animation. Make sure the state gets + * updated by calling #hide() directly. + */ + hide() DejankUtils.postAfterTraversal { primaryBouncerCallbackInteractor.dispatchReset() } primaryBouncerCallbackInteractor.dispatchFullyHidden() } else if ( diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/StartKeyguardTransitionModule.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/StartKeyguardTransitionModule.kt index 37f33afbf53e..dbffeab436a4 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/StartKeyguardTransitionModule.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/StartKeyguardTransitionModule.kt @@ -46,5 +46,19 @@ abstract class StartKeyguardTransitionModule { @Binds @IntoSet + abstract fun bouncerGone(impl: BouncerToGoneTransitionInteractor): TransitionInteractor + + @Binds + @IntoSet abstract fun lockscreenGone(impl: LockscreenGoneTransitionInteractor): TransitionInteractor + + @Binds + @IntoSet + abstract fun dreamingLockscreen( + impl: DreamingLockscreenTransitionInteractor + ): TransitionInteractor + + @Binds + @IntoSet + abstract fun dreamingToAod(impl: DreamingToAodTransitionInteractor): TransitionInteractor } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/KeyguardState.kt b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/KeyguardState.kt index 7958033ba017..dd908c420fcb 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/KeyguardState.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/KeyguardState.kt @@ -17,12 +17,29 @@ package com.android.systemui.keyguard.shared.model /** List of all possible states to transition to/from */ enum class KeyguardState { + /* + * The display is completely off, as well as any sensors that would trigger the device to wake + * up. + */ + OFF, /** - * For initialization as well as when the security method is set to NONE, indicating that - * the keyguard should never be shown. + * The device has entered a special low-power mode within SystemUI. Doze is technically a + * special dream service implementation. No UI is visible. In this state, a least some + * low-powered sensors such as lift to wake or tap to wake are enabled, or wake screen for + * notifications is enabled, allowing the device to quickly wake up. + */ + DOZING, + /* + * A device state after the device times out, which can be from both LOCKSCREEN or GONE states. + * DOZING is an example of special version of this state. Dreams may be implemented by third + * parties to present their own UI over keyguard, like a screensaver. + */ + DREAMING, + /** + * The device has entered a special low-power mode within SystemUI, also called the Always-on + * Display (AOD). A minimal UI is presented to show critical information. If the device is in + * low-power mode without a UI, then it is DOZING. */ - NONE, - /* Always-on Display. The device is in a low-power mode with a minimal UI visible */ AOD, /* * The security screen prompt UI, containing PIN, Password, Pattern, and all FPS @@ -34,7 +51,6 @@ enum class KeyguardState { * unlocked if SWIPE security method is used, or if face lockscreen bypass is false. */ LOCKSCREEN, - /* * Keyguard is no longer visible. In most cases the user has just authenticated and keyguard * is being removed, but there are other cases where the user is swiping away keyguard, such as diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/TransitionState.kt b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/TransitionState.kt index 0e0465bb5207..38a93b50ea97 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/TransitionState.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/TransitionState.kt @@ -17,7 +17,12 @@ package com.android.systemui.keyguard.shared.model /** Possible states for a running transition between [State] */ enum class TransitionState { + /* Transition has begun. */ STARTED, + /* Transition is actively running. */ RUNNING, - FINISHED + /* Transition has completed successfully. */ + FINISHED, + /* Transition has been interrupted, and not completed successfully. */ + CANCELED, } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/TransitionStep.kt b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/TransitionStep.kt index 732a6f7b887a..767fd58f78e9 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/TransitionStep.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/TransitionStep.kt @@ -17,8 +17,8 @@ package com.android.systemui.keyguard.shared.model /** This information will flow from the [KeyguardTransitionRepository] to control the UI layer */ data class TransitionStep( - val from: KeyguardState = KeyguardState.NONE, - val to: KeyguardState = KeyguardState.NONE, + val from: KeyguardState = KeyguardState.OFF, + val to: KeyguardState = KeyguardState.OFF, val value: Float = 0f, // constrained [0.0, 1.0] val transitionState: TransitionState = TransitionState.FINISHED, val ownerName: String = "", diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/WakefulnessModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/WakefulnessModel.kt index 64f834d6c5ab..92040f4f0348 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/WakefulnessModel.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/WakefulnessModel.kt @@ -24,5 +24,15 @@ enum class WakefulnessModel { /** Device is now fully awake and interactive. */ AWAKE, /** Signal that the device is now going to sleep. */ - STARTING_TO_SLEEP, + STARTING_TO_SLEEP; + + companion object { + fun isSleepingOrStartingToSleep(model: WakefulnessModel): Boolean { + return model == ASLEEP || model == STARTING_TO_SLEEP + } + + fun isWakingOrStartingToWake(model: WakefulnessModel): Boolean { + return model == AWAKE || model == STARTING_TO_WAKE + } + } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java index 532b8b839fdc..5f8da4161092 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java @@ -279,10 +279,7 @@ public class KeyguardBouncer { * @see #onFullyShown() */ private void onFullyHidden() { - cancelShowRunnable(); - setVisibility(View.INVISIBLE); - mFalsingCollector.onBouncerHidden(); - DejankUtils.postAfterTraversal(mResetRunnable); + } private void setVisibility(@View.Visibility int visibility) { @@ -459,7 +456,13 @@ public class KeyguardBouncer { onFullyShown(); dispatchFullyShown(); } else if (fraction == EXPANSION_HIDDEN && oldExpansion != EXPANSION_HIDDEN) { - onFullyHidden(); + DejankUtils.postAfterTraversal(mResetRunnable); + /* + * There are cases where #hide() was not invoked, such as when + * NotificationPanelViewController controls the hide animation. Make sure the state gets + * updated by calling #hide() directly. + */ + hide(false /* destroyView */); dispatchFullyHidden(); } else if (fraction != EXPANSION_VISIBLE && oldExpansion == EXPANSION_VISIBLE) { dispatchStartingToHide(); 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 86e27aba65f0..d54a8638f2e9 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java @@ -895,7 +895,7 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, Dump float stateBehind = mClipsQsScrim ? state.getNotifAlpha() : state.getBehindAlpha(); float behindAlpha; - int behindTint; + int behindTint = state.getBehindTint(); if (mDarkenWhileDragging) { behindAlpha = MathUtils.lerp(mDefaultScrimAlpha, stateBehind, interpolatedFract); @@ -903,12 +903,14 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, Dump behindAlpha = MathUtils.lerp(0 /* start */, stateBehind, interpolatedFract); } - if (mClipsQsScrim) { - behindTint = ColorUtils.blendARGB(ScrimState.BOUNCER.getNotifTint(), + if (mStatusBarKeyguardViewManager.isPrimaryBouncerInTransit()) { + if (mClipsQsScrim) { + behindTint = ColorUtils.blendARGB(ScrimState.BOUNCER.getNotifTint(), state.getNotifTint(), interpolatedFract); - } else { - behindTint = ColorUtils.blendARGB(ScrimState.BOUNCER.getBehindTint(), + } else { + behindTint = ColorUtils.blendARGB(ScrimState.BOUNCER.getBehindTint(), state.getBehindTint(), interpolatedFract); + } } if (mQsExpansion > 0) { behindAlpha = MathUtils.lerp(behindAlpha, mDefaultScrimAlpha, mQsExpansion); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java index 56fb337e5b4f..045173edbe75 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java @@ -165,6 +165,7 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb @Override public void onFullyHidden() { mPrimaryBouncerAnimating = false; + updateStates(); } @Override @@ -1184,12 +1185,16 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb updateNavigationBarVisibility(navBarVisible); } - if (primaryBouncerShowing != mLastPrimaryBouncerShowing || mFirstUpdate) { + boolean isPrimaryBouncerShowingChanged = + primaryBouncerShowing != mLastPrimaryBouncerShowing; + mLastPrimaryBouncerShowing = primaryBouncerShowing; + + if (isPrimaryBouncerShowingChanged || mFirstUpdate) { mNotificationShadeWindowController.setBouncerShowing(primaryBouncerShowing); mCentralSurfaces.setBouncerShowing(primaryBouncerShowing); } if (primaryBouncerIsOrWillBeShowing != mLastPrimaryBouncerIsOrWillBeShowing || mFirstUpdate - || primaryBouncerShowing != mLastPrimaryBouncerShowing) { + || isPrimaryBouncerShowingChanged) { mKeyguardUpdateManager.sendPrimaryBouncerChanged(primaryBouncerIsOrWillBeShowing, primaryBouncerShowing); } @@ -1198,7 +1203,6 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb mLastShowing = showing; mLastGlobalActionsVisible = mGlobalActionsVisible; mLastOccluded = occluded; - mLastPrimaryBouncerShowing = primaryBouncerShowing; mLastPrimaryBouncerIsOrWillBeShowing = primaryBouncerIsOrWillBeShowing; mLastBouncerDismissible = primaryBouncerDismissible; mLastRemoteInputActive = remoteInputActive; 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 ade83cf58e6e..6ba06344314c 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 @@ -18,6 +18,7 @@ package com.android.systemui.keyguard.data.repository import androidx.test.filters.SmallTest import com.android.keyguard.KeyguardUpdateMonitor +import com.android.keyguard.KeyguardUpdateMonitorCallback import com.android.systemui.SysuiTestCase import com.android.systemui.common.shared.model.Position import com.android.systemui.doze.DozeHost @@ -60,12 +61,12 @@ class KeyguardRepositoryImplTest : SysuiTestCase() { underTest = KeyguardRepositoryImpl( - statusBarStateController, - dozeHost, - wakefulnessLifecycle, - biometricUnlockController, - keyguardStateController, - keyguardUpdateMonitor, + statusBarStateController, + dozeHost, + wakefulnessLifecycle, + biometricUnlockController, + keyguardStateController, + keyguardUpdateMonitor, ) } @@ -257,6 +258,48 @@ class KeyguardRepositoryImplTest : SysuiTestCase() { } @Test + fun isKeyguardGoingAway() = runBlockingTest { + whenever(keyguardStateController.isKeyguardGoingAway).thenReturn(false) + var latest: Boolean? = null + val job = underTest.isKeyguardGoingAway.onEach { latest = it }.launchIn(this) + + assertThat(latest).isFalse() + + val captor = argumentCaptor<KeyguardStateController.Callback>() + verify(keyguardStateController).addCallback(captor.capture()) + + whenever(keyguardStateController.isKeyguardGoingAway).thenReturn(true) + captor.value.onKeyguardGoingAwayChanged() + assertThat(latest).isTrue() + + whenever(keyguardStateController.isKeyguardGoingAway).thenReturn(false) + captor.value.onKeyguardGoingAwayChanged() + assertThat(latest).isFalse() + + job.cancel() + } + + @Test + fun isDreaming() = runBlockingTest { + whenever(keyguardUpdateMonitor.isDreaming()).thenReturn(false) + var latest: Boolean? = null + val job = underTest.isDreaming.onEach { latest = it }.launchIn(this) + + assertThat(latest).isFalse() + + val captor = argumentCaptor<KeyguardUpdateMonitorCallback>() + verify(keyguardUpdateMonitor).registerCallback(captor.capture()) + + captor.value.onDreamingStateChanged(true) + assertThat(latest).isTrue() + + captor.value.onDreamingStateChanged(false) + assertThat(latest).isFalse() + + job.cancel() + } + + @Test fun biometricUnlockState() = runBlockingTest { val values = mutableListOf<BiometricUnlockModel>() val job = underTest.biometricUnlockState.onEach(values::add).launchIn(this) diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepositoryTest.kt index 27d5d0a98978..2b03722f9f31 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepositoryTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepositoryTest.kt @@ -25,8 +25,8 @@ import android.view.Choreographer.FrameCallback import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.animation.Interpolators +import com.android.systemui.keyguard.shared.model.KeyguardState import com.android.systemui.keyguard.shared.model.KeyguardState.AOD -import com.android.systemui.keyguard.shared.model.KeyguardState.BOUNCER import com.android.systemui.keyguard.shared.model.KeyguardState.LOCKSCREEN import com.android.systemui.keyguard.shared.model.TransitionInfo import com.android.systemui.keyguard.shared.model.TransitionState @@ -38,7 +38,6 @@ import java.util.UUID import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Job -import kotlinx.coroutines.delay import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.collect import kotlinx.coroutines.flow.launchIn @@ -91,18 +90,51 @@ class KeyguardTransitionRepositoryTest : SysuiTestCase() { } } - assertSteps(steps, listWithStep(BigDecimal(.1))) + assertSteps(steps, listWithStep(BigDecimal(.1)), AOD, LOCKSCREEN) job.cancel() provider.stop() } @Test - fun `startTransition called during another transition fails`() { - underTest.startTransition(TransitionInfo(OWNER_NAME, AOD, LOCKSCREEN, null)) - underTest.startTransition(TransitionInfo(OWNER_NAME, LOCKSCREEN, BOUNCER, null)) + fun `starting second transition will cancel the first transition`() { + runBlocking(IMMEDIATE) { + val (animator, provider) = setupAnimator(this) - assertThat(wtfHandler.failed).isTrue() + val steps = mutableListOf<TransitionStep>() + val job = underTest.transition(AOD, LOCKSCREEN).onEach { steps.add(it) }.launchIn(this) + + underTest.startTransition(TransitionInfo(OWNER_NAME, AOD, LOCKSCREEN, animator)) + // 3 yields(), alternating with the animator, results in a value 0.1, which can be + // canceled and tested against + yield() + yield() + yield() + + // Now start 2nd transition, which will interrupt the first + val job2 = underTest.transition(LOCKSCREEN, AOD).onEach { steps.add(it) }.launchIn(this) + val (animator2, provider2) = setupAnimator(this) + underTest.startTransition(TransitionInfo(OWNER_NAME, LOCKSCREEN, AOD, animator2)) + + val startTime = System.currentTimeMillis() + while (animator2.isRunning()) { + yield() + if (System.currentTimeMillis() - startTime > MAX_TEST_DURATION) { + fail("Failed test due to excessive runtime of: $MAX_TEST_DURATION") + } + } + + 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)) + assertSteps(steps.subList(4, steps.size), secondTransitionSteps, LOCKSCREEN, AOD) + + job.cancel() + job2.cancel() + provider.stop() + provider2.stop() + } } @Test @@ -165,11 +197,15 @@ class KeyguardTransitionRepositoryTest : SysuiTestCase() { assertThat(wtfHandler.failed).isTrue() } - private fun listWithStep(step: BigDecimal): List<BigDecimal> { + private fun listWithStep( + step: BigDecimal, + start: BigDecimal = BigDecimal.ZERO, + stop: BigDecimal = BigDecimal.ONE, + ): List<BigDecimal> { val steps = mutableListOf<BigDecimal>() - var i = BigDecimal.ZERO - while (i.compareTo(BigDecimal.ONE) <= 0) { + var i = start + while (i.compareTo(stop) <= 0) { steps.add(i) i = (i + step).setScale(2, RoundingMode.HALF_UP) } @@ -177,23 +213,43 @@ class KeyguardTransitionRepositoryTest : SysuiTestCase() { return steps } - private fun assertSteps(steps: List<TransitionStep>, fractions: List<BigDecimal>) { + private fun assertSteps( + steps: List<TransitionStep>, + fractions: List<BigDecimal>, + from: KeyguardState, + to: KeyguardState, + ) { assertThat(steps[0]) - .isEqualTo(TransitionStep(AOD, LOCKSCREEN, 0f, TransitionState.STARTED, OWNER_NAME)) + .isEqualTo( + TransitionStep( + from, + to, + fractions[0].toFloat(), + TransitionState.STARTED, + OWNER_NAME + ) + ) fractions.forEachIndexed { index, fraction -> assertThat(steps[index + 1]) .isEqualTo( TransitionStep( - AOD, - LOCKSCREEN, + from, + to, fraction.toFloat(), TransitionState.RUNNING, OWNER_NAME ) ) } + val lastValue = fractions[fractions.size - 1].toFloat() + val status = + if (lastValue < 1f) { + TransitionState.CANCELED + } else { + TransitionState.FINISHED + } assertThat(steps[steps.size - 1]) - .isEqualTo(TransitionStep(AOD, LOCKSCREEN, 1f, TransitionState.FINISHED, OWNER_NAME)) + .isEqualTo(TransitionStep(from, to, lastValue, status, OWNER_NAME)) assertThat(wtfHandler.failed).isFalse() } @@ -230,7 +286,7 @@ class KeyguardTransitionRepositoryTest : SysuiTestCase() { scope.launch { frames.collect { // Delay is required for AnimationHandler to properly register a callback - delay(1) + yield() val (frameNumber, callback) = it callback?.doFrame(frameNumber) } @@ -243,7 +299,7 @@ class KeyguardTransitionRepositoryTest : SysuiTestCase() { } override fun postFrameCallback(cb: FrameCallback) { - frames.value = Pair(++frameCount, cb) + frames.value = Pair(frameCount++, cb) } override fun postCommitCallback(runnable: Runnable) {} override fun getFrameTime() = frameCount diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerInteractorTest.kt index c85f7b9e6885..21e506830d88 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerInteractorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerInteractorTest.kt @@ -155,6 +155,7 @@ class PrimaryBouncerInteractorTest : SysuiTestCase() { mPrimaryBouncerInteractor.setPanelExpansion(EXPANSION_HIDDEN) verify(repository).setPrimaryVisible(false) verify(repository).setPrimaryShow(null) + verify(repository).setPrimaryHide(true) verify(falsingCollector).onBouncerHidden() verify(mPrimaryBouncerCallbackInteractor).dispatchReset() verify(mPrimaryBouncerCallbackInteractor).dispatchFullyHidden() 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 df48e1d43584..808abc8e9de5 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 @@ -1437,6 +1437,17 @@ public class ScrimControllerTest extends SysuiTestCase { } @Test + public void behindTint_inKeyguardState_bouncerNotActive_usesKeyguardBehindTint() { + when(mStatusBarKeyguardViewManager.isPrimaryBouncerInTransit()).thenReturn(false); + mScrimController.setClipsQsScrim(false); + + mScrimController.transitionTo(ScrimState.KEYGUARD); + finishAnimationsImmediately(); + assertThat(mScrimBehind.getTint()) + .isEqualTo(ScrimState.KEYGUARD.getBehindTint()); + } + + @Test public void testNotificationTransparency_followsTransitionToFullShade() { mScrimController.transitionTo(SHADE_LOCKED); mScrimController.setRawPanelExpansionFraction(1.0f); 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 6f70f0ee0f2b..a798f403c73a 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 @@ -44,6 +44,9 @@ class FakeKeyguardRepository : KeyguardRepository { private val _isDozing = MutableStateFlow(false) override val isDozing: Flow<Boolean> = _isDozing + private val _isDreaming = MutableStateFlow(false) + override val isDreaming: Flow<Boolean> = _isDreaming + private val _dozeAmount = MutableStateFlow(0f) override val dozeAmount: Flow<Float> = _dozeAmount @@ -54,10 +57,13 @@ class FakeKeyguardRepository : KeyguardRepository { override val wakefulnessState: Flow<WakefulnessModel> = _wakefulnessState private val _isUdfpsSupported = MutableStateFlow(false) - + private val _isBouncerShowing = MutableStateFlow(false) override val isBouncerShowing: Flow<Boolean> = _isBouncerShowing + private val _isKeyguardGoingAway = MutableStateFlow(false) + override val isKeyguardGoingAway: Flow<Boolean> = _isKeyguardGoingAway + private val _biometricUnlockState = MutableStateFlow(BiometricUnlockModel.NONE) override val biometricUnlockState: Flow<BiometricUnlockModel> = _biometricUnlockState |