Loading packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt +57 −0 Original line number Diff line number Diff line Loading @@ -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, Loading packages/SystemUI/src/com/android/systemui/deviceentry/data/repository/DeviceEntryRepository.kt +21 −7 Original line number Diff line number Diff line Loading @@ -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 Loading @@ -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 Loading @@ -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. */ Loading @@ -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 { Loading @@ -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 Loading packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractor.kt +21 −4 Original line number Diff line number Diff line Loading @@ -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 Loading Loading @@ -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 Loading @@ -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)) && Loading Loading @@ -185,6 +191,17 @@ constructor( return repository.isLockscreenEnabled() } /** * 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 Loading packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractor.kt +3 −3 Original line number Diff line number Diff line Loading @@ -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 Loading Loading @@ -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, ) : Loading Loading @@ -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 { Loading packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt +19 −0 Original line number Diff line number Diff line Loading @@ -149,6 +149,7 @@ constructor( resetShadeSessions() handleKeyguardEnabledness() notifyKeyguardDismissCallbacks() refreshLockscreenEnabled() } else { sceneLogger.logFrameworkEnabled( isEnabled = false, Loading Loading @@ -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() } } } } Loading
packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt +57 −0 Original line number Diff line number Diff line Loading @@ -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, Loading
packages/SystemUI/src/com/android/systemui/deviceentry/data/repository/DeviceEntryRepository.kt +21 −7 Original line number Diff line number Diff line Loading @@ -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 Loading @@ -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 Loading @@ -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. */ Loading @@ -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 { Loading @@ -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 Loading
packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractor.kt +21 −4 Original line number Diff line number Diff line Loading @@ -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 Loading Loading @@ -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 Loading @@ -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)) && Loading Loading @@ -185,6 +191,17 @@ constructor( return repository.isLockscreenEnabled() } /** * 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 Loading
packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractor.kt +3 −3 Original line number Diff line number Diff line Loading @@ -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 Loading Loading @@ -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, ) : Loading Loading @@ -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 { Loading
packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt +19 −0 Original line number Diff line number Diff line Loading @@ -149,6 +149,7 @@ constructor( resetShadeSessions() handleKeyguardEnabledness() notifyKeyguardDismissCallbacks() refreshLockscreenEnabled() } else { sceneLogger.logFrameworkEnabled( isEnabled = false, Loading Loading @@ -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() } } } }