Merge "[flexiglass] Pick up on non-secure auth method changes." into main
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 fd1b213..a120bdc 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 @@
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 e2ad774..3f937bb 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 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 @@
* 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 @@
* 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 @@
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 @@
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 ea0e59b..9b95ac4 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 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 @@
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 @@
*/
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 @@
}
/**
+ * 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 cd28bec..8f50b03 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.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 @@
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 @@
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 8711e88..51447cc 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 @@
resetShadeSessions()
handleKeyguardEnabledness()
notifyKeyguardDismissCallbacks()
+ refreshLockscreenEnabled()
} else {
sceneLogger.logFrameworkEnabled(
isEnabled = false,
@@ -735,4 +736,22 @@
}
}
}
+
+ /**
+ * 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 045bd5d..2dcd275 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 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 126d858..4634a7f 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 @@
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 @@
communalSceneInteractor = communalSceneInteractor,
powerInteractor = powerInteractor,
keyguardOcclusionInteractor = keyguardOcclusionInteractor,
- deviceEntryRepository = deviceEntryRepository,
+ deviceEntryInteractor = deviceEntryInteractor,
wakeToGoneInteractor = keyguardWakeDirectlyToGoneInteractor,
dreamManager = dreamManager
)