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

This commit is contained in:
Matt Pietal
2022-12-22 12:39:36 +00:00
committed by Android (Google) Code Review
25 changed files with 640 additions and 252 deletions

View File

@@ -101,13 +101,11 @@ constructor(
transitionViewModel.dreamOverlayTranslationY(it.translationYPx)
}
.collect { px ->
setElementsTranslationYAtPosition(
px,
ComplicationLayoutParams.POSITION_TOP
)
setElementsTranslationYAtPosition(
px,
ComplicationLayoutParams.POSITION_BOTTOM
ComplicationLayoutParams.iteratePositions(
{ position: Int ->
setElementsTranslationYAtPosition(px, position)
},
POSITION_TOP or POSITION_BOTTOM
)
}
}
@@ -115,15 +113,15 @@ constructor(
/* Alpha animations, when moving from DREAMING->LOCKSCREEN state */
launch {
transitionViewModel.dreamOverlayAlpha.collect { alpha ->
setElementsAlphaAtPosition(
alpha = alpha,
position = ComplicationLayoutParams.POSITION_TOP,
fadingOut = true,
)
setElementsAlphaAtPosition(
alpha = alpha,
position = ComplicationLayoutParams.POSITION_BOTTOM,
fadingOut = true,
ComplicationLayoutParams.iteratePositions(
{ position: Int ->
setElementsAlphaAtPosition(
alpha = alpha,
position = position,
fadingOut = true,
)
},
POSITION_TOP or POSITION_BOTTOM
)
}
}

View File

@@ -20,26 +20,39 @@ import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.statusbar.policy.CallbackController
import javax.inject.Inject
/** Dream-related callback information */
/** Dream overlay-related callback information */
@SysUISingleton
class DreamCallbackController @Inject constructor() :
CallbackController<DreamCallbackController.DreamCallback> {
class DreamOverlayCallbackController @Inject constructor() :
CallbackController<DreamOverlayCallbackController.Callback> {
private val callbacks = mutableSetOf<DreamCallbackController.DreamCallback>()
private val callbacks = mutableSetOf<DreamOverlayCallbackController.Callback>()
override fun addCallback(callback: DreamCallbackController.DreamCallback) {
var isDreaming = false
private set
override fun addCallback(callback: DreamOverlayCallbackController.Callback) {
callbacks.add(callback)
}
override fun removeCallback(callback: DreamCallbackController.DreamCallback) {
override fun removeCallback(callback: DreamOverlayCallbackController.Callback) {
callbacks.remove(callback)
}
fun onWakeUp() {
isDreaming = false
callbacks.forEach { it.onWakeUp() }
}
interface DreamCallback {
fun onStartDream() {
isDreaming = true
callbacks.forEach { it.onStartDream() }
}
interface Callback {
/** Dream overlay has ended */
fun onWakeUp()
/** Dream overlay has started */
fun onStartDream()
}
}

View File

@@ -64,7 +64,7 @@ public class DreamOverlayService extends android.service.dreams.DreamOverlayServ
// A controller for the dream overlay container view (which contains both the status bar and the
// content area).
private DreamOverlayContainerViewController mDreamOverlayContainerViewController;
private final DreamCallbackController mDreamCallbackController;
private final DreamOverlayCallbackController mDreamOverlayCallbackController;
private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
@Nullable
private final ComponentName mLowLightDreamComponent;
@@ -134,7 +134,7 @@ public class DreamOverlayService extends android.service.dreams.DreamOverlayServ
UiEventLogger uiEventLogger,
@Nullable @Named(LowLightDreamModule.LOW_LIGHT_DREAM_COMPONENT)
ComponentName lowLightDreamComponent,
DreamCallbackController dreamCallbackController) {
DreamOverlayCallbackController dreamOverlayCallbackController) {
mContext = context;
mExecutor = executor;
mWindowManager = windowManager;
@@ -143,7 +143,7 @@ public class DreamOverlayService extends android.service.dreams.DreamOverlayServ
mKeyguardUpdateMonitor.registerCallback(mKeyguardCallback);
mStateController = stateController;
mUiEventLogger = uiEventLogger;
mDreamCallbackController = dreamCallbackController;
mDreamOverlayCallbackController = dreamOverlayCallbackController;
final ViewModelStore viewModelStore = new ViewModelStore();
final Complication.Host host =
@@ -203,6 +203,7 @@ public class DreamOverlayService extends android.service.dreams.DreamOverlayServ
dreamComponent != null && dreamComponent.equals(mLowLightDreamComponent));
mUiEventLogger.log(DreamOverlayEvent.DREAM_OVERLAY_COMPLETE_START);
mDreamOverlayCallbackController.onStartDream();
mStarted = true;
});
}
@@ -219,7 +220,7 @@ public class DreamOverlayService extends android.service.dreams.DreamOverlayServ
public void onWakeUp(@NonNull Runnable onCompletedCallback) {
mExecutor.execute(() -> {
if (mDreamOverlayContainerViewController != null) {
mDreamCallbackController.onWakeUp();
mDreamOverlayCallbackController.onWakeUp();
mDreamOverlayContainerViewController.wakeUp(onCompletedCallback, mExecutor);
}
});

View File

@@ -29,8 +29,7 @@ import com.android.systemui.doze.DozeHost
import com.android.systemui.doze.DozeMachine
import com.android.systemui.doze.DozeTransitionCallback
import com.android.systemui.doze.DozeTransitionListener
import com.android.systemui.dreams.DreamCallbackController
import com.android.systemui.dreams.DreamCallbackController.DreamCallback
import com.android.systemui.dreams.DreamOverlayCallbackController
import com.android.systemui.keyguard.WakefulnessLifecycle
import com.android.systemui.keyguard.shared.model.BiometricUnlockModel
import com.android.systemui.keyguard.shared.model.BiometricUnlockSource
@@ -49,7 +48,6 @@ import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.merge
/** Defines interface for classes that encapsulate application state for the keyguard. */
interface KeyguardRepository {
@@ -81,6 +79,9 @@ interface KeyguardRepository {
*/
val isKeyguardShowing: Flow<Boolean>
/** Is an activity showing over the keyguard? */
val isKeyguardOccluded: Flow<Boolean>
/** Observable for the signal that keyguard is about to go away. */
val isKeyguardGoingAway: Flow<Boolean>
@@ -107,6 +108,9 @@ interface KeyguardRepository {
*/
val isDreaming: Flow<Boolean>
/** Observable for whether the device is dreaming with an overlay, see [DreamOverlayService] */
val isDreamingWithOverlay: Flow<Boolean>
/**
* Observable for the amount of doze we are currently in.
*
@@ -179,7 +183,7 @@ constructor(
private val keyguardUpdateMonitor: KeyguardUpdateMonitor,
private val dozeTransitionListener: DozeTransitionListener,
private val authController: AuthController,
private val dreamCallbackController: DreamCallbackController,
private val dreamOverlayCallbackController: DreamOverlayCallbackController,
) : KeyguardRepository {
private val _animateBottomAreaDozingTransitions = MutableStateFlow(false)
override val animateBottomAreaDozingTransitions =
@@ -191,28 +195,55 @@ constructor(
private val _clockPosition = MutableStateFlow(Position(0, 0))
override val clockPosition = _clockPosition.asStateFlow()
override val isKeyguardShowing: Flow<Boolean> = conflatedCallbackFlow {
val callback =
object : KeyguardStateController.Callback {
override fun onKeyguardShowingChanged() {
trySendWithFailureLogging(
keyguardStateController.isShowing,
TAG,
"updated isKeyguardShowing"
)
}
override val isKeyguardShowing: Flow<Boolean> =
conflatedCallbackFlow {
val callback =
object : KeyguardStateController.Callback {
override fun onKeyguardShowingChanged() {
trySendWithFailureLogging(
keyguardStateController.isShowing,
TAG,
"updated isKeyguardShowing"
)
}
}
keyguardStateController.addCallback(callback)
// Adding the callback does not send an initial update.
trySendWithFailureLogging(
keyguardStateController.isShowing,
TAG,
"initial isKeyguardShowing"
)
awaitClose { keyguardStateController.removeCallback(callback) }
}
.distinctUntilChanged()
keyguardStateController.addCallback(callback)
// Adding the callback does not send an initial update.
trySendWithFailureLogging(
keyguardStateController.isShowing,
TAG,
"initial isKeyguardShowing"
)
override val isKeyguardOccluded: Flow<Boolean> =
conflatedCallbackFlow {
val callback =
object : KeyguardStateController.Callback {
override fun onKeyguardShowingChanged() {
trySendWithFailureLogging(
keyguardStateController.isOccluded,
TAG,
"updated isKeyguardOccluded"
)
}
}
awaitClose { keyguardStateController.removeCallback(callback) }
}
keyguardStateController.addCallback(callback)
// Adding the callback does not send an initial update.
trySendWithFailureLogging(
keyguardStateController.isOccluded,
TAG,
"initial isKeyguardOccluded"
)
awaitClose { keyguardStateController.removeCallback(callback) }
}
.distinctUntilChanged()
override val isKeyguardGoingAway: Flow<Boolean> = conflatedCallbackFlow {
val callback =
@@ -279,36 +310,45 @@ constructor(
}
.distinctUntilChanged()
override val isDreamingWithOverlay: Flow<Boolean> =
conflatedCallbackFlow {
val callback =
object : DreamOverlayCallbackController.Callback {
override fun onStartDream() {
trySendWithFailureLogging(true, TAG, "updated isDreamingWithOverlay")
}
override fun onWakeUp() {
trySendWithFailureLogging(false, TAG, "updated isDreamingWithOverlay")
}
}
dreamOverlayCallbackController.addCallback(callback)
trySendWithFailureLogging(
dreamOverlayCallbackController.isDreaming,
TAG,
"initial isDreamingWithOverlay",
)
awaitClose { dreamOverlayCallbackController.removeCallback(callback) }
}
.distinctUntilChanged()
override val isDreaming: Flow<Boolean> =
merge(
conflatedCallbackFlow {
val callback =
object : KeyguardUpdateMonitorCallback() {
override fun onDreamingStateChanged(isDreaming: Boolean) {
trySendWithFailureLogging(isDreaming, TAG, "updated isDreaming")
}
conflatedCallbackFlow {
val callback =
object : KeyguardUpdateMonitorCallback() {
override fun onDreamingStateChanged(isDreaming: Boolean) {
trySendWithFailureLogging(isDreaming, TAG, "updated isDreaming")
}
keyguardUpdateMonitor.registerCallback(callback)
trySendWithFailureLogging(
keyguardUpdateMonitor.isDreaming,
TAG,
"initial isDreaming",
)
}
keyguardUpdateMonitor.registerCallback(callback)
trySendWithFailureLogging(
keyguardUpdateMonitor.isDreaming,
TAG,
"initial isDreaming",
)
awaitClose { keyguardUpdateMonitor.removeCallback(callback) }
},
conflatedCallbackFlow {
val callback =
object : DreamCallback {
override fun onWakeUp() {
trySendWithFailureLogging(false, TAG, "updated isDreaming")
}
}
dreamCallbackController.addCallback(callback)
awaitClose { dreamCallbackController.removeCallback(callback) }
}
)
awaitClose { keyguardUpdateMonitor.removeCallback(callback) }
}
.distinctUntilChanged()
override val linearDozeAmount: Flow<Float> = conflatedCallbackFlow {

View File

@@ -131,6 +131,10 @@ class KeyguardTransitionRepositoryImpl @Inject constructor() : KeyguardTransitio
}
override fun startTransition(info: TransitionInfo): UUID? {
if (lastStep.from == info.from && lastStep.to == info.to) {
Log.i(TAG, "Duplicate call to start the transition, rejecting: $info")
return null
}
if (lastStep.transitionState != TransitionState.FINISHED) {
Log.i(TAG, "Transition still active: $lastStep, canceling")
}

View File

@@ -21,6 +21,7 @@ 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.BiometricUnlockModel.Companion.isWakeAndUnlock
import com.android.systemui.keyguard.shared.model.DozeStateModel
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.keyguard.shared.model.TransitionInfo
@@ -30,33 +31,33 @@ import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.launch
@SysUISingleton
class AodLockscreenTransitionInteractor
class FromAodTransitionInteractor
@Inject
constructor(
@Application private val scope: CoroutineScope,
private val keyguardInteractor: KeyguardInteractor,
private val keyguardTransitionRepository: KeyguardTransitionRepository,
private val keyguardTransitionInteractor: KeyguardTransitionInteractor,
) : TransitionInteractor(AodLockscreenTransitionInteractor::class.simpleName!!) {
) : TransitionInteractor(FromAodTransitionInteractor::class.simpleName!!) {
override fun start() {
listenForTransitionToAodFromLockscreen()
listenForTransitionToLockscreenFromDozeStates()
listenForAodToLockscreen()
listenForAodToGone()
}
private fun listenForTransitionToAodFromLockscreen() {
private fun listenForAodToLockscreen() {
scope.launch {
keyguardInteractor
.dozeTransitionTo(DozeStateModel.DOZE_AOD)
.dozeTransitionTo(DozeStateModel.FINISH)
.sample(keyguardTransitionInteractor.startedKeyguardTransitionStep, ::Pair)
.collect { pair ->
val (dozeToAod, lastStartedStep) = pair
if (lastStartedStep.to == KeyguardState.LOCKSCREEN) {
if (lastStartedStep.to == KeyguardState.AOD) {
keyguardTransitionRepository.startTransition(
TransitionInfo(
name,
KeyguardState.LOCKSCREEN,
KeyguardState.AOD,
KeyguardState.LOCKSCREEN,
getAnimator(),
)
)
@@ -65,20 +66,20 @@ constructor(
}
}
private fun listenForTransitionToLockscreenFromDozeStates() {
val canGoToLockscreen = setOf(KeyguardState.AOD, KeyguardState.DOZING)
private fun listenForAodToGone() {
scope.launch {
keyguardInteractor
.dozeTransitionTo(DozeStateModel.FINISH)
.sample(keyguardTransitionInteractor.startedKeyguardTransitionStep, ::Pair)
keyguardInteractor.biometricUnlockState
.sample(keyguardTransitionInteractor.finishedKeyguardState, ::Pair)
.collect { pair ->
val (dozeToAod, lastStartedStep) = pair
if (canGoToLockscreen.contains(lastStartedStep.to)) {
val (biometricUnlockState, keyguardState) = pair
if (
keyguardState == KeyguardState.AOD && isWakeAndUnlock(biometricUnlockState)
) {
keyguardTransitionRepository.startTransition(
TransitionInfo(
name,
lastStartedStep.to,
KeyguardState.LOCKSCREEN,
KeyguardState.AOD,
KeyguardState.GONE,
getAnimator(),
)
)

View File

@@ -23,16 +23,18 @@ 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.WakefulnessState
import com.android.systemui.shade.data.repository.ShadeRepository
import com.android.systemui.util.kotlin.sample
import java.util.UUID
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.launch
@SysUISingleton
class BouncerToGoneTransitionInteractor
class FromBouncerTransitionInteractor
@Inject
constructor(
@Application private val scope: CoroutineScope,
@@ -40,15 +42,54 @@ constructor(
private val shadeRepository: ShadeRepository,
private val keyguardTransitionRepository: KeyguardTransitionRepository,
private val keyguardTransitionInteractor: KeyguardTransitionInteractor
) : TransitionInteractor(BouncerToGoneTransitionInteractor::class.simpleName!!) {
) : TransitionInteractor(FromBouncerTransitionInteractor::class.simpleName!!) {
private var transitionId: UUID? = null
override fun start() {
listenForKeyguardGoingAway()
listenForBouncerToGone()
listenForBouncerToLockscreenOrAod()
}
private fun listenForKeyguardGoingAway() {
private fun listenForBouncerToLockscreenOrAod() {
scope.launch {
keyguardInteractor.isBouncerShowing
.sample(
combine(
keyguardInteractor.wakefulnessModel,
keyguardTransitionInteractor.startedKeyguardTransitionStep,
::Pair
),
::toTriple
)
.collect { triple ->
val (isBouncerShowing, wakefulnessState, lastStartedTransitionStep) = triple
if (
!isBouncerShowing && lastStartedTransitionStep.to == KeyguardState.BOUNCER
) {
val to =
if (
wakefulnessState.state == WakefulnessState.STARTING_TO_SLEEP ||
wakefulnessState.state == WakefulnessState.ASLEEP
) {
KeyguardState.AOD
} else {
KeyguardState.LOCKSCREEN
}
keyguardTransitionRepository.startTransition(
TransitionInfo(
ownerName = name,
from = KeyguardState.BOUNCER,
to = to,
animator = getAnimator(),
)
)
}
}
}
}
private fun listenForBouncerToGone() {
scope.launch {
keyguardInteractor.isKeyguardGoingAway
.sample(keyguardTransitionInteractor.finishedKeyguardState, { a, b -> Pair(a, b) })

View File

@@ -21,36 +21,46 @@ 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.DozeStateModel.Companion.isDozeOff
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 kotlin.time.Duration
import kotlin.time.Duration.Companion.milliseconds
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.launch
@SysUISingleton
class LockscreenGoneTransitionInteractor
class FromDozingTransitionInteractor
@Inject
constructor(
@Application private val scope: CoroutineScope,
private val keyguardInteractor: KeyguardInteractor,
private val keyguardTransitionInteractor: KeyguardTransitionInteractor,
private val keyguardTransitionRepository: KeyguardTransitionRepository,
) : TransitionInteractor(LockscreenGoneTransitionInteractor::class.simpleName!!) {
private val keyguardTransitionInteractor: KeyguardTransitionInteractor,
) : TransitionInteractor(FromDozingTransitionInteractor::class.simpleName!!) {
override fun start() {
listenForDozingToLockscreen()
}
private fun listenForDozingToLockscreen() {
scope.launch {
keyguardInteractor.isKeyguardGoingAway
keyguardInteractor.dozeTransitionModel
.sample(keyguardTransitionInteractor.startedKeyguardTransitionStep, ::Pair)
.collect { pair ->
val (isKeyguardGoingAway, lastStartedStep) = pair
if (isKeyguardGoingAway && lastStartedStep.to == KeyguardState.LOCKSCREEN) {
val (dozeTransitionModel, lastStartedTransition) = pair
if (
isDozeOff(dozeTransitionModel.to) &&
lastStartedTransition.to == KeyguardState.DOZING
) {
keyguardTransitionRepository.startTransition(
TransitionInfo(
name,
KeyguardState.DOZING,
KeyguardState.LOCKSCREEN,
KeyguardState.GONE,
getAnimator(),
)
)
@@ -59,14 +69,14 @@ constructor(
}
}
private fun getAnimator(): ValueAnimator {
private fun getAnimator(duration: Duration = DEFAULT_DURATION): ValueAnimator {
return ValueAnimator().apply {
setInterpolator(Interpolators.LINEAR)
setDuration(TRANSITION_DURATION_MS)
setDuration(duration.inWholeMilliseconds)
}
}
companion object {
private const val TRANSITION_DURATION_MS = 10L
private val DEFAULT_DURATION = 500.milliseconds
}
}

View File

@@ -36,75 +36,40 @@ import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.launch
@SysUISingleton
class DreamingTransitionInteractor
class FromDreamingTransitionInteractor
@Inject
constructor(
@Application private val scope: CoroutineScope,
private val keyguardInteractor: KeyguardInteractor,
private val keyguardTransitionRepository: KeyguardTransitionRepository,
private val keyguardTransitionInteractor: KeyguardTransitionInteractor,
) : TransitionInteractor(DreamingTransitionInteractor::class.simpleName!!) {
private val canDreamFrom =
setOf(KeyguardState.LOCKSCREEN, KeyguardState.GONE, KeyguardState.DOZING)
) : TransitionInteractor(FromDreamingTransitionInteractor::class.simpleName!!) {
override fun start() {
listenForEntryToDreaming()
listenForDreamingToLockscreen()
listenForDreamingToOccluded()
listenForDreamingToGone()
listenForDreamingToDozing()
}
private fun listenForEntryToDreaming() {
scope.launch {
keyguardInteractor.isDreaming
.sample(
combine(
keyguardInteractor.dozeTransitionModel,
keyguardTransitionInteractor.finishedKeyguardState,
::Pair
),
::toTriple
)
.collect { triple ->
val (isDreaming, dozeTransitionModel, keyguardState) = triple
// Dozing/AOD and dreaming have overlapping events. If the state remains in
// FINISH, it means that doze mode is not running and DREAMING is ok to
// commence.
if (
isDozeOff(dozeTransitionModel.to) &&
isDreaming &&
canDreamFrom.contains(keyguardState)
) {
keyguardTransitionRepository.startTransition(
TransitionInfo(
name,
keyguardState,
KeyguardState.DREAMING,
getAnimator(),
)
)
}
}
}
}
private fun listenForDreamingToLockscreen() {
scope.launch {
keyguardInteractor.isDreaming
// Using isDreamingWithOverlay provides an optimized path to LOCKSCREEN state, which
// otherwise would have gone through OCCLUDED first
keyguardInteractor.isDreamingWithOverlay
.sample(
combine(
keyguardInteractor.dozeTransitionModel,
keyguardTransitionInteractor.startedKeyguardTransitionStep,
::Pair,
::Pair
),
::toTriple
)
.collect { triple ->
val (isDreaming, dozeTransitionModel, lastStartedTransition) = triple
if (
isDozeOff(dozeTransitionModel.to) &&
!isDreaming &&
!isDreaming &&
isDozeOff(dozeTransitionModel.to) &&
lastStartedTransition.to == KeyguardState.DREAMING
) {
keyguardTransitionRepository.startTransition(
@@ -120,6 +85,42 @@ constructor(
}
}
private fun listenForDreamingToOccluded() {
scope.launch {
keyguardInteractor.isDreaming
.sample(
combine(
keyguardInteractor.isKeyguardOccluded,
keyguardTransitionInteractor.startedKeyguardTransitionStep,
::Pair,
),
::toTriple
)
.collect { triple ->
val (isDreaming, isOccluded, lastStartedTransition) = triple
if (
isOccluded &&
!isDreaming &&
(lastStartedTransition.to == KeyguardState.DREAMING ||
lastStartedTransition.to == KeyguardState.LOCKSCREEN)
) {
// At the moment, checking for LOCKSCREEN state above provides a corrective
// action. There's no great signal to determine when the dream is ending
// and a transition to OCCLUDED is beginning directly. For now, the solution
// is DREAMING->LOCKSCREEN->OCCLUDED
keyguardTransitionRepository.startTransition(
TransitionInfo(
name,
lastStartedTransition.to,
KeyguardState.OCCLUDED,
getAnimator(),
)
)
}
}
}
}
private fun listenForDreamingToGone() {
scope.launch {
keyguardInteractor.biometricUnlockState

View File

@@ -30,19 +30,44 @@ import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.launch
@SysUISingleton
class GoneAodTransitionInteractor
class FromGoneTransitionInteractor
@Inject
constructor(
@Application private val scope: CoroutineScope,
private val keyguardInteractor: KeyguardInteractor,
private val keyguardTransitionRepository: KeyguardTransitionRepository,
private val keyguardTransitionInteractor: KeyguardTransitionInteractor,
) : TransitionInteractor(GoneAodTransitionInteractor::class.simpleName!!) {
) : TransitionInteractor(FromGoneTransitionInteractor::class.simpleName!!) {
override fun start() {
listenForGoneToAod()
listenForGoneToDreaming()
}
private fun listenForGoneToDreaming() {
scope.launch {
keyguardInteractor.isAbleToDream
.sample(keyguardTransitionInteractor.finishedKeyguardState, ::Pair)
.collect { pair ->
val (isAbleToDream, keyguardState) = pair
if (isAbleToDream && keyguardState == KeyguardState.GONE) {
keyguardTransitionRepository.startTransition(
TransitionInfo(
name,
KeyguardState.GONE,
KeyguardState.DREAMING,
getAnimator(),
)
)
}
}
}
}
private fun listenForGoneToAod() {
scope.launch {
keyguardInteractor.wakefulnessModel
.sample(keyguardTransitionInteractor.finishedKeyguardState, { a, b -> Pair(a, b) })
.sample(keyguardTransitionInteractor.finishedKeyguardState, ::Pair)
.collect { pair ->
val (wakefulnessState, keyguardState) = pair
if (

View File

@@ -21,11 +21,11 @@ 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.DozeStateModel
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.keyguard.shared.model.StatusBarState.KEYGUARD
import com.android.systemui.keyguard.shared.model.TransitionInfo
import com.android.systemui.keyguard.shared.model.TransitionState
import com.android.systemui.keyguard.shared.model.WakefulnessState
import com.android.systemui.shade.data.repository.ShadeRepository
import com.android.systemui.util.kotlin.sample
import java.util.UUID
@@ -36,57 +36,54 @@ import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.launch
@SysUISingleton
class LockscreenBouncerTransitionInteractor
class FromLockscreenTransitionInteractor
@Inject
constructor(
@Application private val scope: CoroutineScope,
private val keyguardInteractor: KeyguardInteractor,
private val shadeRepository: ShadeRepository,
private val keyguardTransitionInteractor: KeyguardTransitionInteractor,
private val keyguardTransitionRepository: KeyguardTransitionRepository,
private val keyguardTransitionInteractor: KeyguardTransitionInteractor
) : TransitionInteractor(LockscreenBouncerTransitionInteractor::class.simpleName!!) {
) : TransitionInteractor(FromLockscreenTransitionInteractor::class.simpleName!!) {
private var transitionId: UUID? = null
override fun start() {
listenForDraggingUpToBouncer()
listenForBouncer()
listenForLockscreenToGone()
listenForLockscreenToOccluded()
listenForLockscreenToAod()
listenForLockscreenToBouncer()
listenForLockscreenToDreaming()
listenForLockscreenToBouncerDragging()
}
private fun listenForBouncer() {
private fun listenForLockscreenToDreaming() {
scope.launch {
keyguardInteractor.isBouncerShowing
.sample(
combine(
keyguardInteractor.wakefulnessModel,
keyguardTransitionInteractor.startedKeyguardTransitionStep,
::Pair
),
::toTriple
)
.collect { triple ->
val (isBouncerShowing, wakefulnessState, lastStartedTransitionStep) = triple
if (
!isBouncerShowing && lastStartedTransitionStep.to == KeyguardState.BOUNCER
) {
val to =
if (
wakefulnessState.state == WakefulnessState.STARTING_TO_SLEEP ||
wakefulnessState.state == WakefulnessState.ASLEEP
) {
KeyguardState.AOD
} else {
KeyguardState.LOCKSCREEN
}
keyguardInteractor.isAbleToDream
.sample(keyguardTransitionInteractor.startedKeyguardTransitionStep, ::Pair)
.collect { pair ->
val (isAbleToDream, lastStartedTransition) = pair
if (isAbleToDream && lastStartedTransition.to == KeyguardState.LOCKSCREEN) {
keyguardTransitionRepository.startTransition(
TransitionInfo(
ownerName = name,
from = KeyguardState.BOUNCER,
to = to,
animator = getAnimator(),
name,
KeyguardState.LOCKSCREEN,
KeyguardState.DREAMING,
getAnimator(),
)
)
} else if (
}
}
}
}
private fun listenForLockscreenToBouncer() {
scope.launch {
keyguardInteractor.isBouncerShowing
.sample(keyguardTransitionInteractor.startedKeyguardTransitionStep, ::Pair)
.collect { pair ->
val (isBouncerShowing, lastStartedTransitionStep) = pair
if (
isBouncerShowing && lastStartedTransitionStep.to == KeyguardState.LOCKSCREEN
) {
keyguardTransitionRepository.startTransition(
@@ -98,13 +95,12 @@ constructor(
)
)
}
Unit
}
}
}
/* Starts transitions when manually dragging up the bouncer from the lockscreen. */
private fun listenForDraggingUpToBouncer() {
private fun listenForLockscreenToBouncerDragging() {
scope.launch {
shadeRepository.shadeModel
.sample(
@@ -157,6 +153,76 @@ constructor(
}
}
private fun listenForLockscreenToGone() {
scope.launch {
keyguardInteractor.isKeyguardGoingAway
.sample(keyguardTransitionInteractor.startedKeyguardTransitionStep, ::Pair)
.collect { pair ->
val (isKeyguardGoingAway, lastStartedStep) = pair
if (isKeyguardGoingAway && lastStartedStep.to == KeyguardState.LOCKSCREEN) {
keyguardTransitionRepository.startTransition(
TransitionInfo(
name,
KeyguardState.LOCKSCREEN,
KeyguardState.GONE,
getAnimator(),
)
)
}
}
}
}
private fun listenForLockscreenToOccluded() {
scope.launch {
keyguardInteractor.isKeyguardOccluded
.sample(
combine(
keyguardTransitionInteractor.finishedKeyguardState,
keyguardInteractor.isDreaming,
::Pair
),
::toTriple
)
.collect { triple ->
val (isOccluded, keyguardState, isDreaming) = triple
// Occlusion signals come from the framework, and should interrupt any
// existing transition
if (isOccluded && !isDreaming) {
keyguardTransitionRepository.startTransition(
TransitionInfo(
name,
keyguardState,
KeyguardState.OCCLUDED,
getAnimator(),
)
)
}
}
}
}
private fun listenForLockscreenToAod() {
scope.launch {
keyguardInteractor
.dozeTransitionTo(DozeStateModel.DOZE_AOD)
.sample(keyguardTransitionInteractor.startedKeyguardTransitionStep, ::Pair)
.collect { pair ->
val (dozeToAod, lastStartedStep) = pair
if (lastStartedStep.to == KeyguardState.LOCKSCREEN) {
keyguardTransitionRepository.startTransition(
TransitionInfo(
name,
KeyguardState.LOCKSCREEN,
KeyguardState.AOD,
getAnimator(),
)
)
}
}
}
}
private fun getAnimator(): ValueAnimator {
return ValueAnimator().apply {
setInterpolator(Interpolators.LINEAR)
@@ -165,6 +231,6 @@ constructor(
}
companion object {
private const val TRANSITION_DURATION_MS = 300L
private const val TRANSITION_DURATION_MS = 500L
}
}

View File

@@ -21,39 +21,43 @@ 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.BiometricUnlockModel.Companion.isWakeAndUnlock
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 kotlin.time.Duration
import kotlin.time.Duration.Companion.milliseconds
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.launch
@SysUISingleton
class AodToGoneTransitionInteractor
class FromOccludedTransitionInteractor
@Inject
constructor(
@Application private val scope: CoroutineScope,
private val keyguardInteractor: KeyguardInteractor,
private val keyguardTransitionRepository: KeyguardTransitionRepository,
private val keyguardTransitionInteractor: KeyguardTransitionInteractor,
) : TransitionInteractor(AodToGoneTransitionInteractor::class.simpleName!!) {
) : TransitionInteractor(FromOccludedTransitionInteractor::class.simpleName!!) {
override fun start() {
listenForOccludedToLockscreen()
listenForOccludedToDreaming()
}
private fun listenForOccludedToDreaming() {
scope.launch {
keyguardInteractor.biometricUnlockState
.sample(keyguardTransitionInteractor.finishedKeyguardState, { a, b -> Pair(a, b) })
keyguardInteractor.isAbleToDream
.sample(keyguardTransitionInteractor.finishedKeyguardState, ::Pair)
.collect { pair ->
val (biometricUnlockState, keyguardState) = pair
if (
keyguardState == KeyguardState.AOD && isWakeAndUnlock(biometricUnlockState)
) {
val (isAbleToDream, keyguardState) = pair
if (isAbleToDream && keyguardState == KeyguardState.OCCLUDED) {
keyguardTransitionRepository.startTransition(
TransitionInfo(
name,
KeyguardState.AOD,
KeyguardState.GONE,
KeyguardState.OCCLUDED,
KeyguardState.DREAMING,
getAnimator(),
)
)
@@ -62,14 +66,37 @@ constructor(
}
}
private fun getAnimator(): ValueAnimator {
private fun listenForOccludedToLockscreen() {
scope.launch {
keyguardInteractor.isKeyguardOccluded
.sample(keyguardTransitionInteractor.startedKeyguardTransitionStep, ::Pair)
.collect { pair ->
val (isOccluded, lastStartedKeyguardState) = pair
// Occlusion signals come from the framework, and should interrupt any
// existing transition
if (!isOccluded && lastStartedKeyguardState.to == KeyguardState.OCCLUDED) {
keyguardTransitionRepository.startTransition(
TransitionInfo(
name,
KeyguardState.OCCLUDED,
KeyguardState.LOCKSCREEN,
getAnimator(),
)
)
}
}
}
}
private fun getAnimator(duration: Duration = DEFAULT_DURATION): ValueAnimator {
return ValueAnimator().apply {
setInterpolator(Interpolators.LINEAR)
setDuration(TRANSITION_DURATION_MS)
setDuration(duration.inWholeMilliseconds)
}
}
companion object {
private const val TRANSITION_DURATION_MS = 500L
private val DEFAULT_DURATION = 500.milliseconds
val TO_LOCKSCREEN_DURATION = 1183.milliseconds
}
}

View File

@@ -22,12 +22,16 @@ import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.keyguard.data.repository.KeyguardRepository
import com.android.systemui.keyguard.shared.model.BiometricUnlockModel
import com.android.systemui.keyguard.shared.model.DozeStateModel
import com.android.systemui.keyguard.shared.model.DozeStateModel.Companion.isDozeOff
import com.android.systemui.keyguard.shared.model.DozeTransitionModel
import com.android.systemui.keyguard.shared.model.StatusBarState
import com.android.systemui.keyguard.shared.model.WakefulnessModel
import com.android.systemui.util.kotlin.sample
import javax.inject.Inject
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.merge
/**
* Encapsulates business-logic related to the keyguard but not to a more specific part within it.
@@ -52,8 +56,27 @@ constructor(
* but not vice-versa.
*/
val isDreaming: Flow<Boolean> = repository.isDreaming
/** Whether the system is dreaming with an overlay active */
val isDreamingWithOverlay: Flow<Boolean> = repository.isDreamingWithOverlay
/**
* Dozing and dreaming have overlapping events. If the doze state remains in FINISH, it means
* that doze mode is not running and DREAMING is ok to commence.
*/
val isAbleToDream: Flow<Boolean> =
merge(isDreaming, isDreamingWithOverlay)
.sample(
dozeTransitionModel,
{ isDreaming, dozeTransitionModel ->
isDreaming && isDozeOff(dozeTransitionModel.to)
}
)
.distinctUntilChanged()
/** Whether the keyguard is showing or not. */
val isKeyguardShowing: Flow<Boolean> = repository.isKeyguardShowing
/** Whether the keyguard is occluded (covered by an activity). */
val isKeyguardOccluded: Flow<Boolean> = repository.isKeyguardOccluded
/** Whether the keyguard is going away. */
val isKeyguardGoingAway: Flow<Boolean> = repository.isKeyguardGoingAway
/** Whether the bouncer is showing or not. */
@@ -74,8 +97,8 @@ constructor(
/** The approximate location on the screen of the face unlock sensor, if one is available. */
val faceSensorLocation: Flow<Point?> = repository.faceSensorLocation
fun dozeTransitionTo(state: DozeStateModel): Flow<DozeTransitionModel> {
return dozeTransitionModel.filter { it.to == state }
fun dozeTransitionTo(vararg states: DozeStateModel): Flow<DozeTransitionModel> {
return dozeTransitionModel.filter { states.contains(it.to) }
}
fun isKeyguardShowing(): Boolean {
return repository.isKeyguardShowing()

View File

@@ -37,13 +37,13 @@ constructor(
// exhaustive
val ret =
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")
is AodToGoneTransitionInteractor -> Log.d(TAG, "Started $it")
is BouncerToGoneTransitionInteractor -> Log.d(TAG, "Started $it")
is DreamingTransitionInteractor -> Log.d(TAG, "Started $it")
is FromBouncerTransitionInteractor -> Log.d(TAG, "Started $it")
is FromAodTransitionInteractor -> Log.d(TAG, "Started $it")
is FromGoneTransitionInteractor -> Log.d(TAG, "Started $it")
is FromLockscreenTransitionInteractor -> Log.d(TAG, "Started $it")
is FromDreamingTransitionInteractor -> Log.d(TAG, "Started $it")
is FromOccludedTransitionInteractor -> Log.d(TAG, "Started $it")
is FromDozingTransitionInteractor -> Log.d(TAG, "Started $it")
}
it.start()
}

View File

@@ -89,6 +89,7 @@ constructor(
KeyguardState.BOUNCER -> true
KeyguardState.LOCKSCREEN -> true
KeyguardState.GONE -> true
KeyguardState.OCCLUDED -> true
}
}
}

View File

@@ -32,25 +32,25 @@ abstract class StartKeyguardTransitionModule {
@Binds
@IntoSet
abstract fun lockscreenBouncer(
impl: LockscreenBouncerTransitionInteractor
): TransitionInteractor
abstract fun fromBouncer(impl: FromBouncerTransitionInteractor): TransitionInteractor
@Binds
@IntoSet
abstract fun aodLockscreen(impl: AodLockscreenTransitionInteractor): TransitionInteractor
abstract fun fromLockscreen(impl: FromLockscreenTransitionInteractor): TransitionInteractor
@Binds @IntoSet abstract fun goneAod(impl: GoneAodTransitionInteractor): TransitionInteractor
@Binds @IntoSet abstract fun fromAod(impl: FromAodTransitionInteractor): TransitionInteractor
@Binds @IntoSet abstract fun aodGone(impl: AodToGoneTransitionInteractor): TransitionInteractor
@Binds @IntoSet abstract fun fromGone(impl: FromGoneTransitionInteractor): TransitionInteractor
@Binds
@IntoSet
abstract fun bouncerGone(impl: BouncerToGoneTransitionInteractor): TransitionInteractor
abstract fun fromDreaming(impl: FromDreamingTransitionInteractor): TransitionInteractor
@Binds
@IntoSet
abstract fun lockscreenGone(impl: LockscreenGoneTransitionInteractor): TransitionInteractor
abstract fun fromOccluded(impl: FromOccludedTransitionInteractor): TransitionInteractor
@Binds @IntoSet abstract fun dreaming(impl: DreamingTransitionInteractor): TransitionInteractor
@Binds
@IntoSet
abstract fun fromDozing(impl: FromDozingTransitionInteractor): TransitionInteractor
}

View File

@@ -31,4 +31,6 @@ sealed class TransitionInteractor(val name: String) {
abstract fun start()
fun <A, B, C> toTriple(a: A, bc: Pair<B, C>) = Triple(a, bc.first, bc.second)
fun <A, B, C> toTriple(ab: Pair<A, B>, c: C) = Triple(ab.first, ab.second, c)
}

View File

@@ -57,4 +57,8 @@ enum class KeyguardState {
* with SWIPE security method or face unlock without bypass.
*/
GONE,
/*
* An activity is displaying over the keyguard.
*/
OCCLUDED,
}

View File

@@ -19,7 +19,7 @@ package com.android.systemui.keyguard.ui.viewmodel
import com.android.systemui.animation.Interpolators.EMPHASIZED_ACCELERATE
import com.android.systemui.animation.Interpolators.EMPHASIZED_DECELERATE
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.keyguard.domain.interactor.DreamingTransitionInteractor.Companion.TO_LOCKSCREEN_DURATION
import com.android.systemui.keyguard.domain.interactor.FromDreamingTransitionInteractor.Companion.TO_LOCKSCREEN_DURATION
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
import com.android.systemui.keyguard.shared.model.AnimationParams
import javax.inject.Inject

View File

@@ -18,6 +18,7 @@ package com.android.systemui.dreams
import android.testing.AndroidTestingRunner
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.google.common.truth.Truth.assertThat
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
@@ -30,23 +31,27 @@ import org.mockito.MockitoAnnotations
@SmallTest
@RunWith(AndroidTestingRunner::class)
class DreamCallbackControllerTest : SysuiTestCase() {
class DreamOverlayCallbackControllerTest : SysuiTestCase() {
@Mock private lateinit var callback: DreamCallbackController.DreamCallback
@Mock private lateinit var callback: DreamOverlayCallbackController.Callback
private lateinit var underTest: DreamCallbackController
private lateinit var underTest: DreamOverlayCallbackController
@Before
fun setUp() {
MockitoAnnotations.initMocks(this)
underTest = DreamCallbackController()
underTest = DreamOverlayCallbackController()
}
@Test
fun testOnWakeUpInvokesCallback() {
fun onWakeUpInvokesCallback() {
underTest.onStartDream()
assertThat(underTest.isDreaming).isEqualTo(true)
underTest.addCallback(callback)
underTest.onWakeUp()
verify(callback).onWakeUp()
assertThat(underTest.isDreaming).isEqualTo(false)
// Adding twice should not invoke twice
reset(callback)
@@ -60,4 +65,27 @@ class DreamCallbackControllerTest : SysuiTestCase() {
underTest.onWakeUp()
verify(callback, never()).onWakeUp()
}
@Test
fun onStartDreamInvokesCallback() {
underTest.addCallback(callback)
assertThat(underTest.isDreaming).isEqualTo(false)
underTest.onStartDream()
verify(callback).onStartDream()
assertThat(underTest.isDreaming).isEqualTo(true)
// Adding twice should not invoke twice
reset(callback)
underTest.addCallback(callback)
underTest.onStartDream()
verify(callback, times(1)).onStartDream()
// After remove, no call to callback
reset(callback)
underTest.removeCallback(callback)
underTest.onStartDream()
verify(callback, never()).onStartDream()
}
}

View File

@@ -114,7 +114,7 @@ public class DreamOverlayServiceTest extends SysuiTestCase {
UiEventLogger mUiEventLogger;
@Mock
DreamCallbackController mDreamCallbackController;
DreamOverlayCallbackController mDreamOverlayCallbackController;
@Captor
ArgumentCaptor<View> mViewCaptor;
@@ -145,7 +145,7 @@ public class DreamOverlayServiceTest extends SysuiTestCase {
mKeyguardUpdateMonitor,
mUiEventLogger,
LOW_LIGHT_COMPONENT,
mDreamCallbackController);
mDreamOverlayCallbackController);
}
@Test
@@ -357,7 +357,7 @@ public class DreamOverlayServiceTest extends SysuiTestCase {
mService.onWakeUp(callback);
mMainExecutor.runAllReady();
verify(mDreamOverlayContainerViewController).wakeUp(callback, mMainExecutor);
verify(mDreamCallbackController).onWakeUp();
verify(mDreamOverlayCallbackController).onWakeUp();
}
@Test

View File

@@ -28,8 +28,7 @@ import com.android.systemui.doze.DozeHost
import com.android.systemui.doze.DozeMachine
import com.android.systemui.doze.DozeTransitionCallback
import com.android.systemui.doze.DozeTransitionListener
import com.android.systemui.dreams.DreamCallbackController
import com.android.systemui.dreams.DreamCallbackController.DreamCallback
import com.android.systemui.dreams.DreamOverlayCallbackController
import com.android.systemui.keyguard.WakefulnessLifecycle
import com.android.systemui.keyguard.shared.model.BiometricUnlockModel
import com.android.systemui.keyguard.shared.model.BiometricUnlockSource
@@ -68,7 +67,7 @@ class KeyguardRepositoryImplTest : SysuiTestCase() {
@Mock private lateinit var dozeTransitionListener: DozeTransitionListener
@Mock private lateinit var authController: AuthController
@Mock private lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor
@Mock private lateinit var dreamCallbackController: DreamCallbackController
@Mock private lateinit var dreamOverlayCallbackController: DreamOverlayCallbackController
private lateinit var underTest: KeyguardRepositoryImpl
@@ -86,7 +85,7 @@ class KeyguardRepositoryImplTest : SysuiTestCase() {
keyguardUpdateMonitor,
dozeTransitionListener,
authController,
dreamCallbackController,
dreamOverlayCallbackController,
)
}
@@ -170,6 +169,29 @@ class KeyguardRepositoryImplTest : SysuiTestCase() {
job.cancel()
}
@Test
fun isKeyguardOccluded() =
runTest(UnconfinedTestDispatcher()) {
whenever(keyguardStateController.isOccluded).thenReturn(false)
var latest: Boolean? = null
val job = underTest.isKeyguardOccluded.onEach { latest = it }.launchIn(this)
assertThat(latest).isFalse()
val captor = argumentCaptor<KeyguardStateController.Callback>()
verify(keyguardStateController).addCallback(captor.capture())
whenever(keyguardStateController.isOccluded).thenReturn(true)
captor.value.onKeyguardShowingChanged()
assertThat(latest).isTrue()
whenever(keyguardStateController.isOccluded).thenReturn(false)
captor.value.onKeyguardShowingChanged()
assertThat(latest).isFalse()
job.cancel()
}
@Test
fun isDozing() =
runTest(UnconfinedTestDispatcher()) {
@@ -343,19 +365,22 @@ class KeyguardRepositoryImplTest : SysuiTestCase() {
}
@Test
fun isDreamingFromDreamCallbackController() =
fun isDreamingFromDreamOverlayCallbackController() =
runTest(UnconfinedTestDispatcher()) {
whenever(keyguardUpdateMonitor.isDreaming()).thenReturn(true)
whenever(dreamOverlayCallbackController.isDreaming).thenReturn(false)
var latest: Boolean? = null
val job = underTest.isDreaming.onEach { latest = it }.launchIn(this)
val job = underTest.isDreamingWithOverlay.onEach { latest = it }.launchIn(this)
assertThat(latest).isTrue()
assertThat(latest).isFalse()
val listener =
withArgCaptor<DreamCallbackController.DreamCallback> {
verify(dreamCallbackController).addCallback(capture())
withArgCaptor<DreamOverlayCallbackController.Callback> {
verify(dreamOverlayCallbackController).addCallback(capture())
}
listener.onStartDream()
assertThat(latest).isTrue()
listener.onWakeUp()
assertThat(latest).isFalse()

View File

@@ -23,6 +23,8 @@ import com.android.systemui.animation.Interpolators
import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository
import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepositoryImpl
import com.android.systemui.keyguard.shared.model.DozeStateModel
import com.android.systemui.keyguard.shared.model.DozeTransitionModel
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.keyguard.shared.model.TransitionInfo
import com.android.systemui.keyguard.shared.model.WakeSleepReason
@@ -42,6 +44,7 @@ import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.JUnit4
import org.mockito.Mock
import org.mockito.Mockito.reset
import org.mockito.Mockito.verify
import org.mockito.MockitoAnnotations
@@ -64,8 +67,8 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() {
// Used to verify transition requests for test output
@Mock private lateinit var mockTransitionRepository: KeyguardTransitionRepository
private lateinit var lockscreenBouncerTransitionInteractor:
LockscreenBouncerTransitionInteractor
private lateinit var fromLockscreenTransitionInteractor: FromLockscreenTransitionInteractor
private lateinit var fromDreamingTransitionInteractor: FromDreamingTransitionInteractor
@Before
fun setUp() {
@@ -79,17 +82,74 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() {
transitionRepository = KeyguardTransitionRepositoryImpl()
runner = KeyguardTransitionRunner(transitionRepository)
lockscreenBouncerTransitionInteractor =
LockscreenBouncerTransitionInteractor(
fromLockscreenTransitionInteractor =
FromLockscreenTransitionInteractor(
scope = testScope,
keyguardInteractor = KeyguardInteractor(keyguardRepository),
shadeRepository = shadeRepository,
keyguardTransitionRepository = mockTransitionRepository,
keyguardTransitionInteractor = KeyguardTransitionInteractor(transitionRepository),
)
lockscreenBouncerTransitionInteractor.start()
fromLockscreenTransitionInteractor.start()
fromDreamingTransitionInteractor =
FromDreamingTransitionInteractor(
scope = testScope,
keyguardInteractor = KeyguardInteractor(keyguardRepository),
keyguardTransitionRepository = mockTransitionRepository,
keyguardTransitionInteractor = KeyguardTransitionInteractor(transitionRepository),
)
fromDreamingTransitionInteractor.start()
}
@Test
fun `DREAMING to LOCKSCREEN`() =
testScope.runTest {
// GIVEN a device is dreaming and occluded
keyguardRepository.setDreamingWithOverlay(true)
keyguardRepository.setKeyguardOccluded(true)
runCurrent()
// GIVEN a prior transition has run to DREAMING
runner.startTransition(
testScope,
TransitionInfo(
ownerName = "",
from = KeyguardState.LOCKSCREEN,
to = KeyguardState.DREAMING,
animator =
ValueAnimator().apply {
duration = 10
interpolator = Interpolators.LINEAR
},
)
)
runCurrent()
reset(mockTransitionRepository)
// WHEN doze is complete
keyguardRepository.setDozeTransitionModel(
DozeTransitionModel(from = DozeStateModel.DOZE, to = DozeStateModel.FINISH)
)
// AND dreaming has stopped
keyguardRepository.setDreamingWithOverlay(false)
// AND occluded has stopped
keyguardRepository.setKeyguardOccluded(false)
runCurrent()
val info =
withArgCaptor<TransitionInfo> {
verify(mockTransitionRepository).startTransition(capture())
}
// THEN a transition to BOUNCER should occur
assertThat(info.ownerName).isEqualTo("FromDreamingTransitionInteractor")
assertThat(info.from).isEqualTo(KeyguardState.DREAMING)
assertThat(info.to).isEqualTo(KeyguardState.LOCKSCREEN)
assertThat(info.animator).isNotNull()
coroutineContext.cancelChildren()
}
@Test
fun `LOCKSCREEN to BOUNCER via bouncer showing call`() =
testScope.runTest {
@@ -97,7 +157,7 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() {
keyguardRepository.setWakefulnessModel(startingToWake())
runCurrent()
// GIVEN a transition has run to LOCKSCREEN
// GIVEN a prior transition has run to LOCKSCREEN
runner.startTransition(
testScope,
TransitionInfo(
@@ -122,7 +182,7 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() {
verify(mockTransitionRepository).startTransition(capture())
}
// THEN a transition to BOUNCER should occur
assertThat(info.ownerName).isEqualTo("LockscreenBouncerTransitionInteractor")
assertThat(info.ownerName).isEqualTo("FromLockscreenTransitionInteractor")
assertThat(info.from).isEqualTo(KeyguardState.LOCKSCREEN)
assertThat(info.to).isEqualTo(KeyguardState.BOUNCER)
assertThat(info.animator).isNotNull()

View File

@@ -21,7 +21,7 @@ import com.android.systemui.SysuiTestCase
import com.android.systemui.animation.Interpolators.EMPHASIZED_ACCELERATE
import com.android.systemui.animation.Interpolators.EMPHASIZED_DECELERATE
import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
import com.android.systemui.keyguard.domain.interactor.DreamingTransitionInteractor.Companion.TO_LOCKSCREEN_DURATION
import com.android.systemui.keyguard.domain.interactor.FromDreamingTransitionInteractor.Companion.TO_LOCKSCREEN_DURATION
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
import com.android.systemui.keyguard.shared.model.AnimationParams
import com.android.systemui.keyguard.shared.model.KeyguardState

View File

@@ -46,12 +46,18 @@ class FakeKeyguardRepository : KeyguardRepository {
private val _isKeyguardShowing = MutableStateFlow(false)
override val isKeyguardShowing: Flow<Boolean> = _isKeyguardShowing
private val _isKeyguardOccluded = MutableStateFlow(false)
override val isKeyguardOccluded: Flow<Boolean> = _isKeyguardOccluded
private val _isDozing = MutableStateFlow(false)
override val isDozing: Flow<Boolean> = _isDozing
private val _isDreaming = MutableStateFlow(false)
override val isDreaming: Flow<Boolean> = _isDreaming
private val _isDreamingWithOverlay = MutableStateFlow(false)
override val isDreamingWithOverlay: Flow<Boolean> = _isDreamingWithOverlay
private val _dozeAmount = MutableStateFlow(0f)
override val linearDozeAmount: Flow<Float> = _dozeAmount
@@ -112,10 +118,18 @@ class FakeKeyguardRepository : KeyguardRepository {
_isKeyguardShowing.value = isShowing
}
fun setKeyguardOccluded(isOccluded: Boolean) {
_isKeyguardOccluded.value = isOccluded
}
fun setDozing(isDozing: Boolean) {
_isDozing.value = isDozing
}
fun setDreamingWithOverlay(isDreaming: Boolean) {
_isDreamingWithOverlay.value = isDreaming
}
fun setDozeAmount(dozeAmount: Float) {
_dozeAmount.value = dozeAmount
}
@@ -144,6 +158,10 @@ class FakeKeyguardRepository : KeyguardRepository {
_fingerprintSensorLocation.tryEmit(location)
}
fun setDozeTransitionModel(model: DozeTransitionModel) {
_dozeTransitionModel.value = model
}
override fun isUdfpsSupported(): Boolean {
return _isUdfpsSupported.value
}