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
This commit is contained in:
@@ -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)
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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" })
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
|
||||
@@ -22,4 +22,9 @@ import dagger.Module
|
||||
@Module
|
||||
interface KeyguardRepositoryModule {
|
||||
@Binds fun keyguardRepository(impl: KeyguardRepositoryImpl): KeyguardRepository
|
||||
|
||||
@Binds
|
||||
fun keyguardTransitionRepository(
|
||||
impl: KeyguardTransitionRepositoryImpl
|
||||
): KeyguardTransitionRepository
|
||||
}
|
||||
|
||||
@@ -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<TransitionStep>
|
||||
|
||||
/**
|
||||
* 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<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]
|
||||
*/
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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(),
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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()
|
||||
|
||||
@@ -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) }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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 {
|
||||
|
||||
@@ -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 }
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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])
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
Reference in New Issue
Block a user