Loading packages/SystemUI/src/com/android/systemui/authentication/data/repository/AuthenticationRepository.kt +15 −11 Original line number Diff line number Diff line Loading @@ -19,17 +19,22 @@ package com.android.systemui.authentication.data.repository import com.android.internal.widget.LockPatternUtils import com.android.keyguard.KeyguardSecurityModel import com.android.systemui.authentication.shared.model.AuthenticationMethodModel import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.keyguard.data.repository.KeyguardRepository import com.android.systemui.user.data.repository.UserRepository import dagger.Binds import dagger.Module import java.util.function.Function import javax.inject.Inject import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.CoroutineScope 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 import java.util.function.Function import javax.inject.Inject /** Defines interface for classes that can access authentication-related application state. */ interface AuthenticationRepository { Loading Loading @@ -64,9 +69,6 @@ interface AuthenticationRepository { */ suspend fun getAuthenticationMethod(): AuthenticationMethodModel /** See [isUnlocked]. */ fun setUnlocked(isUnlocked: Boolean) /** See [isBypassEnabled]. */ fun setBypassEnabled(isBypassEnabled: Boolean) Loading @@ -77,14 +79,20 @@ interface AuthenticationRepository { class AuthenticationRepositoryImpl @Inject constructor( @Application private val applicationScope: CoroutineScope, private val getSecurityMode: Function<Int, KeyguardSecurityModel.SecurityMode>, @Background private val backgroundDispatcher: CoroutineDispatcher, private val userRepository: UserRepository, private val lockPatternUtils: LockPatternUtils, keyguardRepository: KeyguardRepository, ) : AuthenticationRepository { private val _isUnlocked = MutableStateFlow(false) override val isUnlocked: StateFlow<Boolean> = _isUnlocked.asStateFlow() override val isUnlocked: StateFlow<Boolean> = keyguardRepository.isKeyguardUnlocked.stateIn( scope = applicationScope, started = SharingStarted.WhileSubscribed(), initialValue = false, ) private val _isBypassEnabled = MutableStateFlow(false) override val isBypassEnabled: StateFlow<Boolean> = _isBypassEnabled.asStateFlow() Loading Loading @@ -128,10 +136,6 @@ constructor( } } override fun setUnlocked(isUnlocked: Boolean) { _isUnlocked.value = isUnlocked } override fun setBypassEnabled(isBypassEnabled: Boolean) { _isBypassEnabled.value = isBypassEnabled } Loading packages/SystemUI/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractor.kt +0 −23 Original line number Diff line number Diff line Loading @@ -89,22 +89,6 @@ constructor( return !isUnlocked.value && getAuthenticationMethod().isSecure } /** * Unlocks the device, assuming that the authentication challenge has been completed * successfully. */ fun unlockDevice() { repository.setUnlocked(true) } /** * Locks the device. From now on, the device will remain locked until [authenticate] is called * with the correct input. */ fun lockDevice() { repository.setUnlocked(false) } /** * Attempts to authenticate the user and unlock the device. * Loading Loading @@ -146,7 +130,6 @@ constructor( if (isSuccessful) { repository.setFailedAuthenticationAttempts(0) repository.setUnlocked(true) } else { repository.setFailedAuthenticationAttempts( repository.failedAuthenticationAttempts.value + 1 Loading @@ -156,12 +139,6 @@ constructor( return isSuccessful } /** Triggers a biometric-powered unlock of the device. */ fun biometricUnlock() { // TODO(b/280883900): only allow this if the biometric is enabled and there's a match. repository.setUnlocked(true) } /** See [isBypassEnabled]. */ fun toggleBypassEnabled() { repository.setBypassEnabled(!repository.isBypassEnabled.value) Loading packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractor.kt +22 −16 Original line number Diff line number Diff line Loading @@ -35,6 +35,9 @@ import kotlinx.coroutines.delay import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.flatMapLatest import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.launch Loading Loading @@ -72,22 +75,26 @@ constructor( val throttling: StateFlow<AuthenticationThrottledModel?> = repository.throttling init { // UNLOCKING SHOWS Gone. // // Move to the gone scene if the device becomes unlocked while on the bouncer scene. applicationScope.launch { sceneInteractor.currentScene(containerName).collect { currentScene -> sceneInteractor .currentScene(containerName) .flatMapLatest { currentScene -> if (currentScene.key == SceneKey.Bouncer) { when (getAuthenticationMethod()) { is AuthenticationMethodModel.None -> sceneInteractor.setCurrentScene( containerName, SceneModel(SceneKey.Gone), ) is AuthenticationMethodModel.Swipe -> authenticationInteractor.isUnlocked } else { flowOf(false) } } .distinctUntilChanged() .collect { isUnlocked -> if (isUnlocked) { sceneInteractor.setCurrentScene( containerName, SceneModel(SceneKey.Lockscreen), containerName = containerName, scene = SceneModel(SceneKey.Gone), ) else -> Unit } } } } Loading Loading @@ -119,7 +126,6 @@ constructor( scene = SceneModel(SceneKey.Bouncer), ) } else { authenticationInteractor.unlockDevice() sceneInteractor.setCurrentScene( containerName = containerName, scene = SceneModel(SceneKey.Gone), Loading packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModel.kt +36 −18 Original line number Diff line number Diff line Loading @@ -22,17 +22,19 @@ import com.android.systemui.authentication.shared.model.AuthenticationMethodMode import com.android.systemui.bouncer.domain.interactor.BouncerInteractor import com.android.systemui.bouncer.shared.model.AuthenticationThrottledModel import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.util.kotlin.pairwise import dagger.assisted.Assisted import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.channels.BufferOverflow import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.flow import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.launch Loading Loading @@ -83,26 +85,31 @@ constructor( } /** View-model for the current UI, based on the current authentication method. */ val authMethod: StateFlow<AuthMethodBouncerViewModel?> get() = flow { emit(null) emit(interactor.getAuthenticationMethod()) } .map { authMethod -> when (authMethod) { is AuthenticationMethodModel.Pin -> pin is AuthenticationMethodModel.Password -> password is AuthenticationMethodModel.Pattern -> pattern else -> null } } .stateIn( private val _authMethod = MutableSharedFlow<AuthMethodBouncerViewModel?>( replay = 1, onBufferOverflow = BufferOverflow.DROP_OLDEST, ) val authMethod: StateFlow<AuthMethodBouncerViewModel?> = _authMethod.stateIn( scope = applicationScope, started = SharingStarted.WhileSubscribed(), initialValue = null, ) init { applicationScope.launch { _authMethod.subscriptionCount .pairwise() .map { (previousCount, currentCount) -> currentCount > previousCount } .collect { subscriberAdded -> if (subscriberAdded) { reloadAuthMethod() } } } } /** The user-facing message to show in the bouncer. */ val message: StateFlow<MessageViewModel> = combine( Loading Loading @@ -184,6 +191,17 @@ constructor( ) } private suspend fun reloadAuthMethod() { _authMethod.tryEmit( when (interactor.getAuthenticationMethod()) { is AuthenticationMethodModel.Pin -> pin is AuthenticationMethodModel.Password -> password is AuthenticationMethodModel.Pattern -> pattern else -> null } ) } data class MessageViewModel( val text: String, Loading packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModel.kt +1 −4 Original line number Diff line number Diff line Loading @@ -42,10 +42,7 @@ class PinBouncerViewModel( /** The length of the hinted PIN, or `null` if pin length hint should not be shown. */ val hintedPinLength: StateFlow<Int?> = flow { emit(null) emit(interactor.getAuthenticationMethod()) } flow { emit(interactor.getAuthenticationMethod()) } .map { authMethod -> // Hinting is enabled for 6-digit codes only autoConfirmPinLength(authMethod).takeIf { it == HINTING_PASSCODE_LENGTH } Loading Loading
packages/SystemUI/src/com/android/systemui/authentication/data/repository/AuthenticationRepository.kt +15 −11 Original line number Diff line number Diff line Loading @@ -19,17 +19,22 @@ package com.android.systemui.authentication.data.repository import com.android.internal.widget.LockPatternUtils import com.android.keyguard.KeyguardSecurityModel import com.android.systemui.authentication.shared.model.AuthenticationMethodModel import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.keyguard.data.repository.KeyguardRepository import com.android.systemui.user.data.repository.UserRepository import dagger.Binds import dagger.Module import java.util.function.Function import javax.inject.Inject import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.CoroutineScope 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 import java.util.function.Function import javax.inject.Inject /** Defines interface for classes that can access authentication-related application state. */ interface AuthenticationRepository { Loading Loading @@ -64,9 +69,6 @@ interface AuthenticationRepository { */ suspend fun getAuthenticationMethod(): AuthenticationMethodModel /** See [isUnlocked]. */ fun setUnlocked(isUnlocked: Boolean) /** See [isBypassEnabled]. */ fun setBypassEnabled(isBypassEnabled: Boolean) Loading @@ -77,14 +79,20 @@ interface AuthenticationRepository { class AuthenticationRepositoryImpl @Inject constructor( @Application private val applicationScope: CoroutineScope, private val getSecurityMode: Function<Int, KeyguardSecurityModel.SecurityMode>, @Background private val backgroundDispatcher: CoroutineDispatcher, private val userRepository: UserRepository, private val lockPatternUtils: LockPatternUtils, keyguardRepository: KeyguardRepository, ) : AuthenticationRepository { private val _isUnlocked = MutableStateFlow(false) override val isUnlocked: StateFlow<Boolean> = _isUnlocked.asStateFlow() override val isUnlocked: StateFlow<Boolean> = keyguardRepository.isKeyguardUnlocked.stateIn( scope = applicationScope, started = SharingStarted.WhileSubscribed(), initialValue = false, ) private val _isBypassEnabled = MutableStateFlow(false) override val isBypassEnabled: StateFlow<Boolean> = _isBypassEnabled.asStateFlow() Loading Loading @@ -128,10 +136,6 @@ constructor( } } override fun setUnlocked(isUnlocked: Boolean) { _isUnlocked.value = isUnlocked } override fun setBypassEnabled(isBypassEnabled: Boolean) { _isBypassEnabled.value = isBypassEnabled } Loading
packages/SystemUI/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractor.kt +0 −23 Original line number Diff line number Diff line Loading @@ -89,22 +89,6 @@ constructor( return !isUnlocked.value && getAuthenticationMethod().isSecure } /** * Unlocks the device, assuming that the authentication challenge has been completed * successfully. */ fun unlockDevice() { repository.setUnlocked(true) } /** * Locks the device. From now on, the device will remain locked until [authenticate] is called * with the correct input. */ fun lockDevice() { repository.setUnlocked(false) } /** * Attempts to authenticate the user and unlock the device. * Loading Loading @@ -146,7 +130,6 @@ constructor( if (isSuccessful) { repository.setFailedAuthenticationAttempts(0) repository.setUnlocked(true) } else { repository.setFailedAuthenticationAttempts( repository.failedAuthenticationAttempts.value + 1 Loading @@ -156,12 +139,6 @@ constructor( return isSuccessful } /** Triggers a biometric-powered unlock of the device. */ fun biometricUnlock() { // TODO(b/280883900): only allow this if the biometric is enabled and there's a match. repository.setUnlocked(true) } /** See [isBypassEnabled]. */ fun toggleBypassEnabled() { repository.setBypassEnabled(!repository.isBypassEnabled.value) Loading
packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractor.kt +22 −16 Original line number Diff line number Diff line Loading @@ -35,6 +35,9 @@ import kotlinx.coroutines.delay import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.flatMapLatest import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.launch Loading Loading @@ -72,22 +75,26 @@ constructor( val throttling: StateFlow<AuthenticationThrottledModel?> = repository.throttling init { // UNLOCKING SHOWS Gone. // // Move to the gone scene if the device becomes unlocked while on the bouncer scene. applicationScope.launch { sceneInteractor.currentScene(containerName).collect { currentScene -> sceneInteractor .currentScene(containerName) .flatMapLatest { currentScene -> if (currentScene.key == SceneKey.Bouncer) { when (getAuthenticationMethod()) { is AuthenticationMethodModel.None -> sceneInteractor.setCurrentScene( containerName, SceneModel(SceneKey.Gone), ) is AuthenticationMethodModel.Swipe -> authenticationInteractor.isUnlocked } else { flowOf(false) } } .distinctUntilChanged() .collect { isUnlocked -> if (isUnlocked) { sceneInteractor.setCurrentScene( containerName, SceneModel(SceneKey.Lockscreen), containerName = containerName, scene = SceneModel(SceneKey.Gone), ) else -> Unit } } } } Loading Loading @@ -119,7 +126,6 @@ constructor( scene = SceneModel(SceneKey.Bouncer), ) } else { authenticationInteractor.unlockDevice() sceneInteractor.setCurrentScene( containerName = containerName, scene = SceneModel(SceneKey.Gone), Loading
packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModel.kt +36 −18 Original line number Diff line number Diff line Loading @@ -22,17 +22,19 @@ import com.android.systemui.authentication.shared.model.AuthenticationMethodMode import com.android.systemui.bouncer.domain.interactor.BouncerInteractor import com.android.systemui.bouncer.shared.model.AuthenticationThrottledModel import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.util.kotlin.pairwise import dagger.assisted.Assisted import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.channels.BufferOverflow import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.flow import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.launch Loading Loading @@ -83,26 +85,31 @@ constructor( } /** View-model for the current UI, based on the current authentication method. */ val authMethod: StateFlow<AuthMethodBouncerViewModel?> get() = flow { emit(null) emit(interactor.getAuthenticationMethod()) } .map { authMethod -> when (authMethod) { is AuthenticationMethodModel.Pin -> pin is AuthenticationMethodModel.Password -> password is AuthenticationMethodModel.Pattern -> pattern else -> null } } .stateIn( private val _authMethod = MutableSharedFlow<AuthMethodBouncerViewModel?>( replay = 1, onBufferOverflow = BufferOverflow.DROP_OLDEST, ) val authMethod: StateFlow<AuthMethodBouncerViewModel?> = _authMethod.stateIn( scope = applicationScope, started = SharingStarted.WhileSubscribed(), initialValue = null, ) init { applicationScope.launch { _authMethod.subscriptionCount .pairwise() .map { (previousCount, currentCount) -> currentCount > previousCount } .collect { subscriberAdded -> if (subscriberAdded) { reloadAuthMethod() } } } } /** The user-facing message to show in the bouncer. */ val message: StateFlow<MessageViewModel> = combine( Loading Loading @@ -184,6 +191,17 @@ constructor( ) } private suspend fun reloadAuthMethod() { _authMethod.tryEmit( when (interactor.getAuthenticationMethod()) { is AuthenticationMethodModel.Pin -> pin is AuthenticationMethodModel.Password -> password is AuthenticationMethodModel.Pattern -> pattern else -> null } ) } data class MessageViewModel( val text: String, Loading
packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModel.kt +1 −4 Original line number Diff line number Diff line Loading @@ -42,10 +42,7 @@ class PinBouncerViewModel( /** The length of the hinted PIN, or `null` if pin length hint should not be shown. */ val hintedPinLength: StateFlow<Int?> = flow { emit(null) emit(interactor.getAuthenticationMethod()) } flow { emit(interactor.getAuthenticationMethod()) } .map { authMethod -> // Hinting is enabled for 6-digit codes only autoConfirmPinLength(authMethod).takeIf { it == HINTING_PASSCODE_LENGTH } Loading