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