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