diff options
25 files changed, 762 insertions, 143 deletions
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 da1d233949cf..3961438ff591 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<Boolean, Boolean> { + 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 71e04468c07a..d48d7ffcd824 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 @@ -47,6 +48,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 import java.io.PrintWriter import java.util.Locale @@ -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 46f3d4e5f6aa..32ce537ea25a 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 a4d3399eeb5d..40b5b0ac0e7d 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) @JvmField val NEW_ELLIPSE_DETECTION = UnreleasedFlag(214) 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 b186ae0ceec4..6baaf5f10024 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<StatusBarState> + /** Observable for device wake/sleep state */ + val wakefulnessState: Flow<WakefulnessModel> + /** * 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<WakefulnessModel> = 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 d15d7f25bbde..0c725208e22d 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 ab25597b077c..e3d1a27dad2b 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]. - */ - 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] +/** + * 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 var updateTransitionId: UUID? = null + val transitions: Flow<TransitionStep> /** * Interactors that require information about changes between [KeyguardState]s will call this to @@ -60,22 +66,56 @@ 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? + + /** + * Allows manual control of a transition. When calling [startTransition], the consumer must pass + * in a null animator. In return, it will get a unique [UUID] that will be validated to allow + * further updates. + * + * When the transition is over, TransitionState.FINISHED must be passed into the [state] + * parameter. + */ + fun updateTransition( + 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<TransitionStep>( + 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] */ - fun startTransition(info: TransitionInfo): UUID? { - if (currentTransitionInfo != null) { + 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: $currentTransitionInfo") + Log.wtf(TAG, "Transition still active: $lastStep") 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) @@ -83,24 +123,24 @@ class KeyguardTransitionRepository @Inject constructor() { object : AnimatorUpdateListener { override fun onAnimationUpdate(animation: ValueAnimator) { emitTransition( - info, - (animation.getAnimatedValue() as Float), - TransitionState.RUNNING + TransitionStep( + 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) + emitTransition(TransitionStep(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) + emitTransition(TransitionStep(info, 1f, TransitionState.FINISHED)) animator.removeListener(this) animator.removeUpdateListener(updateListener) } @@ -111,8 +151,7 @@ class KeyguardTransitionRepository @Inject constructor() { return@startTransition null } ?: run { - Log.i(TAG, "Starting transition (manual): $info") - emitTransition(info, 0f, TransitionState.STARTED) + emitTransition(TransitionStep(info, 0f, TransitionState.STARTED)) // No animator, so it's manual. Provide a mechanism to callback updateTransitionId = UUID.randomUUID() @@ -120,15 +159,7 @@ class KeyguardTransitionRepository @Inject constructor() { } } - /** - * Allows manual control of a transition. When calling [startTransition], the consumer must pass - * in a null animator. In return, it will get a unique [UUID] that will be validated to allow - * further updates. - * - * When the transition is over, TransitionState.FINISHED must be passed into the [state] - * parameter. - */ - fun updateTransition( + override fun updateTransition( transitionId: UUID, @FloatRange(from = 0.0, to = 1.0) value: Float, state: TransitionState @@ -138,52 +169,41 @@ class KeyguardTransitionRepository @Inject constructor() { 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 400376663f1a..0aeff7fc69fd 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 000000000000..0e2a54c57bdb --- /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 fc2269c6b01c..03c6a789326e 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<Boolean> = repository.isDozing /** Whether the keyguard is showing to not. */ val isKeyguardShowing: Flow<Boolean> = repository.isKeyguardShowing + /** The device wake/sleep state */ + val wakefulnessState: Flow<WakefulnessModel> = 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 000000000000..83d94325b9d4 --- /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 b166681433a8..d5ea77b8e729 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<TransitionInteractor>, + 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 7409aec57b4c..dffd097a77c5 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<TransitionStep> = repository.transition(LOCKSCREEN, AOD) + /** GONE->AOD information. */ + val goneToAodTransition: Flow<TransitionStep> = 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<KeyguardState> = + repository.transitions + .filter { step -> step.transitionState == TransitionState.FINISHED } + .map { step -> step.to } + + /* The last started [KeyguardState] transition */ + val startedKeyguardState: Flow<KeyguardState> = + 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 3c2a12e3836a..761f3fd9d9f9 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 000000000000..6c1adbd68ef2 --- /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 74c542c0043f..728bafae2a4c 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 f66d5d3650c8..7958033ba017 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 d8691c17f53d..0e0465bb5207 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 688ec912aac8..0ca358210813 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 000000000000..64f834d6c5ab --- /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 7a1568098e4a..b9ab9d37a805 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<WakefulnessModel>() + val job = underTest.wakefulnessState.onEach(values::add).launchIn(this) + + val captor = argumentCaptor<WakefulnessLifecycle.Observer>() + 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 1b34100b1cef..64913c7c28c2 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<TransitionStep>, fractions: List<BigDecimal>) { - // + 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 000000000000..0424c28a1c24 --- /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<TransitionStep>() + val job1 = + underTest.goneToAodTransition.onEach { goneToAodSteps.add(it) }.launchIn(this) + + var aodToLockscreenSteps = mutableListOf<TransitionStep>() + val job2 = + underTest.aodToLockscreenTransition + .onEach { aodToLockscreenSteps.add(it) } + .launchIn(this) + + val steps = mutableListOf<TransitionStep>() + 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<TransitionStep>() + val job = + underTest.dozeAmountTransition.onEach { dozeAmountSteps.add(it) }.launchIn(this) + + val steps = mutableListOf<TransitionStep>() + + 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<KeyguardState>() + val job1 = + underTest.finishedKeyguardState.onEach { finishedSteps.add(it) }.launchIn(this) + var startedSteps = mutableListOf<KeyguardState>() + val job2 = underTest.startedKeyguardState.onEach { startedSteps.add(it) }.launchIn(this) + + val steps = mutableListOf<TransitionStep>() + + 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 0c126805fb78..11178db8afd2 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> = _statusBarState + private val _wakefulnessState = MutableStateFlow(WakefulnessModel.ASLEEP) + override val wakefulnessState: Flow<WakefulnessModel> = _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 000000000000..6c4424470c1c --- /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<TransitionStep>() + override val transitions: SharedFlow<TransitionStep> = _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 +} |