Loading packages/SystemUI/src/com/android/systemui/authentication/AuthenticationModule.kt +12 −1 Original line number Diff line number Diff line Loading @@ -16,8 +16,11 @@ package com.android.systemui.authentication import com.android.keyguard.KeyguardSecurityModel import com.android.systemui.authentication.data.repository.AuthenticationRepositoryModule import dagger.Module import dagger.Provides import java.util.function.Function @Module( includes = Loading @@ -25,4 +28,12 @@ import dagger.Module AuthenticationRepositoryModule::class, ], ) object AuthenticationModule object AuthenticationModule { @Provides fun getSecurityMode( model: KeyguardSecurityModel, ): Function<Int, KeyguardSecurityModel.SecurityMode> { return Function { userId -> model.getSecurityMode(userId) } } } packages/SystemUI/src/com/android/systemui/authentication/data/repository/AuthenticationRepository.kt +57 −23 Original line number Diff line number Diff line Loading @@ -16,13 +16,20 @@ 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.Background import com.android.systemui.user.data.repository.UserRepository import dagger.Binds import dagger.Module import javax.inject.Inject import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.asStateFlow 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 @@ -37,12 +44,6 @@ interface AuthenticationRepository { */ val isUnlocked: StateFlow<Boolean> /** * The currently-configured authentication method. This determines how the authentication * challenge is completed in order to unlock an otherwise locked device. */ val authenticationMethod: StateFlow<AuthenticationMethodModel> /** * Whether lock screen bypass is enabled. When enabled, the lock screen will be automatically * dismisses once the authentication challenge is completed. For example, completing a biometric Loading @@ -57,12 +58,15 @@ interface AuthenticationRepository { */ val failedAuthenticationAttempts: StateFlow<Int> /** * Returns the currently-configured authentication method. This determines how the * authentication challenge is completed in order to unlock an otherwise locked device. */ suspend fun getAuthenticationMethod(): AuthenticationMethodModel /** See [isUnlocked]. */ fun setUnlocked(isUnlocked: Boolean) /** See [authenticationMethod]. */ fun setAuthenticationMethod(authenticationMethod: AuthenticationMethodModel) /** See [isBypassEnabled]. */ fun setBypassEnabled(isBypassEnabled: Boolean) Loading @@ -70,19 +74,18 @@ interface AuthenticationRepository { fun setFailedAuthenticationAttempts(failedAuthenticationAttempts: Int) } class AuthenticationRepositoryImpl @Inject constructor() : AuthenticationRepository { // TODO(b/280883900): get data from real data sources in SysUI. class AuthenticationRepositoryImpl @Inject constructor( private val getSecurityMode: Function<Int, KeyguardSecurityModel.SecurityMode>, @Background private val backgroundDispatcher: CoroutineDispatcher, private val userRepository: UserRepository, private val lockPatternUtils: LockPatternUtils, ) : AuthenticationRepository { private val _isUnlocked = MutableStateFlow(false) override val isUnlocked: StateFlow<Boolean> = _isUnlocked.asStateFlow() private val _authenticationMethod = MutableStateFlow<AuthenticationMethodModel>( AuthenticationMethodModel.Pin(listOf(1, 2, 3, 4), autoConfirm = false) ) override val authenticationMethod: StateFlow<AuthenticationMethodModel> = _authenticationMethod.asStateFlow() private val _isBypassEnabled = MutableStateFlow(false) override val isBypassEnabled: StateFlow<Boolean> = _isBypassEnabled.asStateFlow() Loading @@ -90,6 +93,41 @@ class AuthenticationRepositoryImpl @Inject constructor() : AuthenticationReposit override val failedAuthenticationAttempts: StateFlow<Int> = _failedAuthenticationAttempts.asStateFlow() override suspend fun getAuthenticationMethod(): AuthenticationMethodModel { return withContext(backgroundDispatcher) { val selectedUserId = userRepository.getSelectedUserInfo().id when (getSecurityMode.apply(selectedUserId)) { KeyguardSecurityModel.SecurityMode.PIN, KeyguardSecurityModel.SecurityMode.SimPin -> AuthenticationMethodModel.Pin( code = listOf(1, 2, 3, 4), // TODO(b/280883900): remove this autoConfirm = lockPatternUtils.isAutoPinConfirmEnabled(selectedUserId), ) KeyguardSecurityModel.SecurityMode.Password, KeyguardSecurityModel.SecurityMode.SimPuk -> AuthenticationMethodModel.Password( password = "password", // TODO(b/280883900): remove this ) KeyguardSecurityModel.SecurityMode.Pattern -> AuthenticationMethodModel.Pattern( coordinates = listOf( AuthenticationMethodModel.Pattern.PatternCoordinate(2, 0), AuthenticationMethodModel.Pattern.PatternCoordinate(2, 1), AuthenticationMethodModel.Pattern.PatternCoordinate(2, 2), AuthenticationMethodModel.Pattern.PatternCoordinate(1, 1), AuthenticationMethodModel.Pattern.PatternCoordinate(0, 0), AuthenticationMethodModel.Pattern.PatternCoordinate(0, 1), AuthenticationMethodModel.Pattern.PatternCoordinate(0, 2), ), // TODO(b/280883900): remove this ) KeyguardSecurityModel.SecurityMode.None -> AuthenticationMethodModel.None KeyguardSecurityModel.SecurityMode.Invalid -> error("Invalid security mode!") null -> error("Invalid security is null!") } } } override fun setUnlocked(isUnlocked: Boolean) { _isUnlocked.value = isUnlocked } Loading @@ -98,10 +136,6 @@ class AuthenticationRepositoryImpl @Inject constructor() : AuthenticationReposit _isBypassEnabled.value = isBypassEnabled } override fun setAuthenticationMethod(authenticationMethod: AuthenticationMethodModel) { _authenticationMethod.value = authenticationMethod } override fun setFailedAuthenticationAttempts(failedAuthenticationAttempts: Int) { _failedAuthenticationAttempts.value = failedAuthenticationAttempts } Loading packages/SystemUI/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractor.kt +19 −49 Original line number Diff line number Diff line Loading @@ -25,9 +25,8 @@ import javax.inject.Inject import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.launch /** Hosts application business logic related to authentication. */ @SysUISingleton Loading @@ -37,12 +36,6 @@ constructor( @Application applicationScope: CoroutineScope, private val repository: AuthenticationRepository, ) { /** * The currently-configured authentication method. This determines how the authentication * challenge is completed in order to unlock an otherwise locked device. */ val authenticationMethod: StateFlow<AuthenticationMethodModel> = repository.authenticationMethod /** * Whether the device is unlocked. * Loading @@ -52,20 +45,18 @@ constructor( * Note that this state has no real bearing on whether the lock screen is showing or dismissed. */ val isUnlocked: StateFlow<Boolean> = combine(authenticationMethod, repository.isUnlocked) { authMethod, isUnlocked -> isUnlockedWithAuthMethod( isUnlocked = isUnlocked, authMethod = authMethod, ) repository.isUnlocked .map { isUnlocked -> if (getAuthenticationMethod() is AuthenticationMethodModel.None) { true } else { isUnlocked } } .stateIn( scope = applicationScope, started = SharingStarted.Eagerly, initialValue = isUnlockedWithAuthMethod( isUnlocked = repository.isUnlocked.value, authMethod = repository.authenticationMethod.value, ) initialValue = true, ) /** Loading @@ -82,25 +73,20 @@ constructor( */ val failedAuthenticationAttempts: StateFlow<Int> = repository.failedAuthenticationAttempts init { // UNLOCKS WHEN AUTH METHOD REMOVED. // // Unlocks the device if the auth method becomes None. applicationScope.launch { repository.authenticationMethod.collect { if (it is AuthenticationMethodModel.None) { unlockDevice() } } } /** * Returns the currently-configured authentication method. This determines how the * authentication challenge is completed in order to unlock an otherwise locked device. */ suspend fun getAuthenticationMethod(): AuthenticationMethodModel { return repository.getAuthenticationMethod() } /** * Returns `true` if the device currently requires authentication before content can be viewed; * `false` if content can be displayed without unlocking first. */ fun isAuthenticationRequired(): Boolean { return !isUnlocked.value && authenticationMethod.value.isSecure suspend fun isAuthenticationRequired(): Boolean { return !isUnlocked.value && getAuthenticationMethod().isSecure } /** Loading Loading @@ -133,8 +119,8 @@ constructor( * @return `true` if the authentication succeeded and the device is now unlocked; `false` when * authentication failed, `null` if the check was not performed. */ fun authenticate(input: List<Any>, tryAutoConfirm: Boolean = false): Boolean? { val authMethod = this.authenticationMethod.value suspend fun authenticate(input: List<Any>, tryAutoConfirm: Boolean = false): Boolean? { val authMethod = getAuthenticationMethod() if (tryAutoConfirm) { if ((authMethod as? AuthenticationMethodModel.Pin)?.autoConfirm != true) { // Do not attempt to authenticate unless the PIN lock is set to auto-confirm. Loading Loading @@ -176,28 +162,12 @@ constructor( repository.setUnlocked(true) } /** See [authenticationMethod]. */ fun setAuthenticationMethod(authenticationMethod: AuthenticationMethodModel) { repository.setAuthenticationMethod(authenticationMethod) } /** See [isBypassEnabled]. */ fun toggleBypassEnabled() { repository.setBypassEnabled(!repository.isBypassEnabled.value) } companion object { private fun isUnlockedWithAuthMethod( isUnlocked: Boolean, authMethod: AuthenticationMethodModel, ): Boolean { return if (authMethod is AuthenticationMethodModel.None) { true } else { isUnlocked } } /** * Returns a PIN code from the given list. It's assumed the given list elements are all * [Int] in the range [0-9]. Loading packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractor.kt +40 −42 Original line number Diff line number Diff line Loading @@ -68,26 +68,14 @@ constructor( ) ) /** * The currently-configured authentication method. This determines how the authentication * challenge is completed in order to unlock an otherwise locked device. */ val authenticationMethod: StateFlow<AuthenticationMethodModel> = authenticationInteractor.authenticationMethod /** The current authentication throttling state. If `null`, there's no throttling. */ val throttling: StateFlow<AuthenticationThrottledModel?> = repository.throttling init { applicationScope.launch { combine( sceneInteractor.currentScene(containerName), authenticationInteractor.authenticationMethod, ::Pair, ) .collect { (currentScene, authMethod) -> sceneInteractor.currentScene(containerName).collect { currentScene -> if (currentScene.key == SceneKey.Bouncer) { when (authMethod) { when (getAuthenticationMethod()) { is AuthenticationMethodModel.None -> sceneInteractor.setCurrentScene( containerName, Loading @@ -105,6 +93,14 @@ constructor( } } /** * Returns the currently-configured authentication method. This determines how the * authentication challenge is completed in order to unlock an otherwise locked device. */ suspend fun getAuthenticationMethod(): AuthenticationMethodModel { return authenticationInteractor.getAuthenticationMethod() } /** * Either shows the bouncer or unlocks the device, if the bouncer doesn't need to be shown. * Loading @@ -115,8 +111,9 @@ constructor( containerName: String, message: String? = null, ) { applicationScope.launch { if (authenticationInteractor.isAuthenticationRequired()) { repository.setMessage(message ?: promptMessage(authenticationMethod.value)) repository.setMessage(message ?: promptMessage(getAuthenticationMethod())) sceneInteractor.setCurrentScene( containerName = containerName, scene = SceneModel(SceneKey.Bouncer), Loading @@ -129,13 +126,14 @@ constructor( ) } } } /** * Resets the user-facing message back to the default according to the current authentication * method. */ fun resetMessage() { repository.setMessage(promptMessage(authenticationMethod.value)) applicationScope.launch { repository.setMessage(promptMessage(getAuthenticationMethod())) } } /** Removes the user-facing message. */ Loading @@ -160,7 +158,7 @@ constructor( * @return `true` if the authentication succeeded and the device is now unlocked; `false` when * authentication failed, `null` if the check was not performed. */ fun authenticate( suspend fun authenticate( input: List<Any>, tryAutoConfirm: Boolean = false, ): Boolean? { Loading Loading @@ -198,7 +196,7 @@ constructor( repository.setThrottling(null) clearMessage() } else -> repository.setMessage(errorMessage(authenticationMethod.value)) else -> repository.setMessage(errorMessage(getAuthenticationMethod())) } return isAuthenticated Loading packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModel.kt +22 −20 Original line number Diff line number Diff line Loading @@ -32,6 +32,7 @@ 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 @@ -66,6 +67,7 @@ constructor( private val password: PasswordBouncerViewModel by lazy { PasswordBouncerViewModel( applicationScope = applicationScope, interactor = interactor, isInputEnabled = isInputEnabled, ) Loading @@ -81,13 +83,24 @@ constructor( } /** View-model for the current UI, based on the current authentication method. */ val authMethod: StateFlow<AuthMethodBouncerViewModel?> = interactor.authenticationMethod .map { authMethod -> toViewModel(authMethod) } 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( scope = applicationScope, started = SharingStarted.WhileSubscribed(), initialValue = toViewModel(interactor.authenticationMethod.value), initialValue = null, ) /** The user-facing message to show in the bouncer. */ Loading Loading @@ -125,7 +138,7 @@ constructor( interactor.throttling .map { model -> model?.let { when (interactor.authenticationMethod.value) { when (interactor.getAuthenticationMethod()) { is AuthenticationMethodModel.Pin -> R.string.kg_too_many_failed_pin_attempts_dialog_message is AuthenticationMethodModel.Password -> Loading Loading @@ -161,17 +174,6 @@ constructor( _throttlingDialogMessage.value = null } private fun toViewModel( authMethod: AuthenticationMethodModel, ): AuthMethodBouncerViewModel? { return when (authMethod) { is AuthenticationMethodModel.Pin -> pin is AuthenticationMethodModel.Password -> password is AuthenticationMethodModel.Pattern -> pattern else -> null } } private fun toMessageViewModel( message: String?, throttling: AuthenticationThrottledModel?, Loading Loading
packages/SystemUI/src/com/android/systemui/authentication/AuthenticationModule.kt +12 −1 Original line number Diff line number Diff line Loading @@ -16,8 +16,11 @@ package com.android.systemui.authentication import com.android.keyguard.KeyguardSecurityModel import com.android.systemui.authentication.data.repository.AuthenticationRepositoryModule import dagger.Module import dagger.Provides import java.util.function.Function @Module( includes = Loading @@ -25,4 +28,12 @@ import dagger.Module AuthenticationRepositoryModule::class, ], ) object AuthenticationModule object AuthenticationModule { @Provides fun getSecurityMode( model: KeyguardSecurityModel, ): Function<Int, KeyguardSecurityModel.SecurityMode> { return Function { userId -> model.getSecurityMode(userId) } } }
packages/SystemUI/src/com/android/systemui/authentication/data/repository/AuthenticationRepository.kt +57 −23 Original line number Diff line number Diff line Loading @@ -16,13 +16,20 @@ 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.Background import com.android.systemui.user.data.repository.UserRepository import dagger.Binds import dagger.Module import javax.inject.Inject import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.asStateFlow 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 @@ -37,12 +44,6 @@ interface AuthenticationRepository { */ val isUnlocked: StateFlow<Boolean> /** * The currently-configured authentication method. This determines how the authentication * challenge is completed in order to unlock an otherwise locked device. */ val authenticationMethod: StateFlow<AuthenticationMethodModel> /** * Whether lock screen bypass is enabled. When enabled, the lock screen will be automatically * dismisses once the authentication challenge is completed. For example, completing a biometric Loading @@ -57,12 +58,15 @@ interface AuthenticationRepository { */ val failedAuthenticationAttempts: StateFlow<Int> /** * Returns the currently-configured authentication method. This determines how the * authentication challenge is completed in order to unlock an otherwise locked device. */ suspend fun getAuthenticationMethod(): AuthenticationMethodModel /** See [isUnlocked]. */ fun setUnlocked(isUnlocked: Boolean) /** See [authenticationMethod]. */ fun setAuthenticationMethod(authenticationMethod: AuthenticationMethodModel) /** See [isBypassEnabled]. */ fun setBypassEnabled(isBypassEnabled: Boolean) Loading @@ -70,19 +74,18 @@ interface AuthenticationRepository { fun setFailedAuthenticationAttempts(failedAuthenticationAttempts: Int) } class AuthenticationRepositoryImpl @Inject constructor() : AuthenticationRepository { // TODO(b/280883900): get data from real data sources in SysUI. class AuthenticationRepositoryImpl @Inject constructor( private val getSecurityMode: Function<Int, KeyguardSecurityModel.SecurityMode>, @Background private val backgroundDispatcher: CoroutineDispatcher, private val userRepository: UserRepository, private val lockPatternUtils: LockPatternUtils, ) : AuthenticationRepository { private val _isUnlocked = MutableStateFlow(false) override val isUnlocked: StateFlow<Boolean> = _isUnlocked.asStateFlow() private val _authenticationMethod = MutableStateFlow<AuthenticationMethodModel>( AuthenticationMethodModel.Pin(listOf(1, 2, 3, 4), autoConfirm = false) ) override val authenticationMethod: StateFlow<AuthenticationMethodModel> = _authenticationMethod.asStateFlow() private val _isBypassEnabled = MutableStateFlow(false) override val isBypassEnabled: StateFlow<Boolean> = _isBypassEnabled.asStateFlow() Loading @@ -90,6 +93,41 @@ class AuthenticationRepositoryImpl @Inject constructor() : AuthenticationReposit override val failedAuthenticationAttempts: StateFlow<Int> = _failedAuthenticationAttempts.asStateFlow() override suspend fun getAuthenticationMethod(): AuthenticationMethodModel { return withContext(backgroundDispatcher) { val selectedUserId = userRepository.getSelectedUserInfo().id when (getSecurityMode.apply(selectedUserId)) { KeyguardSecurityModel.SecurityMode.PIN, KeyguardSecurityModel.SecurityMode.SimPin -> AuthenticationMethodModel.Pin( code = listOf(1, 2, 3, 4), // TODO(b/280883900): remove this autoConfirm = lockPatternUtils.isAutoPinConfirmEnabled(selectedUserId), ) KeyguardSecurityModel.SecurityMode.Password, KeyguardSecurityModel.SecurityMode.SimPuk -> AuthenticationMethodModel.Password( password = "password", // TODO(b/280883900): remove this ) KeyguardSecurityModel.SecurityMode.Pattern -> AuthenticationMethodModel.Pattern( coordinates = listOf( AuthenticationMethodModel.Pattern.PatternCoordinate(2, 0), AuthenticationMethodModel.Pattern.PatternCoordinate(2, 1), AuthenticationMethodModel.Pattern.PatternCoordinate(2, 2), AuthenticationMethodModel.Pattern.PatternCoordinate(1, 1), AuthenticationMethodModel.Pattern.PatternCoordinate(0, 0), AuthenticationMethodModel.Pattern.PatternCoordinate(0, 1), AuthenticationMethodModel.Pattern.PatternCoordinate(0, 2), ), // TODO(b/280883900): remove this ) KeyguardSecurityModel.SecurityMode.None -> AuthenticationMethodModel.None KeyguardSecurityModel.SecurityMode.Invalid -> error("Invalid security mode!") null -> error("Invalid security is null!") } } } override fun setUnlocked(isUnlocked: Boolean) { _isUnlocked.value = isUnlocked } Loading @@ -98,10 +136,6 @@ class AuthenticationRepositoryImpl @Inject constructor() : AuthenticationReposit _isBypassEnabled.value = isBypassEnabled } override fun setAuthenticationMethod(authenticationMethod: AuthenticationMethodModel) { _authenticationMethod.value = authenticationMethod } override fun setFailedAuthenticationAttempts(failedAuthenticationAttempts: Int) { _failedAuthenticationAttempts.value = failedAuthenticationAttempts } Loading
packages/SystemUI/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractor.kt +19 −49 Original line number Diff line number Diff line Loading @@ -25,9 +25,8 @@ import javax.inject.Inject import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.launch /** Hosts application business logic related to authentication. */ @SysUISingleton Loading @@ -37,12 +36,6 @@ constructor( @Application applicationScope: CoroutineScope, private val repository: AuthenticationRepository, ) { /** * The currently-configured authentication method. This determines how the authentication * challenge is completed in order to unlock an otherwise locked device. */ val authenticationMethod: StateFlow<AuthenticationMethodModel> = repository.authenticationMethod /** * Whether the device is unlocked. * Loading @@ -52,20 +45,18 @@ constructor( * Note that this state has no real bearing on whether the lock screen is showing or dismissed. */ val isUnlocked: StateFlow<Boolean> = combine(authenticationMethod, repository.isUnlocked) { authMethod, isUnlocked -> isUnlockedWithAuthMethod( isUnlocked = isUnlocked, authMethod = authMethod, ) repository.isUnlocked .map { isUnlocked -> if (getAuthenticationMethod() is AuthenticationMethodModel.None) { true } else { isUnlocked } } .stateIn( scope = applicationScope, started = SharingStarted.Eagerly, initialValue = isUnlockedWithAuthMethod( isUnlocked = repository.isUnlocked.value, authMethod = repository.authenticationMethod.value, ) initialValue = true, ) /** Loading @@ -82,25 +73,20 @@ constructor( */ val failedAuthenticationAttempts: StateFlow<Int> = repository.failedAuthenticationAttempts init { // UNLOCKS WHEN AUTH METHOD REMOVED. // // Unlocks the device if the auth method becomes None. applicationScope.launch { repository.authenticationMethod.collect { if (it is AuthenticationMethodModel.None) { unlockDevice() } } } /** * Returns the currently-configured authentication method. This determines how the * authentication challenge is completed in order to unlock an otherwise locked device. */ suspend fun getAuthenticationMethod(): AuthenticationMethodModel { return repository.getAuthenticationMethod() } /** * Returns `true` if the device currently requires authentication before content can be viewed; * `false` if content can be displayed without unlocking first. */ fun isAuthenticationRequired(): Boolean { return !isUnlocked.value && authenticationMethod.value.isSecure suspend fun isAuthenticationRequired(): Boolean { return !isUnlocked.value && getAuthenticationMethod().isSecure } /** Loading Loading @@ -133,8 +119,8 @@ constructor( * @return `true` if the authentication succeeded and the device is now unlocked; `false` when * authentication failed, `null` if the check was not performed. */ fun authenticate(input: List<Any>, tryAutoConfirm: Boolean = false): Boolean? { val authMethod = this.authenticationMethod.value suspend fun authenticate(input: List<Any>, tryAutoConfirm: Boolean = false): Boolean? { val authMethod = getAuthenticationMethod() if (tryAutoConfirm) { if ((authMethod as? AuthenticationMethodModel.Pin)?.autoConfirm != true) { // Do not attempt to authenticate unless the PIN lock is set to auto-confirm. Loading Loading @@ -176,28 +162,12 @@ constructor( repository.setUnlocked(true) } /** See [authenticationMethod]. */ fun setAuthenticationMethod(authenticationMethod: AuthenticationMethodModel) { repository.setAuthenticationMethod(authenticationMethod) } /** See [isBypassEnabled]. */ fun toggleBypassEnabled() { repository.setBypassEnabled(!repository.isBypassEnabled.value) } companion object { private fun isUnlockedWithAuthMethod( isUnlocked: Boolean, authMethod: AuthenticationMethodModel, ): Boolean { return if (authMethod is AuthenticationMethodModel.None) { true } else { isUnlocked } } /** * Returns a PIN code from the given list. It's assumed the given list elements are all * [Int] in the range [0-9]. Loading
packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractor.kt +40 −42 Original line number Diff line number Diff line Loading @@ -68,26 +68,14 @@ constructor( ) ) /** * The currently-configured authentication method. This determines how the authentication * challenge is completed in order to unlock an otherwise locked device. */ val authenticationMethod: StateFlow<AuthenticationMethodModel> = authenticationInteractor.authenticationMethod /** The current authentication throttling state. If `null`, there's no throttling. */ val throttling: StateFlow<AuthenticationThrottledModel?> = repository.throttling init { applicationScope.launch { combine( sceneInteractor.currentScene(containerName), authenticationInteractor.authenticationMethod, ::Pair, ) .collect { (currentScene, authMethod) -> sceneInteractor.currentScene(containerName).collect { currentScene -> if (currentScene.key == SceneKey.Bouncer) { when (authMethod) { when (getAuthenticationMethod()) { is AuthenticationMethodModel.None -> sceneInteractor.setCurrentScene( containerName, Loading @@ -105,6 +93,14 @@ constructor( } } /** * Returns the currently-configured authentication method. This determines how the * authentication challenge is completed in order to unlock an otherwise locked device. */ suspend fun getAuthenticationMethod(): AuthenticationMethodModel { return authenticationInteractor.getAuthenticationMethod() } /** * Either shows the bouncer or unlocks the device, if the bouncer doesn't need to be shown. * Loading @@ -115,8 +111,9 @@ constructor( containerName: String, message: String? = null, ) { applicationScope.launch { if (authenticationInteractor.isAuthenticationRequired()) { repository.setMessage(message ?: promptMessage(authenticationMethod.value)) repository.setMessage(message ?: promptMessage(getAuthenticationMethod())) sceneInteractor.setCurrentScene( containerName = containerName, scene = SceneModel(SceneKey.Bouncer), Loading @@ -129,13 +126,14 @@ constructor( ) } } } /** * Resets the user-facing message back to the default according to the current authentication * method. */ fun resetMessage() { repository.setMessage(promptMessage(authenticationMethod.value)) applicationScope.launch { repository.setMessage(promptMessage(getAuthenticationMethod())) } } /** Removes the user-facing message. */ Loading @@ -160,7 +158,7 @@ constructor( * @return `true` if the authentication succeeded and the device is now unlocked; `false` when * authentication failed, `null` if the check was not performed. */ fun authenticate( suspend fun authenticate( input: List<Any>, tryAutoConfirm: Boolean = false, ): Boolean? { Loading Loading @@ -198,7 +196,7 @@ constructor( repository.setThrottling(null) clearMessage() } else -> repository.setMessage(errorMessage(authenticationMethod.value)) else -> repository.setMessage(errorMessage(getAuthenticationMethod())) } return isAuthenticated Loading
packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModel.kt +22 −20 Original line number Diff line number Diff line Loading @@ -32,6 +32,7 @@ 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 @@ -66,6 +67,7 @@ constructor( private val password: PasswordBouncerViewModel by lazy { PasswordBouncerViewModel( applicationScope = applicationScope, interactor = interactor, isInputEnabled = isInputEnabled, ) Loading @@ -81,13 +83,24 @@ constructor( } /** View-model for the current UI, based on the current authentication method. */ val authMethod: StateFlow<AuthMethodBouncerViewModel?> = interactor.authenticationMethod .map { authMethod -> toViewModel(authMethod) } 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( scope = applicationScope, started = SharingStarted.WhileSubscribed(), initialValue = toViewModel(interactor.authenticationMethod.value), initialValue = null, ) /** The user-facing message to show in the bouncer. */ Loading Loading @@ -125,7 +138,7 @@ constructor( interactor.throttling .map { model -> model?.let { when (interactor.authenticationMethod.value) { when (interactor.getAuthenticationMethod()) { is AuthenticationMethodModel.Pin -> R.string.kg_too_many_failed_pin_attempts_dialog_message is AuthenticationMethodModel.Password -> Loading Loading @@ -161,17 +174,6 @@ constructor( _throttlingDialogMessage.value = null } private fun toViewModel( authMethod: AuthenticationMethodModel, ): AuthMethodBouncerViewModel? { return when (authMethod) { is AuthenticationMethodModel.Pin -> pin is AuthenticationMethodModel.Password -> password is AuthenticationMethodModel.Pattern -> pattern else -> null } } private fun toMessageViewModel( message: String?, throttling: AuthenticationThrottledModel?, Loading