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.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
108         ) { 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
130             .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 
151     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 
165     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 
179     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]. */
186 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