1 /*
<lambda>null2 * 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.biometrics.domain.interactor
18
19 import android.hardware.biometrics.PromptInfo
20 import com.android.internal.widget.LockPatternUtils
21 import com.android.systemui.biometrics.Utils
22 import com.android.systemui.biometrics.Utils.getCredentialType
23 import com.android.systemui.biometrics.Utils.isDeviceCredentialAllowed
24 import com.android.systemui.biometrics.data.repository.FingerprintPropertyRepository
25 import com.android.systemui.biometrics.data.repository.PromptRepository
26 import com.android.systemui.biometrics.domain.model.BiometricModalities
27 import com.android.systemui.biometrics.domain.model.BiometricOperationInfo
28 import com.android.systemui.biometrics.domain.model.BiometricPromptRequest
29 import com.android.systemui.biometrics.shared.model.BiometricUserInfo
30 import com.android.systemui.biometrics.shared.model.FingerprintSensorType
31 import com.android.systemui.biometrics.shared.model.PromptKind
32 import com.android.systemui.dagger.SysUISingleton
33 import javax.inject.Inject
34 import kotlinx.coroutines.flow.Flow
35 import kotlinx.coroutines.flow.combine
36 import kotlinx.coroutines.flow.distinctUntilChanged
37 import kotlinx.coroutines.flow.map
38
39 /**
40 * Business logic for BiometricPrompt's biometric view variants (face, fingerprint, coex, etc.).
41 *
42 * This is used to cache the calling app's options that were given to the underlying authenticate
43 * APIs and should be set before any UI is shown to the user.
44 *
45 * There can be at most one request active at a given time. Use [resetPrompt] when no request is
46 * active to clear the cache.
47 *
48 * Views that use credential fallback should use [PromptCredentialInteractor] instead.
49 */
50 interface PromptSelectorInteractor {
51
52 /** Static metadata about the current prompt. */
53 val prompt: Flow<BiometricPromptRequest.Biometric?>
54
55 /** If using a credential is allowed. */
56 val isCredentialAllowed: Flow<Boolean>
57
58 /**
59 * The kind of credential the user may use as a fallback or [PromptKind.Biometric] if unknown or
60 * not [isCredentialAllowed].
61 */
62 val credentialKind: Flow<PromptKind>
63
64 /**
65 * If the API caller or the user's personal preferences require explicit confirmation after
66 * successful authentication.
67 */
68 val isConfirmationRequired: Flow<Boolean>
69
70 /** Fingerprint sensor type */
71 val sensorType: Flow<FingerprintSensorType>
72
73 /** Use biometrics for authentication. */
74 fun useBiometricsForAuthentication(
75 promptInfo: PromptInfo,
76 userId: Int,
77 challenge: Long,
78 modalities: BiometricModalities,
79 )
80
81 /** Use credential-based authentication instead of biometrics. */
82 fun useCredentialsForAuthentication(
83 promptInfo: PromptInfo,
84 @Utils.CredentialType kind: Int,
85 userId: Int,
86 challenge: Long,
87 )
88
89 /** Unset the current authentication request. */
90 fun resetPrompt()
91 }
92
93 @SysUISingleton
94 class PromptSelectorInteractorImpl
95 @Inject
96 constructor(
97 fingerprintPropertyRepository: FingerprintPropertyRepository,
98 private val promptRepository: PromptRepository,
99 lockPatternUtils: LockPatternUtils,
100 ) : PromptSelectorInteractor {
101
102 override val prompt: Flow<BiometricPromptRequest.Biometric?> =
103 combine(
104 promptRepository.promptInfo,
105 promptRepository.challenge,
106 promptRepository.userId,
107 promptRepository.kind
kindnull108 ) { promptInfo, challenge, userId, kind ->
109 if (promptInfo == null || userId == null || challenge == null) {
110 return@combine null
111 }
112
113 when (kind) {
114 is PromptKind.Biometric ->
115 BiometricPromptRequest.Biometric(
116 info = promptInfo,
117 userInfo = BiometricUserInfo(userId = userId),
118 operationInfo = BiometricOperationInfo(gatekeeperChallenge = challenge),
119 modalities = kind.activeModalities,
120 )
121 else -> null
122 }
123 }
124
125 override val isConfirmationRequired: Flow<Boolean> =
126 promptRepository.isConfirmationRequired.distinctUntilChanged()
127
128 override val isCredentialAllowed: Flow<Boolean> =
129 promptRepository.promptInfo
infonull130 .map { info -> if (info != null) isDeviceCredentialAllowed(info) else false }
131 .distinctUntilChanged()
132
133 override val credentialKind: Flow<PromptKind> =
134 combine(prompt, isCredentialAllowed) { prompt, isAllowed ->
135 if (prompt != null && isAllowed) {
136 when (
137 getCredentialType(lockPatternUtils, prompt.userInfo.deviceCredentialOwnerId)
138 ) {
139 Utils.CREDENTIAL_PIN -> PromptKind.Pin
140 Utils.CREDENTIAL_PASSWORD -> PromptKind.Password
141 Utils.CREDENTIAL_PATTERN -> PromptKind.Pattern
142 else -> PromptKind.Biometric()
143 }
144 } else {
145 PromptKind.Biometric()
146 }
147 }
148
149 override val sensorType: Flow<FingerprintSensorType> = fingerprintPropertyRepository.sensorType
150
useBiometricsForAuthenticationnull151 override fun useBiometricsForAuthentication(
152 promptInfo: PromptInfo,
153 userId: Int,
154 challenge: Long,
155 modalities: BiometricModalities
156 ) {
157 promptRepository.setPrompt(
158 promptInfo = promptInfo,
159 userId = userId,
160 gatekeeperChallenge = challenge,
161 kind = PromptKind.Biometric(modalities),
162 )
163 }
164
useCredentialsForAuthenticationnull165 override fun useCredentialsForAuthentication(
166 promptInfo: PromptInfo,
167 @Utils.CredentialType kind: Int,
168 userId: Int,
169 challenge: Long,
170 ) {
171 promptRepository.setPrompt(
172 promptInfo = promptInfo,
173 userId = userId,
174 gatekeeperChallenge = challenge,
175 kind = kind.asBiometricPromptCredential(),
176 )
177 }
178
resetPromptnull179 override fun resetPrompt() {
180 promptRepository.unsetPrompt()
181 }
182 }
183
184 // TODO(b/251476085): remove along with Utils.CredentialType
185 /** Convert a [Utils.CredentialType] to the corresponding [PromptKind]. */
asBiometricPromptCredentialnull186 private fun @receiver:Utils.CredentialType Int.asBiometricPromptCredential(): PromptKind =
187 when (this) {
188 Utils.CREDENTIAL_PIN -> PromptKind.Pin
189 Utils.CREDENTIAL_PASSWORD -> PromptKind.Password
190 Utils.CREDENTIAL_PATTERN -> PromptKind.Pattern
191 else -> PromptKind.Biometric()
192 }
193