<lambda>null1 package com.android.systemui.biometrics.ui.viewmodel
2
3 import android.content.Context
4 import android.graphics.drawable.Drawable
5 import android.text.InputType
6 import com.android.internal.widget.LockPatternView
7 import com.android.systemui.R
8 import com.android.systemui.biometrics.Utils
9 import com.android.systemui.biometrics.domain.interactor.CredentialStatus
10 import com.android.systemui.biometrics.domain.interactor.PromptCredentialInteractor
11 import com.android.systemui.biometrics.domain.model.BiometricPromptRequest
12 import com.android.systemui.biometrics.shared.model.BiometricUserInfo
13 import com.android.systemui.dagger.qualifiers.Application
14 import javax.inject.Inject
15 import kotlin.reflect.KClass
16 import kotlinx.coroutines.flow.Flow
17 import kotlinx.coroutines.flow.MutableSharedFlow
18 import kotlinx.coroutines.flow.MutableStateFlow
19 import kotlinx.coroutines.flow.asSharedFlow
20 import kotlinx.coroutines.flow.asStateFlow
21 import kotlinx.coroutines.flow.combine
22 import kotlinx.coroutines.flow.filterIsInstance
23 import kotlinx.coroutines.flow.map
24
25 /** View-model for all CredentialViews within BiometricPrompt. */
26 class CredentialViewModel
27 @Inject
28 constructor(
29 @Application private val applicationContext: Context,
30 private val credentialInteractor: PromptCredentialInteractor,
31 ) {
32
33 /** Top level information about the prompt. */
34 val header: Flow<CredentialHeaderViewModel> =
35 credentialInteractor.prompt.filterIsInstance<BiometricPromptRequest.Credential>().map {
36 request ->
37 BiometricPromptHeaderViewModelImpl(
38 request,
39 user = request.userInfo,
40 title = request.title,
41 subtitle = request.subtitle,
42 description = request.description,
43 icon = applicationContext.asLockIcon(request.userInfo.deviceCredentialOwnerId),
44 )
45 }
46
47 /** Input flags for text based credential views */
48 val inputFlags: Flow<Int?> =
49 credentialInteractor.prompt.map {
50 when (it) {
51 is BiometricPromptRequest.Credential.Pin ->
52 InputType.TYPE_CLASS_NUMBER or InputType.TYPE_NUMBER_VARIATION_PASSWORD
53 else -> null
54 }
55 }
56
57 /** If stealth mode is active (hide user credential input). */
58 val stealthMode: Flow<Boolean> =
59 credentialInteractor.prompt.map {
60 when (it) {
61 is BiometricPromptRequest.Credential.Pattern -> it.stealthMode
62 else -> false
63 }
64 }
65
66 private val _animateContents: MutableStateFlow<Boolean> = MutableStateFlow(true)
67 /** If this view should be animated on transitions. */
68 val animateContents = _animateContents.asStateFlow()
69
70 /** Error messages to show the user. */
71 val errorMessage: Flow<String> =
72 combine(credentialInteractor.verificationError, credentialInteractor.prompt) { error, p ->
73 when (error) {
74 is CredentialStatus.Fail.Error -> error.error
75 ?: applicationContext.asBadCredentialErrorMessage(p)
76 is CredentialStatus.Fail.Throttled -> error.error
77 null -> ""
78 }
79 }
80
81 private val _validatedAttestation: MutableSharedFlow<ByteArray?> = MutableSharedFlow()
82 /** Results of [checkPatternCredential]. A non-null attestation is supplied on success. */
83 val validatedAttestation: Flow<ByteArray?> = _validatedAttestation.asSharedFlow()
84
85 private val _remainingAttempts: MutableStateFlow<RemainingAttempts> =
86 MutableStateFlow(RemainingAttempts())
87 /** If set, the number of remaining attempts before the user must stop. */
88 val remainingAttempts: Flow<RemainingAttempts> = _remainingAttempts.asStateFlow()
89
90 /** Enable transition animations. */
91 fun setAnimateContents(animate: Boolean) {
92 _animateContents.value = animate
93 }
94
95 /** Show an error message to inform the user the pattern is too short to attempt validation. */
96 fun showPatternTooShortError() {
97 credentialInteractor.setVerificationError(
98 CredentialStatus.Fail.Error(
99 applicationContext.asBadCredentialErrorMessage(
100 BiometricPromptRequest.Credential.Pattern::class
101 )
102 )
103 )
104 }
105
106 /** Reset the error message to an empty string. */
107 fun resetErrorMessage() {
108 credentialInteractor.resetVerificationError()
109 }
110
111 /** Check a PIN or password and update [validatedAttestation] or [remainingAttempts]. */
112 suspend fun checkCredential(text: CharSequence, header: CredentialHeaderViewModel) =
113 checkCredential(credentialInteractor.checkCredential(header.asRequest(), text = text))
114
115 /** Check a pattern and update [validatedAttestation] or [remainingAttempts]. */
116 suspend fun checkCredential(
117 pattern: List<LockPatternView.Cell>,
118 header: CredentialHeaderViewModel
119 ) = checkCredential(credentialInteractor.checkCredential(header.asRequest(), pattern = pattern))
120
121 private suspend fun checkCredential(result: CredentialStatus) {
122 when (result) {
123 is CredentialStatus.Success.Verified -> {
124 _validatedAttestation.emit(result.hat)
125 _remainingAttempts.value = RemainingAttempts()
126 }
127 is CredentialStatus.Fail.Error -> {
128 _validatedAttestation.emit(null)
129 _remainingAttempts.value =
130 RemainingAttempts(result.remainingAttempts, result.urgentMessage ?: "")
131 }
132 is CredentialStatus.Fail.Throttled -> {
133 // required for completeness, but a throttled error cannot be the final result
134 _validatedAttestation.emit(null)
135 _remainingAttempts.value = RemainingAttempts()
136 }
137 }
138 }
139 }
140
Contextnull141 private fun Context.asBadCredentialErrorMessage(prompt: BiometricPromptRequest?): String =
142 asBadCredentialErrorMessage(
143 if (prompt != null) prompt::class else BiometricPromptRequest.Credential.Password::class
144 )
145
146 private fun <T : BiometricPromptRequest> Context.asBadCredentialErrorMessage(
147 clazz: KClass<T>
148 ): String =
149 getString(
150 when (clazz) {
151 BiometricPromptRequest.Credential.Pin::class -> R.string.biometric_dialog_wrong_pin
152 BiometricPromptRequest.Credential.Password::class ->
153 R.string.biometric_dialog_wrong_password
154 BiometricPromptRequest.Credential.Pattern::class ->
155 R.string.biometric_dialog_wrong_pattern
156 else -> R.string.biometric_dialog_wrong_password
157 }
158 )
159
Contextnull160 private fun Context.asLockIcon(userId: Int): Drawable {
161 val id =
162 if (Utils.isManagedProfile(this, userId)) {
163 R.drawable.auth_dialog_enterprise
164 } else {
165 R.drawable.auth_dialog_lock
166 }
167 return resources.getDrawable(id, theme)
168 }
169
170 private class BiometricPromptHeaderViewModelImpl(
171 val request: BiometricPromptRequest.Credential,
172 override val user: BiometricUserInfo,
173 override val title: String,
174 override val subtitle: String,
175 override val description: String,
176 override val icon: Drawable,
177 ) : CredentialHeaderViewModel
178
asRequestnull179 private fun CredentialHeaderViewModel.asRequest(): BiometricPromptRequest.Credential =
180 (this as BiometricPromptHeaderViewModelImpl).request
181