diff options
7 files changed, 137 insertions, 19 deletions
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt index fd1b21332973..a120bdc0b743 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt @@ -1559,6 +1559,63 @@ class SceneContainerStartableTest : SysuiTestCase() { verify(dismissCallback).onDismissCancelled() } + @Test + fun refreshLockscreenEnabled() = + testScope.runTest { + val transitionState = + prepareState( + isDeviceUnlocked = true, + initialSceneKey = Scenes.Gone, + ) + underTest.start() + val isLockscreenEnabled by + collectLastValue(kosmos.deviceEntryInteractor.isLockscreenEnabled) + assertThat(isLockscreenEnabled).isTrue() + + kosmos.fakeDeviceEntryRepository.setPendingLockscreenEnabled(false) + runCurrent() + // Pending value didn't propagate yet. + assertThat(isLockscreenEnabled).isTrue() + + // Starting a transition to Lockscreen should refresh the value, causing the pending + // value + // to propagate to the real flow: + transitionState.value = + ObservableTransitionState.Transition( + fromScene = Scenes.Gone, + toScene = Scenes.Lockscreen, + currentScene = flowOf(Scenes.Gone), + progress = flowOf(0.1f), + isInitiatedByUserInput = false, + isUserInputOngoing = flowOf(false), + ) + runCurrent() + assertThat(isLockscreenEnabled).isFalse() + + kosmos.fakeDeviceEntryRepository.setPendingLockscreenEnabled(true) + runCurrent() + // Pending value didn't propagate yet. + assertThat(isLockscreenEnabled).isFalse() + transitionState.value = ObservableTransitionState.Idle(Scenes.Gone) + runCurrent() + assertThat(isLockscreenEnabled).isFalse() + + // Starting another transition to Lockscreen should refresh the value, causing the + // pending + // value to propagate to the real flow: + transitionState.value = + ObservableTransitionState.Transition( + fromScene = Scenes.Gone, + toScene = Scenes.Lockscreen, + currentScene = flowOf(Scenes.Gone), + progress = flowOf(0.1f), + isInitiatedByUserInput = false, + isUserInputOngoing = flowOf(false), + ) + runCurrent() + assertThat(isLockscreenEnabled).isTrue() + } + private fun TestScope.emulateSceneTransition( transitionStateFlow: MutableStateFlow<ObservableTransitionState>, toScene: SceneKey, diff --git a/packages/SystemUI/src/com/android/systemui/deviceentry/data/repository/DeviceEntryRepository.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/data/repository/DeviceEntryRepository.kt index e2ad7741557f..3f937bba46d4 100644 --- a/packages/SystemUI/src/com/android/systemui/deviceentry/data/repository/DeviceEntryRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/deviceentry/data/repository/DeviceEntryRepository.kt @@ -13,8 +13,10 @@ import javax.inject.Inject import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.channels.awaitClose +import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.withContext @@ -25,7 +27,7 @@ interface DeviceEntryRepository { * chosen any secure authentication method and even if they set the lockscreen to be dismissed * when the user swipes on it. */ - suspend fun isLockscreenEnabled(): Boolean + val isLockscreenEnabled: StateFlow<Boolean> /** * Whether lockscreen bypass is enabled. When enabled, the lockscreen will be automatically @@ -39,6 +41,13 @@ interface DeviceEntryRepository { * the lockscreen. */ val isBypassEnabled: StateFlow<Boolean> + + /** + * Whether the lockscreen is enabled for the current user. This is `true` whenever the user has + * chosen any secure authentication method and even if they set the lockscreen to be dismissed + * when the user swipes on it. + */ + suspend fun isLockscreenEnabled(): Boolean } /** Encapsulates application state for device entry. */ @@ -53,12 +62,8 @@ constructor( private val keyguardBypassController: KeyguardBypassController, ) : DeviceEntryRepository { - override suspend fun isLockscreenEnabled(): Boolean { - return withContext(backgroundDispatcher) { - val selectedUserId = userRepository.getSelectedUserInfo().id - !lockPatternUtils.isLockScreenDisabled(selectedUserId) - } - } + private val _isLockscreenEnabled = MutableStateFlow(true) + override val isLockscreenEnabled: StateFlow<Boolean> = _isLockscreenEnabled.asStateFlow() override val isBypassEnabled: StateFlow<Boolean> = conflatedCallbackFlow { @@ -78,6 +83,15 @@ constructor( SharingStarted.Eagerly, initialValue = keyguardBypassController.bypassEnabled, ) + + override suspend fun isLockscreenEnabled(): Boolean { + return withContext(backgroundDispatcher) { + val selectedUserId = userRepository.getSelectedUserInfo().id + val isEnabled = !lockPatternUtils.isLockScreenDisabled(selectedUserId) + _isLockscreenEnabled.value = isEnabled + isEnabled + } + } } @Module diff --git a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractor.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractor.kt index ea0e59bb6ccc..9b95ac4797c0 100644 --- a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractor.kt @@ -28,12 +28,14 @@ import com.android.systemui.utils.coroutines.flow.mapLatestConflated import javax.inject.Inject import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.filter import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.onStart import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.launch @@ -101,6 +103,10 @@ constructor( initialValue = false, ) + val isLockscreenEnabled: Flow<Boolean> by lazy { + repository.isLockscreenEnabled.onStart { refreshLockscreenEnabled() } + } + /** * Whether it's currently possible to swipe up to enter the device without requiring * authentication or when the device is already authenticated using a passive authentication @@ -115,14 +121,14 @@ constructor( */ val canSwipeToEnter: StateFlow<Boolean?> = combine( - // This is true when the user has chosen to show the lockscreen but has not made it - // secure. authenticationInteractor.authenticationMethod.map { - it == AuthenticationMethodModel.None && repository.isLockscreenEnabled() + it == AuthenticationMethodModel.None }, + isLockscreenEnabled, deviceUnlockedInteractor.deviceUnlockStatus, isDeviceEntered - ) { isSwipeAuthMethod, deviceUnlockStatus, isDeviceEntered -> + ) { isNoneAuthMethod, isLockscreenEnabled, deviceUnlockStatus, isDeviceEntered -> + val isSwipeAuthMethod = isNoneAuthMethod && isLockscreenEnabled (isSwipeAuthMethod || (deviceUnlockStatus.isUnlocked && deviceUnlockStatus.deviceUnlockSource?.dismissesLockscreen == false)) && @@ -186,6 +192,17 @@ constructor( } /** + * Forces a refresh of the value of [isLockscreenEnabled] such that the flow emits the latest + * value. + * + * Without calling this method, the flow will have a stale value unless the collector is removed + * and re-added. + */ + suspend fun refreshLockscreenEnabled() { + isLockscreenEnabled() + } + + /** * Whether lockscreen bypass is enabled. When enabled, the lockscreen will be automatically * dismissed once the authentication challenge is completed. For example, completing a biometric * authentication challenge via face unlock or fingerprint sensor can automatically bypass the diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractor.kt index cd28bec938b8..8f50b03eafec 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractor.kt @@ -25,7 +25,7 @@ import com.android.systemui.communal.domain.interactor.CommunalSceneInteractor import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.dagger.qualifiers.Main -import com.android.systemui.deviceentry.data.repository.DeviceEntryRepository +import com.android.systemui.deviceentry.domain.interactor.DeviceEntryInteractor import com.android.systemui.keyguard.KeyguardWmStateRefactor import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository import com.android.systemui.keyguard.shared.model.BiometricUnlockMode.Companion.isWakeAndUnlock @@ -59,7 +59,7 @@ constructor( private val communalInteractor: CommunalInteractor, private val communalSceneInteractor: CommunalSceneInteractor, keyguardOcclusionInteractor: KeyguardOcclusionInteractor, - val deviceEntryRepository: DeviceEntryRepository, + val deviceEntryInteractor: DeviceEntryInteractor, private val wakeToGoneInteractor: KeyguardWakeDirectlyToGoneInteractor, private val dreamManager: DreamManager, ) : @@ -146,7 +146,7 @@ constructor( isIdleOnCommunal, canTransitionToGoneOnWake, primaryBouncerShowing) -> - if (!deviceEntryRepository.isLockscreenEnabled()) { + if (!deviceEntryInteractor.isLockscreenEnabled()) { if (SceneContainerFlag.isEnabled) { // TODO(b/336576536): Check if adaptation for scene framework is needed } else { diff --git a/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt b/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt index 8711e8878525..51447cc6f373 100644 --- a/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt +++ b/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt @@ -149,6 +149,7 @@ constructor( resetShadeSessions() handleKeyguardEnabledness() notifyKeyguardDismissCallbacks() + refreshLockscreenEnabled() } else { sceneLogger.logFrameworkEnabled( isEnabled = false, @@ -735,4 +736,22 @@ constructor( } } } + + /** + * Keeps the value of [DeviceEntryInteractor.isLockscreenEnabled] fresh. + * + * This is needed because that value is sourced from a non-observable data source + * (`LockPatternUtils`, which doesn't expose a listener or callback for this value). Therefore, + * every time a transition to the `Lockscreen` scene is started, the value is re-fetched and + * cached. + */ + private fun refreshLockscreenEnabled() { + applicationScope.launch { + sceneInteractor.transitionState + .map { it.isTransitioning(to = Scenes.Lockscreen) } + .distinctUntilChanged() + .filter { it } + .collectLatest { deviceEntryInteractor.refreshLockscreenEnabled() } + } + } } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/data/repository/FakeDeviceEntryRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/data/repository/FakeDeviceEntryRepository.kt index 045bd5d286df..2dcd275f0103 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/data/repository/FakeDeviceEntryRepository.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/data/repository/FakeDeviceEntryRepository.kt @@ -21,21 +21,32 @@ import dagger.Module import javax.inject.Inject import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.asStateFlow /** Fake implementation of [DeviceEntryRepository] */ @SysUISingleton class FakeDeviceEntryRepository @Inject constructor() : DeviceEntryRepository { - private var isLockscreenEnabled = true + + private val _isLockscreenEnabled = MutableStateFlow(true) + override val isLockscreenEnabled: StateFlow<Boolean> = _isLockscreenEnabled.asStateFlow() private val _isBypassEnabled = MutableStateFlow(false) override val isBypassEnabled: StateFlow<Boolean> = _isBypassEnabled + private var pendingLockscreenEnabled = _isLockscreenEnabled.value + override suspend fun isLockscreenEnabled(): Boolean { - return isLockscreenEnabled + _isLockscreenEnabled.value = pendingLockscreenEnabled + return isLockscreenEnabled.value } fun setLockscreenEnabled(isLockscreenEnabled: Boolean) { - this.isLockscreenEnabled = isLockscreenEnabled + _isLockscreenEnabled.value = isLockscreenEnabled + pendingLockscreenEnabled = _isLockscreenEnabled.value + } + + fun setPendingLockscreenEnabled(isLockscreenEnabled: Boolean) { + pendingLockscreenEnabled = isLockscreenEnabled } fun setBypassEnabled(isBypassEnabled: Boolean) { diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractorKosmos.kt index 126d85890531..4634a7fd009f 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractorKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractorKosmos.kt @@ -19,7 +19,7 @@ package com.android.systemui.keyguard.domain.interactor import android.service.dream.dreamManager import com.android.systemui.communal.domain.interactor.communalInteractor import com.android.systemui.communal.domain.interactor.communalSceneInteractor -import com.android.systemui.deviceentry.data.repository.deviceEntryRepository +import com.android.systemui.deviceentry.domain.interactor.deviceEntryInteractor import com.android.systemui.keyguard.data.repository.keyguardTransitionRepository import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.applicationCoroutineScope @@ -41,7 +41,7 @@ var Kosmos.fromDozingTransitionInteractor by communalSceneInteractor = communalSceneInteractor, powerInteractor = powerInteractor, keyguardOcclusionInteractor = keyguardOcclusionInteractor, - deviceEntryRepository = deviceEntryRepository, + deviceEntryInteractor = deviceEntryInteractor, wakeToGoneInteractor = keyguardWakeDirectlyToGoneInteractor, dreamManager = dreamManager ) |