diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt index 953e8d370fd8b..9d5d8bbd4f408 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt @@ -17,6 +17,7 @@ package com.android.systemui.keyguard.data.repository import com.android.keyguard.KeyguardUpdateMonitor +import com.android.keyguard.KeyguardUpdateMonitorCallback import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow import com.android.systemui.common.shared.model.Position @@ -87,6 +88,14 @@ interface KeyguardRepository { */ val isDozing: Flow + /** + * Observable for whether the device is dreaming. + * + * Dozing/AOD is a specific type of dream, but it is also possible for other non-systemui dreams + * to be active, such as screensavers. + */ + val isDreaming: Flow + /** * Observable for the amount of doze we are currently in. * @@ -139,12 +148,12 @@ interface KeyguardRepository { class KeyguardRepositoryImpl @Inject constructor( - statusBarStateController: StatusBarStateController, - dozeHost: DozeHost, - wakefulnessLifecycle: WakefulnessLifecycle, - biometricUnlockController: BiometricUnlockController, - private val keyguardStateController: KeyguardStateController, - private val keyguardUpdateMonitor: KeyguardUpdateMonitor, + statusBarStateController: StatusBarStateController, + dozeHost: DozeHost, + wakefulnessLifecycle: WakefulnessLifecycle, + biometricUnlockController: BiometricUnlockController, + private val keyguardStateController: KeyguardStateController, + private val keyguardUpdateMonitor: KeyguardUpdateMonitor, ) : KeyguardRepository { private val _animateBottomAreaDozingTransitions = MutableStateFlow(false) override val animateBottomAreaDozingTransitions = @@ -244,6 +253,25 @@ constructor( } .distinctUntilChanged() + override val isDreaming: Flow = + conflatedCallbackFlow { + val callback = + object : KeyguardUpdateMonitorCallback() { + override fun onDreamingStateChanged(isDreaming: Boolean) { + trySendWithFailureLogging(isDreaming, TAG, "updated isDreaming") + } + } + keyguardUpdateMonitor.registerCallback(callback) + trySendWithFailureLogging( + keyguardUpdateMonitor.isDreaming, + TAG, + "initial isDreaming", + ) + + awaitClose { keyguardUpdateMonitor.removeCallback(callback) } + } + .distinctUntilChanged() + override val dozeAmount: Flow = conflatedCallbackFlow { val callback = object : StatusBarStateController.StateListener { diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepository.kt index 63fb4a9e8cad5..bce7d92cd8fb9 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepository.kt @@ -112,7 +112,7 @@ class KeyguardTransitionRepositoryImpl @Inject constructor() : KeyguardTransitio // Seed with transitions signaling a boot into lockscreen state emitTransition( TransitionStep( - KeyguardState.NONE, + KeyguardState.OFF, KeyguardState.LOCKSCREEN, 0f, TransitionState.STARTED, @@ -120,7 +120,7 @@ class KeyguardTransitionRepositoryImpl @Inject constructor() : KeyguardTransitio ) emitTransition( TransitionStep( - KeyguardState.NONE, + KeyguardState.OFF, KeyguardState.LOCKSCREEN, 1f, TransitionState.FINISHED, diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/DreamingLockscreenTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/DreamingLockscreenTransitionInteractor.kt new file mode 100644 index 0000000000000..c44cda42c68d4 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/DreamingLockscreenTransitionInteractor.kt @@ -0,0 +1,81 @@ +/* + * 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.util.kotlin.sample +import javax.inject.Inject +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.flow.collect +import kotlinx.coroutines.launch + +@SysUISingleton +class DreamingLockscreenTransitionInteractor +@Inject +constructor( + @Application private val scope: CoroutineScope, + private val keyguardInteractor: KeyguardInteractor, + private val keyguardTransitionRepository: KeyguardTransitionRepository, + private val keyguardTransitionInteractor: KeyguardTransitionInteractor, +) : TransitionInteractor("DREAMING<->LOCKSCREEN") { + + override fun start() { + scope.launch { + keyguardInteractor.isDreaming + .sample(keyguardTransitionInteractor.finishedKeyguardState, { a, b -> Pair(a, b) }) + .collect { pair -> + val (isDreaming, keyguardState) = pair + if (isDreaming && keyguardState == KeyguardState.LOCKSCREEN) { + keyguardTransitionRepository.startTransition( + TransitionInfo( + name, + KeyguardState.LOCKSCREEN, + KeyguardState.DREAMING, + getAnimator(), + ) + ) + } else if (!isDreaming && keyguardState == KeyguardState.DREAMING) { + keyguardTransitionRepository.startTransition( + TransitionInfo( + name, + KeyguardState.DREAMING, + KeyguardState.LOCKSCREEN, + getAnimator(), + ) + ) + } + } + } + } + + private fun getAnimator(): ValueAnimator { + return ValueAnimator().apply { + setInterpolator(Interpolators.LINEAR) + setDuration(TRANSITION_DURATION_MS) + } + } + + companion object { + private const val TRANSITION_DURATION_MS = 500L + } +} diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/DreamingToAodTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/DreamingToAodTransitionInteractor.kt new file mode 100644 index 0000000000000..9e2b7241ade22 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/DreamingToAodTransitionInteractor.kt @@ -0,0 +1,76 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +package com.android.systemui.keyguard.domain.interactor + +import android.animation.ValueAnimator +import com.android.systemui.animation.Interpolators +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.dagger.qualifiers.Application +import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository +import com.android.systemui.keyguard.shared.model.KeyguardState +import com.android.systemui.keyguard.shared.model.TransitionInfo +import com.android.systemui.keyguard.shared.model.WakefulnessModel.Companion.isSleepingOrStartingToSleep +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 DreamingToAodTransitionInteractor +@Inject +constructor( + @Application private val scope: CoroutineScope, + private val keyguardInteractor: KeyguardInteractor, + private val keyguardTransitionRepository: KeyguardTransitionRepository, + private val keyguardTransitionInteractor: KeyguardTransitionInteractor, +) : TransitionInteractor("DREAMING->AOD") { + + override fun start() { + scope.launch { + keyguardInteractor.wakefulnessState + .sample(keyguardTransitionInteractor.finishedKeyguardState, { a, b -> Pair(a, b) }) + .collect { pair -> + val (wakefulnessState, keyguardState) = pair + if ( + isSleepingOrStartingToSleep(wakefulnessState) && + keyguardState == KeyguardState.DREAMING + ) { + keyguardTransitionRepository.startTransition( + TransitionInfo( + name, + KeyguardState.DREAMING, + 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 = 300L + } +} diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt index 267b48d680b25..5a1c70264ee46 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt @@ -41,6 +41,11 @@ constructor( val dozeAmount: Flow = repository.dozeAmount /** Whether the system is in doze mode. */ val isDozing: Flow = repository.isDozing + /** + * Whether the system is dreaming. [isDreaming] will be always be true when [isDozing] is true, + * but not vice-versa. + */ + val isDreaming: Flow = repository.isDreaming /** Whether the keyguard is showing or not. */ val isKeyguardShowing: Flow = repository.isKeyguardShowing /** Whether the keyguard is going away. */ diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionCoreStartable.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionCoreStartable.kt index fa04d27266d77..43dd358e4808a 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionCoreStartable.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionCoreStartable.kt @@ -43,6 +43,8 @@ constructor( is LockscreenGoneTransitionInteractor -> Log.d(TAG, "Started $it") is AodToGoneTransitionInteractor -> Log.d(TAG, "Started $it") is BouncerToGoneTransitionInteractor -> Log.d(TAG, "Started $it") + is DreamingLockscreenTransitionInteractor -> Log.d(TAG, "Started $it") + is DreamingToAodTransitionInteractor -> Log.d(TAG, "Started $it") } it.start() } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/StartKeyguardTransitionModule.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/StartKeyguardTransitionModule.kt index 9683546c0ab6f..dbffeab436a4b 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/StartKeyguardTransitionModule.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/StartKeyguardTransitionModule.kt @@ -51,4 +51,14 @@ abstract class StartKeyguardTransitionModule { @Binds @IntoSet abstract fun lockscreenGone(impl: LockscreenGoneTransitionInteractor): TransitionInteractor + + @Binds + @IntoSet + abstract fun dreamingLockscreen( + impl: DreamingLockscreenTransitionInteractor + ): TransitionInteractor + + @Binds + @IntoSet + abstract fun dreamingToAod(impl: DreamingToAodTransitionInteractor): TransitionInteractor } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/KeyguardState.kt b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/KeyguardState.kt index 7958033ba0173..dd908c420fcbc 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/KeyguardState.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/KeyguardState.kt @@ -17,12 +17,29 @@ package com.android.systemui.keyguard.shared.model /** List of all possible states to transition to/from */ enum class KeyguardState { - /** - * For initialization as well as when the security method is set to NONE, indicating that - * the keyguard should never be shown. + /* + * The display is completely off, as well as any sensors that would trigger the device to wake + * up. + */ + OFF, + /** + * The device has entered a special low-power mode within SystemUI. Doze is technically a + * special dream service implementation. No UI is visible. In this state, a least some + * low-powered sensors such as lift to wake or tap to wake are enabled, or wake screen for + * notifications is enabled, allowing the device to quickly wake up. + */ + DOZING, + /* + * A device state after the device times out, which can be from both LOCKSCREEN or GONE states. + * DOZING is an example of special version of this state. Dreams may be implemented by third + * parties to present their own UI over keyguard, like a screensaver. + */ + DREAMING, + /** + * The device has entered a special low-power mode within SystemUI, also called the Always-on + * Display (AOD). A minimal UI is presented to show critical information. If the device is in + * low-power mode without a UI, then it is DOZING. */ - NONE, - /* Always-on Display. The device is in a low-power mode with a minimal UI visible */ AOD, /* * The security screen prompt UI, containing PIN, Password, Pattern, and all FPS @@ -34,7 +51,6 @@ enum class KeyguardState { * unlocked if SWIPE security method is used, or if face lockscreen bypass is false. */ LOCKSCREEN, - /* * Keyguard is no longer visible. In most cases the user has just authenticated and keyguard * is being removed, but there are other cases where the user is swiping away keyguard, such as diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/TransitionStep.kt b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/TransitionStep.kt index 732a6f7b887a5..767fd58f78e98 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/TransitionStep.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/TransitionStep.kt @@ -17,8 +17,8 @@ package com.android.systemui.keyguard.shared.model /** This information will flow from the [KeyguardTransitionRepository] to control the UI layer */ data class TransitionStep( - val from: KeyguardState = KeyguardState.NONE, - val to: KeyguardState = KeyguardState.NONE, + val from: KeyguardState = KeyguardState.OFF, + val to: KeyguardState = KeyguardState.OFF, val value: Float = 0f, // constrained [0.0, 1.0] val transitionState: TransitionState = TransitionState.FINISHED, val ownerName: String = "", diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt index 10dfeb9ebc342..6ba06344314c9 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt @@ -18,6 +18,7 @@ package com.android.systemui.keyguard.data.repository import androidx.test.filters.SmallTest import com.android.keyguard.KeyguardUpdateMonitor +import com.android.keyguard.KeyguardUpdateMonitorCallback import com.android.systemui.SysuiTestCase import com.android.systemui.common.shared.model.Position import com.android.systemui.doze.DozeHost @@ -60,12 +61,12 @@ class KeyguardRepositoryImplTest : SysuiTestCase() { underTest = KeyguardRepositoryImpl( - statusBarStateController, - dozeHost, - wakefulnessLifecycle, - biometricUnlockController, - keyguardStateController, - keyguardUpdateMonitor, + statusBarStateController, + dozeHost, + wakefulnessLifecycle, + biometricUnlockController, + keyguardStateController, + keyguardUpdateMonitor, ) } @@ -278,6 +279,26 @@ class KeyguardRepositoryImplTest : SysuiTestCase() { job.cancel() } + @Test + fun isDreaming() = runBlockingTest { + whenever(keyguardUpdateMonitor.isDreaming()).thenReturn(false) + var latest: Boolean? = null + val job = underTest.isDreaming.onEach { latest = it }.launchIn(this) + + assertThat(latest).isFalse() + + val captor = argumentCaptor() + verify(keyguardUpdateMonitor).registerCallback(captor.capture()) + + captor.value.onDreamingStateChanged(true) + assertThat(latest).isTrue() + + captor.value.onDreamingStateChanged(false) + assertThat(latest).isFalse() + + job.cancel() + } + @Test fun biometricUnlockState() = runBlockingTest { val values = mutableListOf() diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt index da215512fb3f5..a798f403c73a8 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt @@ -44,6 +44,9 @@ class FakeKeyguardRepository : KeyguardRepository { private val _isDozing = MutableStateFlow(false) override val isDozing: Flow = _isDozing + private val _isDreaming = MutableStateFlow(false) + override val isDreaming: Flow = _isDreaming + private val _dozeAmount = MutableStateFlow(0f) override val dozeAmount: Flow = _dozeAmount @@ -54,7 +57,7 @@ class FakeKeyguardRepository : KeyguardRepository { override val wakefulnessState: Flow = _wakefulnessState private val _isUdfpsSupported = MutableStateFlow(false) - + private val _isBouncerShowing = MutableStateFlow(false) override val isBouncerShowing: Flow = _isBouncerShowing