Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 6e681ecf authored by TreeHugger Robot's avatar TreeHugger Robot Committed by Automerger Merge Worker
Browse files

Merge "[flexiglass] Hooks up real app state and logic in keyguard (1)" into...

Merge "[flexiglass] Hooks up real app state and logic in keyguard (1)" into udc-qpr-dev am: 848b73fe am: fdad4b60

Original change: https://googleplex-android-review.googlesource.com/c/platform/frameworks/base/+/23783576



Change-Id: I528b618736067de97792e16c542bd06c9a3f434d
Signed-off-by: default avatarAutomerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
parents 8057abea fdad4b60
Loading
Loading
Loading
Loading
+12 −1
Original line number Diff line number Diff line
@@ -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 =
@@ -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) }
    }
}
+57 −23
Original line number Diff line number Diff line
@@ -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 {
@@ -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
@@ -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)

@@ -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()

@@ -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
    }
@@ -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
    }
+19 −49
Original line number Diff line number Diff line
@@ -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
@@ -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.
     *
@@ -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,
            )

    /**
@@ -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
    }

    /**
@@ -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.
@@ -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].
+40 −42
Original line number Diff line number Diff line
@@ -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,
@@ -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.
     *
@@ -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),
@@ -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. */
@@ -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? {
@@ -198,7 +196,7 @@ constructor(
                    repository.setThrottling(null)
                    clearMessage()
                }
            else -> repository.setMessage(errorMessage(authenticationMethod.value))
            else -> repository.setMessage(errorMessage(getAuthenticationMethod()))
        }

        return isAuthenticated
+22 −20
Original line number Diff line number Diff line
@@ -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
@@ -66,6 +67,7 @@ constructor(

    private val password: PasswordBouncerViewModel by lazy {
        PasswordBouncerViewModel(
            applicationScope = applicationScope,
            interactor = interactor,
            isInputEnabled = isInputEnabled,
        )
@@ -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. */
@@ -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 ->
@@ -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