• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download

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