summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author Alejandro Nijamkin <nijamkin@google.com> 2024-07-16 15:35:45 -0700
committer Alejandro Nijamkin <nijamkin@google.com> 2024-07-16 16:26:20 -0700
commit1c80a98dcdeb212927e4c44c31ef4f13e1f18194 (patch)
treefb5763ccc524850347e9a27e320a780c9bff282e
parentd2715b13c06ec5fb6760537556b12da38bc46092 (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
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt57
-rw-r--r--packages/SystemUI/src/com/android/systemui/deviceentry/data/repository/DeviceEntryRepository.kt28
-rw-r--r--packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractor.kt25
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractor.kt6
-rw-r--r--packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt19
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/data/repository/FakeDeviceEntryRepository.kt17
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractorKosmos.kt4
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
)