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.Flags 20 import android.hardware.biometrics.PromptInfo 21 import com.android.internal.widget.LockPatternUtils 22 import com.android.systemui.biometrics.Utils 23 import com.android.systemui.biometrics.Utils.getCredentialType 24 import com.android.systemui.biometrics.Utils.isDeviceCredentialAllowed 25 import com.android.systemui.biometrics.data.repository.FingerprintPropertyRepository 26 import com.android.systemui.biometrics.data.repository.PromptRepository 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.BiometricModalities 30 import com.android.systemui.biometrics.shared.model.BiometricUserInfo 31 import com.android.systemui.biometrics.shared.model.FingerprintSensorType 32 import com.android.systemui.biometrics.shared.model.PromptKind 33 import com.android.systemui.dagger.SysUISingleton 34 import javax.inject.Inject 35 import kotlinx.coroutines.flow.Flow 36 import kotlinx.coroutines.flow.StateFlow 37 import kotlinx.coroutines.flow.combine 38 import kotlinx.coroutines.flow.distinctUntilChanged 39 import kotlinx.coroutines.flow.map 40 41 /** 42 * Business logic for BiometricPrompt's biometric view variants (face, fingerprint, coex, etc.). 43 * 44 * This is used to cache the calling app's options that were given to the underlying authenticate 45 * APIs and should be set before any UI is shown to the user. 46 * 47 * There can be at most one request active at a given time. Use [resetPrompt] when no request is 48 * active to clear the cache. 49 * 50 * Views that use credential fallback should use [PromptCredentialInteractor] instead. 51 */ 52 interface PromptSelectorInteractor { 53 54 /** Static metadata about the current prompt. */ 55 val prompt: Flow<BiometricPromptRequest.Biometric?> 56 57 /** The kind of prompt to use (biometric, pin, pattern, etc.). */ 58 val promptKind: StateFlow<PromptKind> 59 60 /** If using a credential is allowed. */ 61 val isCredentialAllowed: Flow<Boolean> 62 63 /** 64 * The kind of credential the user may use as a fallback or [PromptKind.None] if unknown or not 65 * [isCredentialAllowed]. This is separate from [promptKind], even if [promptKind] is 66 * [PromptKind.Biometric], [credentialKind] should still be one of pin/pattern/password. 67 */ 68 val credentialKind: Flow<PromptKind> 69 70 /** 71 * If the API caller or the user's personal preferences require explicit confirmation after 72 * successful authentication. 73 */ 74 val isConfirmationRequired: Flow<Boolean> 75 76 /** Fingerprint sensor type */ 77 val sensorType: Flow<FingerprintSensorType> 78 79 /** Switch to the credential view. */ 80 fun onSwitchToCredential() 81 82 /** 83 * Update the kind of prompt (biometric prompt w/ or w/o sensor icon, pin view, pattern view, 84 * etc). 85 */ 86 fun setPrompt( 87 promptInfo: PromptInfo, 88 effectiveUserId: Int, 89 requestId: Long, 90 modalities: BiometricModalities, 91 challenge: Long, 92 opPackageName: String, 93 onSwitchToCredential: Boolean, 94 isLandscape: Boolean, 95 ) 96 97 /** Unset the current authentication request. */ 98 fun resetPrompt(requestId: Long) 99 } 100 101 @SysUISingleton 102 class PromptSelectorInteractorImpl 103 @Inject 104 constructor( 105 fingerprintPropertyRepository: FingerprintPropertyRepository, 106 private val displayStateInteractor: DisplayStateInteractor, 107 private val promptRepository: PromptRepository, 108 private val lockPatternUtils: LockPatternUtils, 109 ) : PromptSelectorInteractor { 110 111 override val prompt: Flow<BiometricPromptRequest.Biometric?> = 112 combine( 113 promptRepository.promptInfo, 114 promptRepository.challenge, 115 promptRepository.userId, 116 promptRepository.promptKind, 117 promptRepository.opPackageName, kindnull118 ) { promptInfo, challenge, userId, kind, opPackageName -> 119 if ( 120 promptInfo == null || userId == null || challenge == null || opPackageName == null 121 ) { 122 return@combine null 123 } 124 125 when (kind) { 126 is PromptKind.Biometric -> 127 BiometricPromptRequest.Biometric( 128 info = promptInfo, 129 userInfo = BiometricUserInfo(userId = userId), 130 operationInfo = BiometricOperationInfo(gatekeeperChallenge = challenge), 131 modalities = kind.activeModalities, 132 opPackageName = opPackageName, 133 ) 134 else -> null 135 } 136 } 137 138 override val promptKind: StateFlow<PromptKind> = promptRepository.promptKind 139 140 override val isConfirmationRequired: Flow<Boolean> = 141 promptRepository.isConfirmationRequired.distinctUntilChanged() 142 143 override val isCredentialAllowed: Flow<Boolean> = 144 promptRepository.promptInfo infonull145 .map { info -> if (info != null) isDeviceCredentialAllowed(info) else false } 146 .distinctUntilChanged() 147 148 override val credentialKind: Flow<PromptKind> = isAllowednull149 combine(prompt, isCredentialAllowed) { prompt, isAllowed -> 150 if (prompt != null && isAllowed) { 151 getCredentialType(lockPatternUtils, prompt.userInfo.deviceCredentialOwnerId) 152 } else { 153 PromptKind.None 154 } 155 } 156 157 override val sensorType: Flow<FingerprintSensorType> = fingerprintPropertyRepository.sensorType 158 onSwitchToCredentialnull159 override fun onSwitchToCredential() { 160 val modalities: BiometricModalities = 161 if (promptRepository.promptKind.value.isBiometric()) 162 (promptRepository.promptKind.value as PromptKind.Biometric).activeModalities 163 else BiometricModalities() 164 setPrompt( 165 promptRepository.promptInfo.value!!, 166 promptRepository.userId.value!!, 167 promptRepository.requestId.value!!, 168 modalities, 169 promptRepository.challenge.value!!, 170 promptRepository.opPackageName.value!!, 171 onSwitchToCredential = true, 172 // isLandscape value is not important when onSwitchToCredential is true 173 isLandscape = false, 174 ) 175 } 176 setPromptnull177 override fun setPrompt( 178 promptInfo: PromptInfo, 179 effectiveUserId: Int, 180 requestId: Long, 181 modalities: BiometricModalities, 182 challenge: Long, 183 opPackageName: String, 184 onSwitchToCredential: Boolean, 185 isLandscape: Boolean, 186 ) { 187 val hasCredentialViewShown = promptKind.value.isCredential() 188 val showBpForCredential = 189 Flags.customBiometricPrompt() && 190 com.android.systemui.Flags.constraintBp() && 191 !Utils.isBiometricAllowed(promptInfo) && 192 isDeviceCredentialAllowed(promptInfo) && 193 promptInfo.contentView != null && 194 !promptInfo.isContentViewMoreOptionsButtonUsed 195 val showBpWithoutIconForCredential = showBpForCredential && !hasCredentialViewShown 196 var kind: PromptKind = PromptKind.None 197 198 if (onSwitchToCredential) { 199 kind = getCredentialType(lockPatternUtils, effectiveUserId) 200 } else if (Utils.isBiometricAllowed(promptInfo) || showBpWithoutIconForCredential) { 201 // TODO(b/330908557): Subscribe to 202 // displayStateInteractor.currentRotation.value.isDefaultOrientation() for checking 203 // `isLandscape` after removing AuthContinerView. 204 kind = 205 if (isLandscape) { 206 val paneType = 207 when { 208 displayStateInteractor.isLargeScreen.value -> 209 PromptKind.Biometric.PaneType.ONE_PANE_LARGE_SCREEN_LANDSCAPE 210 showBpWithoutIconForCredential -> 211 PromptKind.Biometric.PaneType.ONE_PANE_NO_SENSOR_LANDSCAPE 212 else -> PromptKind.Biometric.PaneType.TWO_PANE_LANDSCAPE 213 } 214 PromptKind.Biometric( 215 modalities, 216 paneType = paneType, 217 ) 218 } else { 219 PromptKind.Biometric(modalities) 220 } 221 } else if (isDeviceCredentialAllowed(promptInfo)) { 222 kind = getCredentialType(lockPatternUtils, effectiveUserId) 223 } 224 225 promptRepository.setPrompt( 226 promptInfo = promptInfo, 227 userId = effectiveUserId, 228 requestId = requestId, 229 gatekeeperChallenge = challenge, 230 kind = kind, 231 opPackageName = opPackageName, 232 ) 233 } 234 resetPromptnull235 override fun resetPrompt(requestId: Long) { 236 promptRepository.unsetPrompt(requestId) 237 } 238 } 239