1 /* 2 * Copyright (C) 2023 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.systemui.bouncer.ui.viewmodel 18 19 import android.annotation.StringRes 20 import com.android.systemui.authentication.domain.interactor.AuthenticationResult 21 import com.android.systemui.authentication.shared.model.AuthenticationMethodModel 22 import com.android.systemui.bouncer.domain.interactor.BouncerInteractor 23 import kotlinx.coroutines.CoroutineScope 24 import kotlinx.coroutines.flow.MutableStateFlow 25 import kotlinx.coroutines.flow.StateFlow 26 import kotlinx.coroutines.flow.asStateFlow 27 import kotlinx.coroutines.launch 28 29 sealed class AuthMethodBouncerViewModel( 30 protected val viewModelScope: CoroutineScope, 31 protected val interactor: BouncerInteractor, 32 33 /** 34 * Whether user input is enabled. 35 * 36 * If `false`, user input should be completely ignored in the UI as the user is "locked out" of 37 * being able to attempt to unlock the device. 38 */ 39 val isInputEnabled: StateFlow<Boolean>, 40 ) { 41 42 private val _animateFailure = MutableStateFlow(false) 43 /** 44 * Whether a failure animation should be shown. Once consumed, the UI must call 45 * [onFailureAnimationShown] to consume this state. 46 */ 47 val animateFailure: StateFlow<Boolean> = _animateFailure.asStateFlow() 48 49 /** The authentication method that corresponds to this view model. */ 50 abstract val authenticationMethod: AuthenticationMethodModel 51 52 /** 53 * String resource ID of the failure message to be shown during lockout. 54 * 55 * The message must include 2 number parameters: the first one indicating how many unsuccessful 56 * attempts were made, and the second one indicating in how many seconds lockout will expire. 57 */ 58 @get:StringRes abstract val lockoutMessageId: Int 59 60 /** 61 * Notifies that the UI has been hidden from the user (after any transitions have completed). 62 */ onHiddennull63 open fun onHidden() { 64 clearInput() 65 } 66 67 /** Notifies that the user has placed down a pointer. */ onDownnull68 fun onDown() { 69 interactor.onDown() 70 } 71 72 /** 73 * Notifies that the failure animation has been shown. This should be called to consume a `true` 74 * value in [animateFailure]. 75 */ onFailureAnimationShownnull76 fun onFailureAnimationShown() { 77 _animateFailure.value = false 78 } 79 80 /** Clears any previously-entered input. */ clearInputnull81 protected abstract fun clearInput() 82 83 /** Returns the input entered so far. */ 84 protected abstract fun getInput(): List<Any> 85 86 /** 87 * Attempts to authenticate the user using the current input value. 88 * 89 * @see BouncerInteractor.authenticate 90 */ 91 protected fun tryAuthenticate( 92 input: List<Any> = getInput(), 93 useAutoConfirm: Boolean = false, 94 ) { 95 viewModelScope.launch { 96 val authenticationResult = interactor.authenticate(input, useAutoConfirm) 97 if (authenticationResult == AuthenticationResult.SKIPPED && useAutoConfirm) { 98 return@launch 99 } 100 _animateFailure.value = authenticationResult != AuthenticationResult.SUCCEEDED 101 102 clearInput() 103 } 104 } 105 } 106