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

Commit 2907102b authored by Ale Nijamkin's avatar Ale Nijamkin Committed by Android (Google) Code Review
Browse files

Merge "[flexiglass] Bouncer throttling - view models." into udc-dev

parents 76a7f333 1834519f
Loading
Loading
Loading
Loading
+11 −1
Original line number Diff line number Diff line
@@ -16,4 +16,14 @@

package com.android.systemui.bouncer.ui.viewmodel

sealed interface AuthMethodBouncerViewModel
import kotlinx.coroutines.flow.StateFlow

sealed interface AuthMethodBouncerViewModel {
    /**
     * Whether user input is enabled.
     *
     * If `false`, user input should be completely ignored in the UI as the user is "locked out" of
     * being able to attempt to unlock the device.
     */
    val isInputEnabled: StateFlow<Boolean>
}
+80 −0
Original line number Diff line number Diff line
@@ -17,6 +17,7 @@
package com.android.systemui.bouncer.ui.viewmodel

import android.content.Context
import com.android.systemui.R
import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
import com.android.systemui.bouncer.domain.interactor.BouncerInteractor
import com.android.systemui.dagger.qualifiers.Application
@@ -24,10 +25,14 @@ import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
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.distinctUntilChanged
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.launch

/** Holds UI state and handles user input on bouncer UIs. */
class BouncerViewModel
@@ -40,16 +45,42 @@ constructor(
) {
    private val interactor: BouncerInteractor = interactorFactory.create(containerName)

    /**
     * Whether updates to the message should be cross-animated from one message to another.
     *
     * If `false`, no animation should be applied, the message text should just be replaced
     * instantly.
     */
    val isMessageUpdateAnimationsEnabled: StateFlow<Boolean> =
        interactor.throttling
            .map { it == null }
            .stateIn(
                scope = applicationScope,
                started = SharingStarted.WhileSubscribed(),
                initialValue = interactor.throttling.value == null,
            )

    private val isInputEnabled: StateFlow<Boolean> =
        interactor.throttling
            .map { it == null }
            .stateIn(
                scope = applicationScope,
                started = SharingStarted.WhileSubscribed(),
                initialValue = interactor.throttling.value == null,
            )

    private val pin: PinBouncerViewModel by lazy {
        PinBouncerViewModel(
            applicationScope = applicationScope,
            interactor = interactor,
            isInputEnabled = isInputEnabled,
        )
    }

    private val password: PasswordBouncerViewModel by lazy {
        PasswordBouncerViewModel(
            interactor = interactor,
            isInputEnabled = isInputEnabled,
        )
    }

@@ -58,6 +89,7 @@ constructor(
            applicationContext = applicationContext,
            applicationScope = applicationScope,
            interactor = interactor,
            isInputEnabled = isInputEnabled,
        )
    }

@@ -81,11 +113,59 @@ constructor(
                initialValue = interactor.message.value ?: "",
            )

    private val _throttlingDialogMessage = MutableStateFlow<String?>(null)
    /**
     * A message for a throttling dialog to show when the user has attempted the wrong credential
     * too many times and now must wait a while before attempting again.
     *
     * If `null`, no dialog should be shown.
     *
     * Once the dialog is shown, the UI should call [onThrottlingDialogDismissed] when the user
     * dismisses this dialog.
     */
    val throttlingDialogMessage: StateFlow<String?> = _throttlingDialogMessage.asStateFlow()

    init {
        applicationScope.launch {
            interactor.throttling
                .map { model ->
                    model?.let {
                        when (interactor.authenticationMethod.value) {
                            is AuthenticationMethodModel.PIN ->
                                R.string.kg_too_many_failed_pin_attempts_dialog_message
                            is AuthenticationMethodModel.Password ->
                                R.string.kg_too_many_failed_password_attempts_dialog_message
                            is AuthenticationMethodModel.Pattern ->
                                R.string.kg_too_many_failed_pattern_attempts_dialog_message
                            else -> null
                        }?.let { stringResourceId ->
                            applicationContext.getString(
                                stringResourceId,
                                model.failedAttemptCount,
                                model.totalDurationSec,
                            )
                        }
                    }
                }
                .distinctUntilChanged()
                .collect { dialogMessageOrNull ->
                    if (dialogMessageOrNull != null) {
                        _throttlingDialogMessage.value = dialogMessageOrNull
                    }
                }
        }
    }

    /** Notifies that the emergency services button was clicked. */
    fun onEmergencyServicesButtonClicked() {
        // TODO(b/280877228): implement this
    }

    /** Notifies that a throttling dialog has been dismissed by the user. */
    fun onThrottlingDialogDismissed() {
        _throttlingDialogMessage.value = null
    }

    private fun toViewModel(
        authMethod: AuthenticationMethodModel,
    ): AuthMethodBouncerViewModel? {
+1 −0
Original line number Diff line number Diff line
@@ -24,6 +24,7 @@ import kotlinx.coroutines.flow.asStateFlow
/** Holds UI state and handles user input for the password bouncer UI. */
class PasswordBouncerViewModel(
    private val interactor: BouncerInteractor,
    override val isInputEnabled: StateFlow<Boolean>,
) : AuthMethodBouncerViewModel {

    private val _password = MutableStateFlow("")
+15 −0
Original line number Diff line number Diff line
@@ -37,6 +37,7 @@ class PatternBouncerViewModel(
    private val applicationContext: Context,
    applicationScope: CoroutineScope,
    private val interactor: BouncerInteractor,
    override val isInputEnabled: StateFlow<Boolean>,
) : AuthMethodBouncerViewModel {

    /** The number of columns in the dot grid. */
@@ -63,6 +64,16 @@ class PatternBouncerViewModel(
    /** All dots on the grid. */
    val dots: StateFlow<List<PatternDotViewModel>> = _dots.asStateFlow()

    /** Whether the pattern itself should be rendered visibly. */
    val isPatternVisible: StateFlow<Boolean> =
        interactor.authenticationMethod
            .map { authMethod -> isPatternVisible(authMethod) }
            .stateIn(
                scope = applicationScope,
                started = SharingStarted.Eagerly,
                initialValue = isPatternVisible(interactor.authenticationMethod.value),
            )

    /** Notifies that the UI has been shown to the user. */
    fun onShown() {
        interactor.resetMessage()
@@ -146,6 +157,10 @@ class PatternBouncerViewModel(
        _selectedDots.value = linkedSetOf()
    }

    private fun isPatternVisible(authMethodModel: AuthenticationMethodModel): Boolean {
        return (authMethodModel as? AuthenticationMethodModel.Pattern)?.isPatternVisible ?: false
    }

    private fun defaultDots(): List<PatternDotViewModel> {
        return buildList {
            (0 until columnCount).forEach { x ->
+1 −0
Original line number Diff line number Diff line
@@ -33,6 +33,7 @@ import kotlinx.coroutines.launch
class PinBouncerViewModel(
    private val applicationScope: CoroutineScope,
    private val interactor: BouncerInteractor,
    override val isInputEnabled: StateFlow<Boolean>,
) : AuthMethodBouncerViewModel {

    private val entered = MutableStateFlow<List<Int>>(emptyList())
Loading