Merge "Transitions - Add GONE state" into tm-qpr-dev

This commit is contained in:
Matt Pietal
2022-11-01 11:22:12 +00:00
committed by Android (Google) Code Review
25 changed files with 794 additions and 175 deletions

View File

@@ -237,6 +237,9 @@ class DefaultClockController(
) { ) {
var isActive: Boolean = fraction < 0.5f var isActive: Boolean = fraction < 0.5f
fun update(newFraction: Float): Pair<Boolean, Boolean> { fun update(newFraction: Float): Pair<Boolean, Boolean> {
if (newFraction == fraction) {
return Pair(isActive, false)
}
val wasActive = isActive val wasActive = isActive
val hasJumped = val hasJumped =
(fraction == 0f && newFraction == 1f) || (fraction == 1f && newFraction == 0f) (fraction == 0f && newFraction == 1f) || (fraction == 1f && newFraction == 0f)

View File

@@ -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.flags.Flags.REGION_SAMPLING
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor 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.lifecycle.repeatWhenAttached
import com.android.systemui.log.dagger.KeyguardClockLog import com.android.systemui.log.dagger.KeyguardClockLog
import com.android.systemui.plugins.ClockController import com.android.systemui.plugins.ClockController
@@ -47,6 +48,7 @@ import kotlinx.coroutines.DisposableHandle
import kotlinx.coroutines.Job import kotlinx.coroutines.Job
import kotlinx.coroutines.flow.collect import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import java.io.PrintWriter import java.io.PrintWriter
import java.util.Locale import java.util.Locale
@@ -186,8 +188,10 @@ open class ClockEventController @Inject constructor(
private val keyguardUpdateMonitorCallback = object : KeyguardUpdateMonitorCallback() { private val keyguardUpdateMonitorCallback = object : KeyguardUpdateMonitorCallback() {
override fun onKeyguardVisibilityChanged(visible: Boolean) { override fun onKeyguardVisibilityChanged(visible: Boolean) {
isKeyguardVisible = visible isKeyguardVisible = visible
if (!isKeyguardVisible) { if (!featureFlags.isEnabled(DOZING_MIGRATION_1)) {
clock?.animations?.doze(if (isDozing) 1f else 0f) if (!isKeyguardVisible) {
clock?.animations?.doze(if (isDozing) 1f else 0f)
}
} }
} }
@@ -224,6 +228,7 @@ open class ClockEventController @Inject constructor(
listenForDozing(this) listenForDozing(this)
if (featureFlags.isEnabled(DOZING_MIGRATION_1)) { if (featureFlags.isEnabled(DOZING_MIGRATION_1)) {
listenForDozeAmountTransition(this) listenForDozeAmountTransition(this)
listenForGoneToAodTransition(this)
} else { } else {
listenForDozeAmount(this) 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 @VisibleForTesting
internal fun listenForDozing(scope: CoroutineScope): Job { internal fun listenForDozing(scope: CoroutineScope): Job {
return scope.launch { return scope.launch {

View File

@@ -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
import com.android.systemui.plugins.log.LogLevel.DEBUG import com.android.systemui.plugins.log.LogLevel.DEBUG
import com.android.systemui.plugins.log.LogLevel.ERROR 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.VERBOSE
import com.android.systemui.plugins.log.LogLevel.WARNING import com.android.systemui.plugins.log.LogLevel.WARNING
import com.android.systemui.plugins.log.MessageInitializer 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) 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 // TODO: remove after b/237743330 is fixed
fun logStatusBarCalculatedAlpha(alpha: Float) { fun logStatusBarCalculatedAlpha(alpha: Float) {
debugLog({ double1 = alpha.toDouble() }, { "Calculated new alpha: $double1" }) debugLog({ double1 = alpha.toDouble() }, { "Calculated new alpha: $double1" })

View File

@@ -122,7 +122,8 @@ object Flags {
* Migration from the legacy isDozing/dozeAmount paths to the new KeyguardTransitionRepository * Migration from the legacy isDozing/dozeAmount paths to the new KeyguardTransitionRepository
* will occur in stages. This is one stage of many to come. * 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 // 300 - power menu
// TODO(b/254512600): Tracking Bug // TODO(b/254512600): Tracking Bug

View File

@@ -21,7 +21,10 @@ import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCall
import com.android.systemui.common.shared.model.Position import com.android.systemui.common.shared.model.Position
import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.doze.DozeHost 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.StatusBarState
import com.android.systemui.keyguard.shared.model.WakefulnessModel
import com.android.systemui.plugins.statusbar.StatusBarStateController import com.android.systemui.plugins.statusbar.StatusBarStateController
import com.android.systemui.statusbar.policy.KeyguardStateController import com.android.systemui.statusbar.policy.KeyguardStateController
import javax.inject.Inject import javax.inject.Inject
@@ -89,6 +92,9 @@ interface KeyguardRepository {
/** Observable for the [StatusBarState] */ /** Observable for the [StatusBarState] */
val statusBarState: Flow<StatusBarState> val statusBarState: Flow<StatusBarState>
/** Observable for device wake/sleep state */
val wakefulnessState: Flow<WakefulnessModel>
/** /**
* Returns `true` if the keyguard is showing; `false` otherwise. * Returns `true` if the keyguard is showing; `false` otherwise.
* *
@@ -118,6 +124,7 @@ constructor(
statusBarStateController: StatusBarStateController, statusBarStateController: StatusBarStateController,
private val keyguardStateController: KeyguardStateController, private val keyguardStateController: KeyguardStateController,
dozeHost: DozeHost, dozeHost: DozeHost,
wakefulnessLifecycle: WakefulnessLifecycle,
) : KeyguardRepository { ) : KeyguardRepository {
private val _animateBottomAreaDozingTransitions = MutableStateFlow(false) private val _animateBottomAreaDozingTransitions = MutableStateFlow(false)
override val animateBottomAreaDozingTransitions = override val animateBottomAreaDozingTransitions =
@@ -207,6 +214,40 @@ constructor(
awaitClose { statusBarStateController.removeCallback(callback) } 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) { override fun setAnimateDozingTransitions(animate: Boolean) {
_animateBottomAreaDozingTransitions.value = animate _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 { companion object {
private const val TAG = "KeyguardRepositoryImpl" private const val TAG = "KeyguardRepositoryImpl"
} }

View File

@@ -22,4 +22,9 @@ import dagger.Module
@Module @Module
interface KeyguardRepositoryModule { interface KeyguardRepositoryModule {
@Binds fun keyguardRepository(impl: KeyguardRepositoryImpl): KeyguardRepository @Binds fun keyguardRepository(impl: KeyguardRepositoryImpl): KeyguardRepository
@Binds
fun keyguardTransitionRepository(
impl: KeyguardTransitionRepositoryImpl
): KeyguardTransitionRepository
} }

View File

@@ -29,27 +29,33 @@ import com.android.systemui.keyguard.shared.model.TransitionState
import com.android.systemui.keyguard.shared.model.TransitionStep import com.android.systemui.keyguard.shared.model.TransitionStep
import java.util.UUID import java.util.UUID
import javax.inject.Inject import javax.inject.Inject
import kotlinx.coroutines.channels.BufferOverflow
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.asSharedFlow
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.filter import kotlinx.coroutines.flow.filter
@SysUISingleton /**
class KeyguardTransitionRepository @Inject constructor() { * The source of truth for all keyguard transitions.
/* *
* Each transition between [KeyguardState]s will have an associated Flow. * While the keyguard component is visible, it can undergo a number of transitions between different
* In order to collect these events, clients should call [transition]. * 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: Flow<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
/** /**
* Interactors that require information about changes between [KeyguardState]s will call this to * 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 * Begin a transition from one state to another. Will not start if another transition is in
* [currentTransitionInfo.to], or the request will be denied. This is enforced to avoid * progress.
* unplanned transitions.
*/ */
fun startTransition(info: TransitionInfo): UUID? { 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
}
}
/** /**
* Allows manual control of a transition. When calling [startTransition], the consumer must pass * Allows manual control of a transition. When calling [startTransition], the consumer must pass
@@ -132,58 +83,127 @@ class KeyguardTransitionRepository @Inject constructor() {
transitionId: UUID, transitionId: UUID,
@FloatRange(from = 0.0, to = 1.0) value: Float, @FloatRange(from = 0.0, to = 1.0) value: Float,
state: TransitionState 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) { if (updateTransitionId != transitionId) {
Log.wtf(TAG, "Attempting to update with old/invalid transitionId: $transitionId") Log.wtf(TAG, "Attempting to update with old/invalid transitionId: $transitionId")
return return
} }
if (currentTransitionInfo == null) { if (state == TransitionState.FINISHED) {
Log.wtf(TAG, "Attempting to update with null 'currentTransitionInfo'") updateTransitionId = null
return
} }
currentTransitionInfo?.let { info -> val nextStep = lastStep.copy(value = value, transitionState = state)
if (state == TransitionState.FINISHED) { emitTransition(nextStep, isManual = true)
updateTransitionId = null
Log.i(TAG, "Ending transition: $info")
}
emitTransition(info, value, state)
}
} }
private fun emitTransition( private fun emitTransition(nextStep: TransitionStep, isManual: Boolean = false) {
info: TransitionInfo, trace(nextStep, isManual)
value: Float, val emitted = _transitions.tryEmit(nextStep)
transitionState: TransitionState if (!emitted) {
) { Log.w(TAG, "Failed to emit next value without suspending")
trace(info, transitionState)
if (transitionState == TransitionState.FINISHED) {
currentTransitionInfo = null
} }
_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 ( if (
transitionState != TransitionState.STARTED && step.transitionState != TransitionState.STARTED &&
transitionState != TransitionState.FINISHED step.transitionState != TransitionState.FINISHED
) { ) {
return return
} }
val traceName = val traceName =
"Transition: ${info.from} -> ${info.to} " + "Transition: ${step.from} -> ${step.to} " +
if (info.animator == null) { if (isManual) {
"(manual)" "(manual)"
} else { } else {
"" ""
} }
val traceCookie = traceName.hashCode() val traceCookie = traceName.hashCode()
if (transitionState == TransitionState.STARTED) { if (step.transitionState == TransitionState.STARTED) {
Trace.beginAsyncSection(traceName, traceCookie) Trace.beginAsyncSection(traceName, traceCookie)
} else if (transitionState == TransitionState.FINISHED) { } else if (step.transitionState == TransitionState.FINISHED) {
Trace.endAsyncSection(traceName, traceCookie) Trace.endAsyncSection(traceName, traceCookie)
} }
} }

View File

@@ -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.data.repository.KeyguardTransitionRepository
import com.android.systemui.keyguard.shared.model.KeyguardState import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.keyguard.shared.model.TransitionInfo import com.android.systemui.keyguard.shared.model.TransitionInfo
import com.android.systemui.util.kotlin.sample
import javax.inject.Inject import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.collect import kotlinx.coroutines.flow.collect
@@ -36,31 +37,35 @@ constructor(
@Application private val scope: CoroutineScope, @Application private val scope: CoroutineScope,
private val keyguardRepository: KeyguardRepository, private val keyguardRepository: KeyguardRepository,
private val keyguardTransitionRepository: KeyguardTransitionRepository, private val keyguardTransitionRepository: KeyguardTransitionRepository,
private val keyguardTransitionInteractor: KeyguardTransitionInteractor,
) : TransitionInteractor("AOD<->LOCKSCREEN") { ) : TransitionInteractor("AOD<->LOCKSCREEN") {
override fun start() { override fun start() {
scope.launch { scope.launch {
keyguardRepository.isDozing.collect { isDozing -> keyguardRepository.isDozing
if (isDozing) { .sample(keyguardTransitionInteractor.finishedKeyguardState, { a, b -> Pair(a, b) })
keyguardTransitionRepository.startTransition( .collect { pair ->
TransitionInfo( val (isDozing, keyguardState) = pair
name, if (isDozing && keyguardState == KeyguardState.LOCKSCREEN) {
KeyguardState.LOCKSCREEN, keyguardTransitionRepository.startTransition(
KeyguardState.AOD, TransitionInfo(
getAnimator(), name,
KeyguardState.LOCKSCREEN,
KeyguardState.AOD,
getAnimator(),
)
) )
) } else if (!isDozing && keyguardState == KeyguardState.AOD) {
} else { keyguardTransitionRepository.startTransition(
keyguardTransitionRepository.startTransition( TransitionInfo(
TransitionInfo( name,
name, KeyguardState.AOD,
KeyguardState.AOD, KeyguardState.LOCKSCREEN,
KeyguardState.LOCKSCREEN, getAnimator(),
getAnimator(), )
) )
) }
} }
}
} }
} }

View File

@@ -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
}
}

View File

@@ -19,6 +19,7 @@ package com.android.systemui.keyguard.domain.interactor
import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.keyguard.data.repository.KeyguardRepository import com.android.systemui.keyguard.data.repository.KeyguardRepository
import com.android.systemui.keyguard.shared.model.WakefulnessModel
import javax.inject.Inject import javax.inject.Inject
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
@@ -40,6 +41,8 @@ constructor(
val isDozing: Flow<Boolean> = repository.isDozing val isDozing: Flow<Boolean> = repository.isDozing
/** Whether the keyguard is showing to not. */ /** Whether the keyguard is showing to not. */
val isKeyguardShowing: Flow<Boolean> = repository.isKeyguardShowing val isKeyguardShowing: Flow<Boolean> = repository.isKeyguardShowing
/** The device wake/sleep state */
val wakefulnessState: Flow<WakefulnessModel> = repository.wakefulnessState
fun isKeyguardShowing(): Boolean { fun isKeyguardShowing(): Boolean {
return repository.isKeyguardShowing() return repository.isKeyguardShowing()

View File

@@ -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) }
}
}
}

View File

@@ -26,6 +26,7 @@ class KeyguardTransitionCoreStartable
@Inject @Inject
constructor( constructor(
private val interactors: Set<TransitionInteractor>, private val interactors: Set<TransitionInteractor>,
private val auditLogger: KeyguardTransitionAuditLogger,
) : CoreStartable { ) : CoreStartable {
override fun start() { override fun start() {
@@ -38,9 +39,12 @@ constructor(
when (it) { when (it) {
is LockscreenBouncerTransitionInteractor -> Log.d(TAG, "Started $it") is LockscreenBouncerTransitionInteractor -> Log.d(TAG, "Started $it")
is AodLockscreenTransitionInteractor -> 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() it.start()
} }
auditLogger.start()
} }
companion object { companion object {

View File

@@ -19,11 +19,15 @@ package com.android.systemui.keyguard.domain.interactor
import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository 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.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.KeyguardState.LOCKSCREEN
import com.android.systemui.keyguard.shared.model.TransitionState
import com.android.systemui.keyguard.shared.model.TransitionStep import com.android.systemui.keyguard.shared.model.TransitionStep
import javax.inject.Inject import javax.inject.Inject
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.merge import kotlinx.coroutines.flow.merge
@@ -40,6 +44,9 @@ constructor(
/** LOCKSCREEN->AOD transition information. */ /** LOCKSCREEN->AOD transition information. */
val lockscreenToAodTransition: Flow<TransitionStep> = repository.transition(LOCKSCREEN, AOD) 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) <-> * AOD<->LOCKSCREEN transition information, mapped to dozeAmount range of AOD (1f) <->
* Lockscreen (0f). * Lockscreen (0f).
@@ -49,4 +56,16 @@ constructor(
aodToLockscreenTransition.map { step -> step.copy(value = 1f - step.value) }, aodToLockscreenTransition.map { step -> step.copy(value = 1f - step.value) },
lockscreenToAodTransition, 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 }
} }

View File

@@ -29,6 +29,7 @@ import com.android.systemui.util.kotlin.sample
import java.util.UUID import java.util.UUID
import javax.inject.Inject import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
@@ -40,59 +41,63 @@ constructor(
private val keyguardRepository: KeyguardRepository, private val keyguardRepository: KeyguardRepository,
private val shadeRepository: ShadeRepository, private val shadeRepository: ShadeRepository,
private val keyguardTransitionRepository: KeyguardTransitionRepository, private val keyguardTransitionRepository: KeyguardTransitionRepository,
private val keyguardTransitionInteractor: KeyguardTransitionInteractor
) : TransitionInteractor("LOCKSCREEN<->BOUNCER") { ) : TransitionInteractor("LOCKSCREEN<->BOUNCER") {
private var transitionId: UUID? = null private var transitionId: UUID? = null
override fun start() { override fun start() {
scope.launch { scope.launch {
shadeRepository.shadeModel.sample( shadeRepository.shadeModel
combine( .sample(
keyguardTransitionRepository.transitions, combine(
keyguardRepository.statusBarState, keyguardTransitionInteractor.finishedKeyguardState,
) { transitions, statusBarState -> keyguardRepository.statusBarState,
Pair(transitions, statusBarState) ) { keyguardState, statusBarState ->
} Pair(keyguardState, statusBarState)
) { shadeModel, pair -> },
val (transitions, statusBarState) = pair { shadeModel, pair -> Triple(shadeModel, pair.first, pair.second) }
)
.collect { triple ->
val (shadeModel, keyguardState, statusBarState) = triple
val id = transitionId val id = transitionId
if (id != null) { if (id != null) {
// An existing `id` means a transition is started, and calls to // An existing `id` means a transition is started, and calls to
// `updateTransition` will control it until FINISHED // `updateTransition` will control it until FINISHED
keyguardTransitionRepository.updateTransition( keyguardTransitionRepository.updateTransition(
id, id,
shadeModel.expansionAmount, shadeModel.expansionAmount,
if (shadeModel.expansionAmount == 0f || shadeModel.expansionAmount == 1f) { if (
transitionId = null shadeModel.expansionAmount == 0f || shadeModel.expansionAmount == 1f
TransitionState.FINISHED ) {
} else { transitionId = null
TransitionState.RUNNING TransitionState.FINISHED
} } else {
) TransitionState.RUNNING
} else { }
// TODO (b/251849525): Remove statusbarstate check when that state is integrated )
// into KeyguardTransitionRepository } else {
val isOnLockscreen = // TODO (b/251849525): Remove statusbarstate check when that state is
transitions.transitionState == TransitionState.FINISHED && // integrated
transitions.to == KeyguardState.LOCKSCREEN // into KeyguardTransitionRepository
if ( if (
isOnLockscreen && keyguardState == KeyguardState.LOCKSCREEN &&
shadeModel.isUserDragging && shadeModel.isUserDragging &&
statusBarState != SHADE_LOCKED statusBarState != SHADE_LOCKED
) { ) {
transitionId = transitionId =
keyguardTransitionRepository.startTransition( keyguardTransitionRepository.startTransition(
TransitionInfo( TransitionInfo(
ownerName = name, ownerName = name,
from = KeyguardState.LOCKSCREEN, from = KeyguardState.LOCKSCREEN,
to = KeyguardState.BOUNCER, to = KeyguardState.BOUNCER,
animator = null, animator = null,
)
) )
) }
} }
} }
}
} }
} }
} }

View File

@@ -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
}
}

View File

@@ -39,4 +39,10 @@ abstract class StartKeyguardTransitionModule {
@Binds @Binds
@IntoSet @IntoSet
abstract fun aodLockscreen(impl: AodLockscreenTransitionInteractor): TransitionInteractor abstract fun aodLockscreen(impl: AodLockscreenTransitionInteractor): TransitionInteractor
@Binds @IntoSet abstract fun goneAod(impl: GoneAodTransitionInteractor): TransitionInteractor
@Binds
@IntoSet
abstract fun lockscreenGone(impl: LockscreenGoneTransitionInteractor): TransitionInteractor
} }

View File

@@ -17,7 +17,10 @@ package com.android.systemui.keyguard.shared.model
/** List of all possible states to transition to/from */ /** List of all possible states to transition to/from */
enum class KeyguardState { 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, NONE,
/* Always-on Display. The device is in a low-power mode with a minimal UI visible */ /* Always-on Display. The device is in a low-power mode with a minimal UI visible */
AOD, AOD,
@@ -31,4 +34,11 @@ enum class KeyguardState {
* unlocked if SWIPE security method is used, or if face lockscreen bypass is false. * unlocked if SWIPE security method is used, or if face lockscreen bypass is false.
*/ */
LOCKSCREEN, 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,
} }

View File

@@ -17,7 +17,6 @@ package com.android.systemui.keyguard.shared.model
/** Possible states for a running transition between [State] */ /** Possible states for a running transition between [State] */
enum class TransitionState { enum class TransitionState {
NONE,
STARTED, STARTED,
RUNNING, RUNNING,
FINISHED FINISHED

View File

@@ -20,5 +20,11 @@ data class TransitionStep(
val from: KeyguardState = KeyguardState.NONE, val from: KeyguardState = KeyguardState.NONE,
val to: KeyguardState = KeyguardState.NONE, val to: KeyguardState = KeyguardState.NONE,
val value: Float = 0f, // constrained [0.0, 1.0] 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)
}

View File

@@ -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,
}

View File

@@ -20,6 +20,8 @@ import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase import com.android.systemui.SysuiTestCase
import com.android.systemui.common.shared.model.Position import com.android.systemui.common.shared.model.Position
import com.android.systemui.doze.DozeHost 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.plugins.statusbar.StatusBarStateController
import com.android.systemui.statusbar.policy.KeyguardStateController import com.android.systemui.statusbar.policy.KeyguardStateController
import com.android.systemui.util.mockito.argumentCaptor import com.android.systemui.util.mockito.argumentCaptor
@@ -43,6 +45,7 @@ class KeyguardRepositoryImplTest : SysuiTestCase() {
@Mock private lateinit var statusBarStateController: StatusBarStateController @Mock private lateinit var statusBarStateController: StatusBarStateController
@Mock private lateinit var dozeHost: DozeHost @Mock private lateinit var dozeHost: DozeHost
@Mock private lateinit var keyguardStateController: KeyguardStateController @Mock private lateinit var keyguardStateController: KeyguardStateController
@Mock private lateinit var wakefulnessLifecycle: WakefulnessLifecycle
private lateinit var underTest: KeyguardRepositoryImpl private lateinit var underTest: KeyguardRepositoryImpl
@@ -55,6 +58,7 @@ class KeyguardRepositoryImplTest : SysuiTestCase() {
statusBarStateController, statusBarStateController,
keyguardStateController, keyguardStateController,
dozeHost, dozeHost,
wakefulnessLifecycle,
) )
} }
@@ -184,4 +188,33 @@ class KeyguardRepositoryImplTest : SysuiTestCase() {
job.cancel() job.cancel()
verify(statusBarStateController).removeCallback(captor.value) 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)
}
} }

View File

@@ -63,7 +63,7 @@ class KeyguardTransitionRepositoryTest : SysuiTestCase() {
@Before @Before
fun setUp() { fun setUp() {
underTest = KeyguardTransitionRepository() underTest = KeyguardTransitionRepositoryImpl()
wtfHandler = WtfHandler() wtfHandler = WtfHandler()
oldWtfHandler = Log.setWtfHandler(wtfHandler) oldWtfHandler = Log.setWtfHandler(wtfHandler)
} }
@@ -174,9 +174,6 @@ class KeyguardTransitionRepositoryTest : SysuiTestCase() {
} }
private fun assertSteps(steps: List<TransitionStep>, fractions: List<BigDecimal>) { 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)) assertThat(steps[0]).isEqualTo(TransitionStep(AOD, LOCKSCREEN, 0f, TransitionState.STARTED))
fractions.forEachIndexed { index, fraction -> fractions.forEachIndexed { index, fraction ->
assertThat(steps[index + 1]) assertThat(steps[index + 1])

View File

@@ -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
}
}

View File

@@ -19,6 +19,7 @@ package com.android.systemui.keyguard.data.repository
import com.android.systemui.common.shared.model.Position import com.android.systemui.common.shared.model.Position
import com.android.systemui.keyguard.shared.model.StatusBarState 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.Flow
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.StateFlow
@@ -48,6 +49,9 @@ class FakeKeyguardRepository : KeyguardRepository {
private val _statusBarState = MutableStateFlow(StatusBarState.SHADE) private val _statusBarState = MutableStateFlow(StatusBarState.SHADE)
override val statusBarState: Flow<StatusBarState> = _statusBarState override val statusBarState: Flow<StatusBarState> = _statusBarState
private val _wakefulnessState = MutableStateFlow(WakefulnessModel.ASLEEP)
override val wakefulnessState: Flow<WakefulnessModel> = _wakefulnessState
override fun isKeyguardShowing(): Boolean { override fun isKeyguardShowing(): Boolean {
return _isKeyguardShowing.value return _isKeyguardShowing.value
} }

View File

@@ -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
}