From 02dc8128fadb7ed8be3fc8c5815973522d6d64b3 Mon Sep 17 00:00:00 2001 From: Matt Pietal Date: Wed, 26 Oct 2022 20:56:23 +0000 Subject: [PATCH] Transitions - Add GONE state When keyguard is dismissed, transition to a new GONE state. This helps address an issue with the ClockEventController, where the clock was not properly animating back to its AOD state. Adding audit capability via KeyguardTransitionAuditLogger Migrated to MutableSharedFlow. MutableStateFlow was conflating too many events, some of them fairly important. Test: atest ClockEventControllerTest KeyguardRepositoryImplTest KeyguardTransitionRepositoryTest KeyguardTransitionInteractorTest Bug: 195430376 Change-Id: Ic4bd48cea6e783cfdc116fa2776639da0641c100 --- .../shared/clocks/DefaultClockController.kt | 3 + .../android/keyguard/ClockEventController.kt | 25 +- .../keyguard/logging/KeyguardLogger.kt | 9 + .../src/com/android/systemui/flags/Flags.kt | 3 +- .../data/repository/KeyguardRepository.kt | 51 ++++ .../repository/KeyguardRepositoryModule.kt | 5 + .../KeyguardTransitionRepository.kt | 226 ++++++++++-------- .../AodLockscreenTransitionInteractor.kt | 41 ++-- .../interactor/GoneAodTransitionInteractor.kt | 76 ++++++ .../domain/interactor/KeyguardInteractor.kt | 3 + .../KeyguardTransitionAuditLogger.kt | 51 ++++ .../KeyguardTransitionCoreStartable.kt | 4 + .../KeyguardTransitionInteractor.kt | 19 ++ .../LockscreenBouncerTransitionInteractor.kt | 91 +++---- .../LockscreenGoneTransitionInteractor.kt | 67 ++++++ .../StartKeyguardTransitionModule.kt | 6 + .../keyguard/shared/model/KeyguardState.kt | 12 +- .../keyguard/shared/model/TransitionState.kt | 1 - .../keyguard/shared/model/TransitionStep.kt | 10 +- .../keyguard/shared/model/WakefulnessModel.kt | 28 +++ .../repository/KeyguardRepositoryImplTest.kt | 33 +++ .../KeyguardTransitionRepositoryTest.kt | 5 +- .../KeyguardTransitionInteractorTest.kt | 149 ++++++++++++ .../data/repository/FakeKeyguardRepository.kt | 4 + .../FakeKeyguardTransitionRepository.kt | 47 ++++ 25 files changed, 794 insertions(+), 175 deletions(-) create mode 100644 packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/GoneAodTransitionInteractor.kt create mode 100644 packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionAuditLogger.kt create mode 100644 packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/LockscreenGoneTransitionInteractor.kt create mode 100644 packages/SystemUI/src/com/android/systemui/keyguard/shared/model/WakefulnessModel.kt create mode 100644 packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorTest.kt create mode 100644 packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardTransitionRepository.kt diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/clocks/DefaultClockController.kt b/packages/SystemUI/shared/src/com/android/systemui/shared/clocks/DefaultClockController.kt index da1d233949cfd..3961438ff5919 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/clocks/DefaultClockController.kt +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/clocks/DefaultClockController.kt @@ -237,6 +237,9 @@ class DefaultClockController( ) { var isActive: Boolean = fraction < 0.5f fun update(newFraction: Float): Pair { + if (newFraction == fraction) { + return Pair(isActive, false) + } val wasActive = isActive val hasJumped = (fraction == 0f && newFraction == 1f) || (fraction == 1f && newFraction == 0f) diff --git a/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt b/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt index 40a96b060bc0c..79537678e2ad1 100644 --- a/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt +++ b/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt @@ -34,6 +34,7 @@ import com.android.systemui.flags.Flags.DOZING_MIGRATION_1 import com.android.systemui.flags.Flags.REGION_SAMPLING import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor +import com.android.systemui.keyguard.shared.model.TransitionState import com.android.systemui.lifecycle.repeatWhenAttached import com.android.systemui.log.dagger.KeyguardClockLog import com.android.systemui.plugins.ClockController @@ -52,6 +53,7 @@ import kotlinx.coroutines.DisposableHandle import kotlinx.coroutines.Job import kotlinx.coroutines.flow.collect import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.flow.filter import kotlinx.coroutines.launch /** @@ -186,8 +188,10 @@ open class ClockEventController @Inject constructor( private val keyguardUpdateMonitorCallback = object : KeyguardUpdateMonitorCallback() { override fun onKeyguardVisibilityChanged(visible: Boolean) { isKeyguardVisible = visible - if (!isKeyguardVisible) { - clock?.animations?.doze(if (isDozing) 1f else 0f) + if (!featureFlags.isEnabled(DOZING_MIGRATION_1)) { + if (!isKeyguardVisible) { + clock?.animations?.doze(if (isDozing) 1f else 0f) + } } } @@ -224,6 +228,7 @@ open class ClockEventController @Inject constructor( listenForDozing(this) if (featureFlags.isEnabled(DOZING_MIGRATION_1)) { listenForDozeAmountTransition(this) + listenForGoneToAodTransition(this) } else { listenForDozeAmount(this) } @@ -276,6 +281,22 @@ open class ClockEventController @Inject constructor( } } + /** + * When keyguard is displayed again after being gone, the clock must be reset to full + * dozing. + */ + @VisibleForTesting + internal fun listenForGoneToAodTransition(scope: CoroutineScope): Job { + return scope.launch { + keyguardTransitionInteractor.goneToAodTransition.filter { + it.transitionState == TransitionState.STARTED + }.collect { + dozeAmount = 1f + clock?.animations?.doze(dozeAmount) + } + } + } + @VisibleForTesting internal fun listenForDozing(scope: CoroutineScope): Job { return scope.launch { diff --git a/packages/SystemUI/src/com/android/keyguard/logging/KeyguardLogger.kt b/packages/SystemUI/src/com/android/keyguard/logging/KeyguardLogger.kt index 46f3d4e5f6aa8..32ce537ea25a0 100644 --- a/packages/SystemUI/src/com/android/keyguard/logging/KeyguardLogger.kt +++ b/packages/SystemUI/src/com/android/keyguard/logging/KeyguardLogger.kt @@ -21,6 +21,7 @@ import com.android.systemui.plugins.log.LogBuffer import com.android.systemui.plugins.log.LogLevel import com.android.systemui.plugins.log.LogLevel.DEBUG import com.android.systemui.plugins.log.LogLevel.ERROR +import com.android.systemui.plugins.log.LogLevel.INFO import com.android.systemui.plugins.log.LogLevel.VERBOSE import com.android.systemui.plugins.log.LogLevel.WARNING import com.android.systemui.plugins.log.MessageInitializer @@ -50,6 +51,14 @@ class KeyguardLogger @Inject constructor(@KeyguardLog private val buffer: LogBuf buffer.log(TAG, DEBUG, messageInitializer, messagePrinter) } + fun v(msg: String, arg: Any) { + buffer.log(TAG, VERBOSE, { str1 = arg.toString() }, { "$msg: $str1" }) + } + + fun i(msg: String, arg: Any) { + buffer.log(TAG, INFO, { str1 = arg.toString() }, { "$msg: $str1" }) + } + // TODO: remove after b/237743330 is fixed fun logStatusBarCalculatedAlpha(alpha: Float) { debugLog({ double1 = alpha.toDouble() }, { "Calculated new alpha: $double1" }) diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt index 86ea251717fc2..3347b650ccd07 100644 --- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt +++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt @@ -122,7 +122,8 @@ object Flags { * Migration from the legacy isDozing/dozeAmount paths to the new KeyguardTransitionRepository * will occur in stages. This is one stage of many to come. */ - @JvmField val DOZING_MIGRATION_1 = UnreleasedFlag(213, teamfood = true) + // TODO(b/255607168): Tracking Bug + @JvmField val DOZING_MIGRATION_1 = UnreleasedFlag(213) // 300 - power menu // TODO(b/254512600): Tracking Bug 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 b186ae0ceec4d..6baaf5f10024b 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 @@ -21,7 +21,10 @@ import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCall import com.android.systemui.common.shared.model.Position import com.android.systemui.dagger.SysUISingleton import com.android.systemui.doze.DozeHost +import com.android.systemui.keyguard.WakefulnessLifecycle +import com.android.systemui.keyguard.WakefulnessLifecycle.Wakefulness import com.android.systemui.keyguard.shared.model.StatusBarState +import com.android.systemui.keyguard.shared.model.WakefulnessModel import com.android.systemui.plugins.statusbar.StatusBarStateController import com.android.systemui.statusbar.policy.KeyguardStateController import javax.inject.Inject @@ -89,6 +92,9 @@ interface KeyguardRepository { /** Observable for the [StatusBarState] */ val statusBarState: Flow + /** Observable for device wake/sleep state */ + val wakefulnessState: Flow + /** * Returns `true` if the keyguard is showing; `false` otherwise. * @@ -118,6 +124,7 @@ constructor( statusBarStateController: StatusBarStateController, private val keyguardStateController: KeyguardStateController, dozeHost: DozeHost, + wakefulnessLifecycle: WakefulnessLifecycle, ) : KeyguardRepository { private val _animateBottomAreaDozingTransitions = MutableStateFlow(false) override val animateBottomAreaDozingTransitions = @@ -207,6 +214,40 @@ constructor( awaitClose { statusBarStateController.removeCallback(callback) } } + override val wakefulnessState: Flow = conflatedCallbackFlow { + val callback = + object : WakefulnessLifecycle.Observer { + override fun onStartedWakingUp() { + trySendWithFailureLogging( + WakefulnessModel.STARTING_TO_WAKE, + TAG, + "Wakefulness: starting to wake" + ) + } + override fun onFinishedWakingUp() { + trySendWithFailureLogging(WakefulnessModel.AWAKE, TAG, "Wakefulness: awake") + } + override fun onStartedGoingToSleep() { + trySendWithFailureLogging( + WakefulnessModel.STARTING_TO_SLEEP, + TAG, + "Wakefulness: starting to sleep" + ) + } + override fun onFinishedGoingToSleep() { + trySendWithFailureLogging(WakefulnessModel.ASLEEP, TAG, "Wakefulness: asleep") + } + } + wakefulnessLifecycle.addObserver(callback) + trySendWithFailureLogging( + wakefulnessIntToObject(wakefulnessLifecycle.getWakefulness()), + TAG, + "initial wakefulness state" + ) + + awaitClose { wakefulnessLifecycle.removeObserver(callback) } + } + override fun setAnimateDozingTransitions(animate: Boolean) { _animateBottomAreaDozingTransitions.value = animate } @@ -228,6 +269,16 @@ constructor( } } + private fun wakefulnessIntToObject(@Wakefulness value: Int): WakefulnessModel { + return when (value) { + 0 -> WakefulnessModel.ASLEEP + 1 -> WakefulnessModel.STARTING_TO_WAKE + 2 -> WakefulnessModel.AWAKE + 3 -> WakefulnessModel.STARTING_TO_SLEEP + else -> throw IllegalArgumentException("Invalid Wakefulness value: $value") + } + } + companion object { private const val TAG = "KeyguardRepositoryImpl" } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryModule.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryModule.kt index d15d7f25bbdec..0c725208e22dc 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryModule.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryModule.kt @@ -22,4 +22,9 @@ import dagger.Module @Module interface KeyguardRepositoryModule { @Binds fun keyguardRepository(impl: KeyguardRepositoryImpl): KeyguardRepository + + @Binds + fun keyguardTransitionRepository( + impl: KeyguardTransitionRepositoryImpl + ): KeyguardTransitionRepository } 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 ab25597b077cb..e3d1a27dad2b7 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 @@ -29,27 +29,33 @@ import com.android.systemui.keyguard.shared.model.TransitionState import com.android.systemui.keyguard.shared.model.TransitionStep import java.util.UUID import javax.inject.Inject +import kotlinx.coroutines.channels.BufferOverflow import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.flow.MutableSharedFlow +import kotlinx.coroutines.flow.asSharedFlow +import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.filter -@SysUISingleton -class KeyguardTransitionRepository @Inject constructor() { - /* - * Each transition between [KeyguardState]s will have an associated Flow. - * In order to collect these events, clients should call [transition]. +/** + * The source of truth for all keyguard transitions. + * + * While the keyguard component is visible, it can undergo a number of transitions between different + * UI screens, such as AOD (Always-on Display), Bouncer, and others mentioned in [KeyguardState]. + * These UI elements should listen to events emitted by [transitions], to ensure a centrally + * coordinated experience. + * + * To create or modify logic that controls when and how transitions get created, look at + * [TransitionInteractor]. These interactors will call [startTransition] and [updateTransition] on + * this repository. + */ +interface KeyguardTransitionRepository { + /** + * All events regarding transitions, as they start, run, and complete. [TransitionStep#value] is + * a float between [0, 1] representing progress towards completion. If this is a user driven + * transition, that value may not be a monotonic progression, as the user may swipe in any + * direction. */ - private val _transitions = MutableStateFlow(TransitionStep()) - val transitions = _transitions.asStateFlow() - - /* Information about the active transition. */ - private var currentTransitionInfo: TransitionInfo? = null - /* - * When manual control of the transition is requested, a unique [UUID] is used as the handle - * to permit calls to [updateTransition] - */ - private var updateTransitionId: UUID? = null + val transitions: Flow /** * Interactors that require information about changes between [KeyguardState]s will call this to @@ -60,65 +66,10 @@ class KeyguardTransitionRepository @Inject constructor() { } /** - * Begin a transition from one state to another. The [info.from] must match - * [currentTransitionInfo.to], or the request will be denied. This is enforced to avoid - * unplanned transitions. + * Begin a transition from one state to another. Will not start if another transition is in + * progress. */ - fun startTransition(info: TransitionInfo): UUID? { - if (currentTransitionInfo != null) { - // 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: $currentTransitionInfo") - return null - } - currentTransitionInfo?.animator?.cancel() - - currentTransitionInfo = info - info.animator?.let { animator -> - // An animator was provided, so use it to run the transition - animator.setFloatValues(0f, 1f) - val updateListener = - object : AnimatorUpdateListener { - override fun onAnimationUpdate(animation: ValueAnimator) { - emitTransition( - info, - (animation.getAnimatedValue() as Float), - TransitionState.RUNNING - ) - } - } - val adapter = - object : AnimatorListenerAdapter() { - override fun onAnimationStart(animation: Animator) { - Log.i(TAG, "Starting transition: $info") - emitTransition(info, 0f, TransitionState.STARTED) - } - override fun onAnimationCancel(animation: Animator) { - Log.i(TAG, "Cancelling transition: $info") - } - override fun onAnimationEnd(animation: Animator) { - Log.i(TAG, "Ending transition: $info") - emitTransition(info, 1f, TransitionState.FINISHED) - animator.removeListener(this) - animator.removeUpdateListener(updateListener) - } - } - animator.addListener(adapter) - animator.addUpdateListener(updateListener) - animator.start() - return@startTransition null - } - ?: run { - Log.i(TAG, "Starting transition (manual): $info") - emitTransition(info, 0f, TransitionState.STARTED) - - // No animator, so it's manual. Provide a mechanism to callback - updateTransitionId = UUID.randomUUID() - return@startTransition updateTransitionId - } - } + fun startTransition(info: TransitionInfo): UUID? /** * Allows manual control of a transition. When calling [startTransition], the consumer must pass @@ -132,58 +83,127 @@ class KeyguardTransitionRepository @Inject constructor() { transitionId: UUID, @FloatRange(from = 0.0, to = 1.0) value: Float, state: TransitionState + ) +} + +@SysUISingleton +class KeyguardTransitionRepositoryImpl @Inject constructor() : KeyguardTransitionRepository { + /* + * Each transition between [KeyguardState]s will have an associated Flow. + * In order to collect these events, clients should call [transition]. + */ + private val _transitions = + MutableSharedFlow( + extraBufferCapacity = 10, + onBufferOverflow = BufferOverflow.DROP_OLDEST + ) + override val transitions = _transitions.asSharedFlow().distinctUntilChanged() + private var lastStep: TransitionStep = TransitionStep() + + /* + * When manual control of the transition is requested, a unique [UUID] is used as the handle + * to permit calls to [updateTransition] + */ + private var updateTransitionId: UUID? = null + + 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 + } + + info.animator?.let { animator -> + // An animator was provided, so use it to run the transition + animator.setFloatValues(0f, 1f) + val updateListener = + object : AnimatorUpdateListener { + override fun onAnimationUpdate(animation: ValueAnimator) { + emitTransition( + TransitionStep( + info, + (animation.getAnimatedValue() as Float), + TransitionState.RUNNING + ) + ) + } + } + val adapter = + object : AnimatorListenerAdapter() { + override fun onAnimationStart(animation: Animator) { + emitTransition(TransitionStep(info, 0f, TransitionState.STARTED)) + } + override fun onAnimationCancel(animation: Animator) { + Log.i(TAG, "Cancelling transition: $info") + } + override fun onAnimationEnd(animation: Animator) { + emitTransition(TransitionStep(info, 1f, TransitionState.FINISHED)) + animator.removeListener(this) + animator.removeUpdateListener(updateListener) + } + } + animator.addListener(adapter) + animator.addUpdateListener(updateListener) + animator.start() + return@startTransition null + } + ?: run { + emitTransition(TransitionStep(info, 0f, TransitionState.STARTED)) + + // No animator, so it's manual. Provide a mechanism to callback + updateTransitionId = UUID.randomUUID() + return@startTransition updateTransitionId + } + } + + override fun updateTransition( + transitionId: UUID, + @FloatRange(from = 0.0, to = 1.0) value: Float, + state: TransitionState ) { if (updateTransitionId != transitionId) { Log.wtf(TAG, "Attempting to update with old/invalid transitionId: $transitionId") return } - if (currentTransitionInfo == null) { - Log.wtf(TAG, "Attempting to update with null 'currentTransitionInfo'") - return + if (state == TransitionState.FINISHED) { + updateTransitionId = null } - currentTransitionInfo?.let { info -> - if (state == TransitionState.FINISHED) { - updateTransitionId = null - Log.i(TAG, "Ending transition: $info") - } - - emitTransition(info, value, state) - } + val nextStep = lastStep.copy(value = value, transitionState = state) + emitTransition(nextStep, isManual = true) } - private fun emitTransition( - info: TransitionInfo, - value: Float, - transitionState: TransitionState - ) { - trace(info, transitionState) - - if (transitionState == TransitionState.FINISHED) { - currentTransitionInfo = null + private fun emitTransition(nextStep: TransitionStep, isManual: Boolean = false) { + trace(nextStep, isManual) + val emitted = _transitions.tryEmit(nextStep) + if (!emitted) { + Log.w(TAG, "Failed to emit next value without suspending") } - _transitions.value = TransitionStep(info.from, info.to, value, transitionState) + lastStep = nextStep } - private fun trace(info: TransitionInfo, transitionState: TransitionState) { + private fun trace(step: TransitionStep, isManual: Boolean) { if ( - transitionState != TransitionState.STARTED && - transitionState != TransitionState.FINISHED + step.transitionState != TransitionState.STARTED && + step.transitionState != TransitionState.FINISHED ) { return } val traceName = - "Transition: ${info.from} -> ${info.to} " + - if (info.animator == null) { + "Transition: ${step.from} -> ${step.to} " + + if (isManual) { "(manual)" } else { "" } val traceCookie = traceName.hashCode() - if (transitionState == TransitionState.STARTED) { + if (step.transitionState == TransitionState.STARTED) { Trace.beginAsyncSection(traceName, traceCookie) - } else if (transitionState == TransitionState.FINISHED) { + } else if (step.transitionState == TransitionState.FINISHED) { Trace.endAsyncSection(traceName, traceCookie) } } 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 400376663f1af..0aeff7fc69fd9 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 @@ -24,6 +24,7 @@ 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.util.kotlin.sample import javax.inject.Inject import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.collect @@ -36,31 +37,35 @@ constructor( @Application private val scope: CoroutineScope, private val keyguardRepository: KeyguardRepository, private val keyguardTransitionRepository: KeyguardTransitionRepository, + private val keyguardTransitionInteractor: KeyguardTransitionInteractor, ) : TransitionInteractor("AOD<->LOCKSCREEN") { override fun start() { scope.launch { - keyguardRepository.isDozing.collect { isDozing -> - if (isDozing) { - keyguardTransitionRepository.startTransition( - TransitionInfo( - name, - KeyguardState.LOCKSCREEN, - KeyguardState.AOD, - getAnimator(), + keyguardRepository.isDozing + .sample(keyguardTransitionInteractor.finishedKeyguardState, { a, b -> Pair(a, b) }) + .collect { pair -> + val (isDozing, keyguardState) = pair + if (isDozing && keyguardState == KeyguardState.LOCKSCREEN) { + keyguardTransitionRepository.startTransition( + TransitionInfo( + name, + KeyguardState.LOCKSCREEN, + KeyguardState.AOD, + getAnimator(), + ) ) - ) - } else { - keyguardTransitionRepository.startTransition( - TransitionInfo( - name, - KeyguardState.AOD, - KeyguardState.LOCKSCREEN, - getAnimator(), + } else if (!isDozing && keyguardState == KeyguardState.AOD) { + keyguardTransitionRepository.startTransition( + TransitionInfo( + name, + KeyguardState.AOD, + KeyguardState.LOCKSCREEN, + getAnimator(), + ) ) - ) + } } - } } } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/GoneAodTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/GoneAodTransitionInteractor.kt new file mode 100644 index 0000000000000..0e2a54c57bdb4 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/GoneAodTransitionInteractor.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 +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 GoneAodTransitionInteractor +@Inject +constructor( + @Application private val scope: CoroutineScope, + private val keyguardInteractor: KeyguardInteractor, + private val keyguardTransitionRepository: KeyguardTransitionRepository, + private val keyguardTransitionInteractor: KeyguardTransitionInteractor, +) : TransitionInteractor("GONE->AOD") { + + override fun start() { + scope.launch { + keyguardInteractor.wakefulnessState + .sample(keyguardTransitionInteractor.finishedKeyguardState, { a, b -> Pair(a, b) }) + .collect { pair -> + val (wakefulnessState, keyguardState) = pair + if ( + keyguardState == KeyguardState.GONE && + wakefulnessState == WakefulnessModel.STARTING_TO_SLEEP + ) { + keyguardTransitionRepository.startTransition( + TransitionInfo( + name, + KeyguardState.GONE, + 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 = 500L + } +} 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 fc2269c6b01ca..03c6a789326e4 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 @@ -19,6 +19,7 @@ package com.android.systemui.keyguard.domain.interactor import com.android.systemui.dagger.SysUISingleton import com.android.systemui.keyguard.data.repository.KeyguardRepository +import com.android.systemui.keyguard.shared.model.WakefulnessModel import javax.inject.Inject import kotlinx.coroutines.flow.Flow @@ -40,6 +41,8 @@ constructor( val isDozing: Flow = repository.isDozing /** Whether the keyguard is showing to not. */ val isKeyguardShowing: Flow = repository.isKeyguardShowing + /** The device wake/sleep state */ + val wakefulnessState: Flow = repository.wakefulnessState fun isKeyguardShowing(): Boolean { return repository.isKeyguardShowing() 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 new file mode 100644 index 0000000000000..83d94325b9d49 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionAuditLogger.kt @@ -0,0 +1,51 @@ +/* + * 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 com.android.keyguard.logging.KeyguardLogger +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.dagger.qualifiers.Application +import javax.inject.Inject +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.flow.collect +import kotlinx.coroutines.launch + +/** Collect flows of interest for auditing keyguard transitions. */ +@SysUISingleton +class KeyguardTransitionAuditLogger +@Inject +constructor( + @Application private val scope: CoroutineScope, + private val interactor: KeyguardTransitionInteractor, + private val keyguardInteractor: KeyguardInteractor, + private val logger: KeyguardLogger, +) { + + fun start() { + scope.launch { + keyguardInteractor.wakefulnessState.collect { logger.v("WakefulnessState", it) } + } + + scope.launch { + interactor.finishedKeyguardState.collect { logger.i("Finished transition to", it) } + } + + scope.launch { + interactor.startedKeyguardState.collect { logger.i("Started transition to", 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 b166681433a8b..d5ea77b8e729d 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 @@ -26,6 +26,7 @@ class KeyguardTransitionCoreStartable @Inject constructor( private val interactors: Set, + private val auditLogger: KeyguardTransitionAuditLogger, ) : CoreStartable { override fun start() { @@ -38,9 +39,12 @@ constructor( when (it) { is LockscreenBouncerTransitionInteractor -> Log.d(TAG, "Started $it") is AodLockscreenTransitionInteractor -> Log.d(TAG, "Started $it") + is GoneAodTransitionInteractor -> Log.d(TAG, "Started $it") + is LockscreenGoneTransitionInteractor -> Log.d(TAG, "Started $it") } it.start() } + auditLogger.start() } companion object { 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 7409aec57b4c9..dffd097a77c5b 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 @@ -19,11 +19,15 @@ package com.android.systemui.keyguard.domain.interactor import com.android.systemui.dagger.SysUISingleton import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository +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.GONE import com.android.systemui.keyguard.shared.model.KeyguardState.LOCKSCREEN +import com.android.systemui.keyguard.shared.model.TransitionState import com.android.systemui.keyguard.shared.model.TransitionStep import javax.inject.Inject import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.filter import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.merge @@ -40,6 +44,9 @@ constructor( /** LOCKSCREEN->AOD transition information. */ val lockscreenToAodTransition: Flow = repository.transition(LOCKSCREEN, AOD) + /** GONE->AOD information. */ + val goneToAodTransition: Flow = repository.transition(GONE, AOD) + /** * AOD<->LOCKSCREEN transition information, mapped to dozeAmount range of AOD (1f) <-> * Lockscreen (0f). @@ -49,4 +56,16 @@ constructor( aodToLockscreenTransition.map { step -> step.copy(value = 1f - step.value) }, lockscreenToAodTransition, ) + + /* The last completed [KeyguardState] transition */ + val finishedKeyguardState: Flow = + repository.transitions + .filter { step -> step.transitionState == TransitionState.FINISHED } + .map { step -> step.to } + + /* The last started [KeyguardState] transition */ + val startedKeyguardState: Flow = + repository.transitions + .filter { step -> step.transitionState == TransitionState.STARTED } + .map { step -> step.to } } 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 3c2a12e3836a4..761f3fd9d9f95 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 @@ -29,6 +29,7 @@ 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.flow.combine import kotlinx.coroutines.launch @@ -40,59 +41,63 @@ constructor( private val keyguardRepository: KeyguardRepository, private val shadeRepository: ShadeRepository, private val keyguardTransitionRepository: KeyguardTransitionRepository, + private val keyguardTransitionInteractor: KeyguardTransitionInteractor ) : TransitionInteractor("LOCKSCREEN<->BOUNCER") { private var transitionId: UUID? = null override fun start() { scope.launch { - shadeRepository.shadeModel.sample( - combine( - keyguardTransitionRepository.transitions, - keyguardRepository.statusBarState, - ) { transitions, statusBarState -> - Pair(transitions, statusBarState) - } - ) { shadeModel, pair -> - val (transitions, statusBarState) = pair + shadeRepository.shadeModel + .sample( + combine( + keyguardTransitionInteractor.finishedKeyguardState, + keyguardRepository.statusBarState, + ) { keyguardState, statusBarState -> + Pair(keyguardState, statusBarState) + }, + { shadeModel, pair -> Triple(shadeModel, pair.first, pair.second) } + ) + .collect { triple -> + val (shadeModel, keyguardState, statusBarState) = triple - val id = transitionId - if (id != null) { - // An existing `id` means a transition is started, and calls to - // `updateTransition` will control it until FINISHED - keyguardTransitionRepository.updateTransition( - id, - shadeModel.expansionAmount, - if (shadeModel.expansionAmount == 0f || shadeModel.expansionAmount == 1f) { - transitionId = null - TransitionState.FINISHED - } else { - TransitionState.RUNNING - } - ) - } else { - // TODO (b/251849525): Remove statusbarstate check when that state is integrated - // into KeyguardTransitionRepository - val isOnLockscreen = - transitions.transitionState == TransitionState.FINISHED && - transitions.to == KeyguardState.LOCKSCREEN - if ( - isOnLockscreen && - shadeModel.isUserDragging && - statusBarState != SHADE_LOCKED - ) { - transitionId = - keyguardTransitionRepository.startTransition( - TransitionInfo( - ownerName = name, - from = KeyguardState.LOCKSCREEN, - to = KeyguardState.BOUNCER, - animator = null, + val id = transitionId + if (id != null) { + // An existing `id` means a transition is started, and calls to + // `updateTransition` will control it until FINISHED + keyguardTransitionRepository.updateTransition( + id, + shadeModel.expansionAmount, + if ( + shadeModel.expansionAmount == 0f || shadeModel.expansionAmount == 1f + ) { + transitionId = null + TransitionState.FINISHED + } else { + TransitionState.RUNNING + } + ) + } else { + // TODO (b/251849525): Remove statusbarstate check when that state is + // integrated + // into KeyguardTransitionRepository + if ( + keyguardState == KeyguardState.LOCKSCREEN && + shadeModel.isUserDragging && + statusBarState != SHADE_LOCKED + ) { + transitionId = + keyguardTransitionRepository.startTransition( + TransitionInfo( + ownerName = name, + from = KeyguardState.LOCKSCREEN, + to = KeyguardState.BOUNCER, + animator = null, + ) ) - ) + } } } - } } } } 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 new file mode 100644 index 0000000000000..6c1adbd68ef2b --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/LockscreenGoneTransitionInteractor.kt @@ -0,0 +1,67 @@ +/* + * 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 javax.inject.Inject +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.flow.collect +import kotlinx.coroutines.launch + +@SysUISingleton +class LockscreenGoneTransitionInteractor +@Inject +constructor( + @Application private val scope: CoroutineScope, + private val keyguardInteractor: KeyguardInteractor, + 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(), + ) + ) + } + } + } + } + + private fun getAnimator(): ValueAnimator { + return ValueAnimator().apply { + setInterpolator(Interpolators.LINEAR) + setDuration(TRANSITION_DURATION_MS) + } + } + + companion object { + private const val TRANSITION_DURATION_MS = 10L + } +} 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 74c542c0043f6..728bafae2a4c2 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 @@ -39,4 +39,10 @@ abstract class StartKeyguardTransitionModule { @Binds @IntoSet abstract fun aodLockscreen(impl: AodLockscreenTransitionInteractor): TransitionInteractor + + @Binds @IntoSet abstract fun goneAod(impl: GoneAodTransitionInteractor): TransitionInteractor + + @Binds + @IntoSet + abstract fun lockscreenGone(impl: LockscreenGoneTransitionInteractor): 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 f66d5d3650c8d..7958033ba0173 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,7 +17,10 @@ package com.android.systemui.keyguard.shared.model /** List of all possible states to transition to/from */ enum class KeyguardState { - /** For initialization only */ + /** + * For initialization as well as when the security method is set to NONE, indicating that + * the keyguard should never be shown. + */ NONE, /* Always-on Display. The device is in a low-power mode with a minimal UI visible */ AOD, @@ -31,4 +34,11 @@ 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 + * with SWIPE security method or face unlock without bypass. + */ + GONE, } 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 d8691c17f53db..0e0465bb52076 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,6 @@ package com.android.systemui.keyguard.shared.model /** Possible states for a running transition between [State] */ enum class TransitionState { - NONE, STARTED, RUNNING, FINISHED 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 688ec912aac8c..0ca358210813d 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 @@ -20,5 +20,11 @@ data class TransitionStep( val from: KeyguardState = KeyguardState.NONE, val to: KeyguardState = KeyguardState.NONE, val value: Float = 0f, // constrained [0.0, 1.0] - val transitionState: TransitionState = TransitionState.NONE, -) + val transitionState: TransitionState = TransitionState.FINISHED, +) { + constructor( + info: TransitionInfo, + value: Float, + transitionState: TransitionState, + ) : this(info.from, info.to, value, transitionState) +} 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 new file mode 100644 index 0000000000000..64f834d6c5ab0 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/WakefulnessModel.kt @@ -0,0 +1,28 @@ +/* + * 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.shared.model + +/** Model device wakefulness states. */ +enum class WakefulnessModel { + /** The device is asleep and not interactive. */ + ASLEEP, + /** Received a signal that the device is beginning to wake up. */ + STARTING_TO_WAKE, + /** Device is now fully awake and interactive. */ + AWAKE, + /** Signal that the device is now going to sleep. */ + STARTING_TO_SLEEP, +} 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 7a1568098e4ab..b9ab9d37a805f 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 @@ -20,6 +20,8 @@ import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.common.shared.model.Position import com.android.systemui.doze.DozeHost +import com.android.systemui.keyguard.WakefulnessLifecycle +import com.android.systemui.keyguard.shared.model.WakefulnessModel import com.android.systemui.plugins.statusbar.StatusBarStateController import com.android.systemui.statusbar.policy.KeyguardStateController import com.android.systemui.util.mockito.argumentCaptor @@ -43,6 +45,7 @@ class KeyguardRepositoryImplTest : SysuiTestCase() { @Mock private lateinit var statusBarStateController: StatusBarStateController @Mock private lateinit var dozeHost: DozeHost @Mock private lateinit var keyguardStateController: KeyguardStateController + @Mock private lateinit var wakefulnessLifecycle: WakefulnessLifecycle private lateinit var underTest: KeyguardRepositoryImpl @@ -55,6 +58,7 @@ class KeyguardRepositoryImplTest : SysuiTestCase() { statusBarStateController, keyguardStateController, dozeHost, + wakefulnessLifecycle, ) } @@ -184,4 +188,33 @@ class KeyguardRepositoryImplTest : SysuiTestCase() { job.cancel() verify(statusBarStateController).removeCallback(captor.value) } + + @Test + fun wakefullness() = runBlockingTest { + val values = mutableListOf() + val job = underTest.wakefulnessState.onEach(values::add).launchIn(this) + + val captor = argumentCaptor() + verify(wakefulnessLifecycle).addObserver(captor.capture()) + + captor.value.onStartedWakingUp() + captor.value.onFinishedWakingUp() + captor.value.onStartedGoingToSleep() + captor.value.onFinishedGoingToSleep() + + assertThat(values) + .isEqualTo( + listOf( + // Initial value will be ASLEEP + WakefulnessModel.ASLEEP, + WakefulnessModel.STARTING_TO_WAKE, + WakefulnessModel.AWAKE, + WakefulnessModel.STARTING_TO_SLEEP, + WakefulnessModel.ASLEEP, + ) + ) + + job.cancel() + verify(wakefulnessLifecycle).removeObserver(captor.value) + } } 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 1b34100b1cef1..64913c7c28c21 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 @@ -63,7 +63,7 @@ class KeyguardTransitionRepositoryTest : SysuiTestCase() { @Before fun setUp() { - underTest = KeyguardTransitionRepository() + underTest = KeyguardTransitionRepositoryImpl() wtfHandler = WtfHandler() oldWtfHandler = Log.setWtfHandler(wtfHandler) } @@ -174,9 +174,6 @@ class KeyguardTransitionRepositoryTest : SysuiTestCase() { } private fun assertSteps(steps: List, fractions: List) { - // + 2 accounts for start and finish of automated transition - assertThat(steps.size).isEqualTo(fractions.size + 2) - assertThat(steps[0]).isEqualTo(TransitionStep(AOD, LOCKSCREEN, 0f, TransitionState.STARTED)) fractions.forEachIndexed { index, fraction -> assertThat(steps[index + 1]) diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorTest.kt new file mode 100644 index 0000000000000..0424c28a1c248 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorTest.kt @@ -0,0 +1,149 @@ +/* + * 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 androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository +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.GONE +import com.android.systemui.keyguard.shared.model.KeyguardState.LOCKSCREEN +import com.android.systemui.keyguard.shared.model.TransitionState.FINISHED +import com.android.systemui.keyguard.shared.model.TransitionState.RUNNING +import com.android.systemui.keyguard.shared.model.TransitionState.STARTED +import com.android.systemui.keyguard.shared.model.TransitionStep +import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.onEach +import kotlinx.coroutines.runBlocking +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.junit.runners.JUnit4 + +@SmallTest +@RunWith(JUnit4::class) +class KeyguardTransitionInteractorTest : SysuiTestCase() { + + private lateinit var underTest: KeyguardTransitionInteractor + private lateinit var repository: FakeKeyguardTransitionRepository + + @Before + fun setUp() { + repository = FakeKeyguardTransitionRepository() + underTest = KeyguardTransitionInteractor(repository) + } + + @Test + fun `transition collectors receives only appropriate events`() = + runBlocking(IMMEDIATE) { + var goneToAodSteps = mutableListOf() + val job1 = + underTest.goneToAodTransition.onEach { goneToAodSteps.add(it) }.launchIn(this) + + var aodToLockscreenSteps = mutableListOf() + val job2 = + underTest.aodToLockscreenTransition + .onEach { aodToLockscreenSteps.add(it) } + .launchIn(this) + + val steps = mutableListOf() + steps.add(TransitionStep(AOD, GONE, 0f, STARTED)) + steps.add(TransitionStep(AOD, GONE, 1f, FINISHED)) + steps.add(TransitionStep(AOD, LOCKSCREEN, 0f, STARTED)) + steps.add(TransitionStep(AOD, LOCKSCREEN, 0.5f, RUNNING)) + steps.add(TransitionStep(AOD, LOCKSCREEN, 1f, FINISHED)) + steps.add(TransitionStep(GONE, AOD, 0f, STARTED)) + steps.add(TransitionStep(GONE, AOD, 0.1f, RUNNING)) + steps.add(TransitionStep(GONE, AOD, 0.2f, RUNNING)) + + steps.forEach { repository.sendTransitionStep(it) } + + assertThat(aodToLockscreenSteps).isEqualTo(steps.subList(2, 5)) + assertThat(goneToAodSteps).isEqualTo(steps.subList(5, 8)) + + job1.cancel() + job2.cancel() + } + + @Test + fun dozeAmountTransitionTest() = + runBlocking(IMMEDIATE) { + var dozeAmountSteps = mutableListOf() + val job = + underTest.dozeAmountTransition.onEach { dozeAmountSteps.add(it) }.launchIn(this) + + val steps = mutableListOf() + + steps.add(TransitionStep(AOD, LOCKSCREEN, 0f, STARTED)) + steps.add(TransitionStep(AOD, LOCKSCREEN, 0.5f, RUNNING)) + steps.add(TransitionStep(AOD, LOCKSCREEN, 1f, FINISHED)) + steps.add(TransitionStep(LOCKSCREEN, AOD, 0f, STARTED)) + steps.add(TransitionStep(LOCKSCREEN, AOD, 0.8f, RUNNING)) + steps.add(TransitionStep(LOCKSCREEN, AOD, 0.9f, RUNNING)) + steps.add(TransitionStep(LOCKSCREEN, AOD, 1f, FINISHED)) + + steps.forEach { repository.sendTransitionStep(it) } + + assertThat(dozeAmountSteps.subList(0, 3)) + .isEqualTo( + listOf( + steps[0].copy(value = 1f - steps[0].value), + steps[1].copy(value = 1f - steps[1].value), + steps[2].copy(value = 1f - steps[2].value), + ) + ) + assertThat(dozeAmountSteps.subList(3, 7)).isEqualTo(steps.subList(3, 7)) + + job.cancel() + } + + @Test + fun keyguardStateTests() = + runBlocking(IMMEDIATE) { + var finishedSteps = mutableListOf() + val job1 = + underTest.finishedKeyguardState.onEach { finishedSteps.add(it) }.launchIn(this) + var startedSteps = mutableListOf() + val job2 = underTest.startedKeyguardState.onEach { startedSteps.add(it) }.launchIn(this) + + val steps = mutableListOf() + + steps.add(TransitionStep(AOD, LOCKSCREEN, 0f, STARTED)) + steps.add(TransitionStep(AOD, LOCKSCREEN, 0.5f, RUNNING)) + steps.add(TransitionStep(AOD, LOCKSCREEN, 1f, FINISHED)) + steps.add(TransitionStep(LOCKSCREEN, AOD, 0f, STARTED)) + steps.add(TransitionStep(LOCKSCREEN, AOD, 0.9f, RUNNING)) + steps.add(TransitionStep(LOCKSCREEN, AOD, 1f, FINISHED)) + steps.add(TransitionStep(AOD, GONE, 1f, STARTED)) + + steps.forEach { repository.sendTransitionStep(it) } + + assertThat(finishedSteps).isEqualTo(listOf(LOCKSCREEN, AOD)) + assertThat(startedSteps).isEqualTo(listOf(LOCKSCREEN, AOD, GONE)) + + job1.cancel() + job2.cancel() + } + + companion object { + private val IMMEDIATE = Dispatchers.Main.immediate + } +} 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 0c126805fb787..11178db8afd2a 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 @@ -19,6 +19,7 @@ package com.android.systemui.keyguard.data.repository import com.android.systemui.common.shared.model.Position import com.android.systemui.keyguard.shared.model.StatusBarState +import com.android.systemui.keyguard.shared.model.WakefulnessModel import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow @@ -48,6 +49,9 @@ class FakeKeyguardRepository : KeyguardRepository { private val _statusBarState = MutableStateFlow(StatusBarState.SHADE) override val statusBarState: Flow = _statusBarState + private val _wakefulnessState = MutableStateFlow(WakefulnessModel.ASLEEP) + override val wakefulnessState: Flow = _wakefulnessState + override fun isKeyguardShowing(): Boolean { return _isKeyguardShowing.value } 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 new file mode 100644 index 0000000000000..6c4424470c1cd --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardTransitionRepository.kt @@ -0,0 +1,47 @@ +/* + * 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.data.repository + +import android.annotation.FloatRange +import com.android.systemui.keyguard.shared.model.TransitionInfo +import com.android.systemui.keyguard.shared.model.TransitionState +import com.android.systemui.keyguard.shared.model.TransitionStep +import java.util.UUID +import kotlinx.coroutines.flow.MutableSharedFlow +import kotlinx.coroutines.flow.SharedFlow + +/** Fake implementation of [KeyguardTransitionRepository] */ +class FakeKeyguardTransitionRepository : KeyguardTransitionRepository { + + private val _transitions = MutableSharedFlow() + override val transitions: SharedFlow = _transitions + + suspend fun sendTransitionStep(step: TransitionStep) { + _transitions.emit(step) + } + + override fun startTransition(info: TransitionInfo): UUID? { + return null + } + + override fun updateTransition( + transitionId: UUID, + @FloatRange(from = 0.0, to = 1.0) value: Float, + state: TransitionState + ) = Unit +}