diff options
| author | 2024-07-16 15:35:45 -0700 | |
|---|---|---|
| committer | 2024-07-16 16:26:20 -0700 | |
| commit | 1c80a98dcdeb212927e4c44c31ef4f13e1f18194 (patch) | |
| tree | fb5763ccc524850347e9a27e320a780c9bff282e | |
| parent | d2715b13c06ec5fb6760537556b12da38bc46092 (diff) | |
[flexiglass] Pick up on non-secure auth method changes.
This CL adds logic that detects and responds to authentication method
changes between None and Swipe.
These changes occur in the Settings app, outside system UI. Unlike
changes between the different secure auth methods (PIN, password,
pattern) or between a secure auth method and a non-secure auth method
(none or swipe) and vice-versa, a switch from none to swipe or from
swipe to none has no callback mechanism nor does it produce any sort of
broadcast that system UI can depend on.
The CL takes the approach of refreshing the value whenever the scene is
starting to transition to the lockscreen scene. A flow was added to
DeviceEntryInteractor/Repository that's hydrated with a value each time
isLockscreenEnabled is invoked. Logic was added to the
SceneContainerStartable to trigger the isLockscreenEnabled method each
time a transition to the lockscreen scene is started.
Fix: 353323330
Test: added integration test at the SceneContainerStartableTest level
Test: manually verified that switching between none and swipe results in
the proper behaviour on the lockscreen
Test: manually verified switching from a secure auth method into one of
the two non-secure ones and back, also making sure it results with
proper behaviour on the lockscreen
Flag: com.android.systemui.scene_container
Change-Id: I4533345ea35511dee3ec2cc833f0b2856bd42bcd
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 ) |