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 18 package com.android.systemui.keyguard.domain.interactor 19 20 import android.content.res.Resources 21 import android.hardware.biometrics.BiometricSourceType 22 import android.hardware.biometrics.BiometricSourceType.FINGERPRINT 23 import android.hardware.fingerprint.FingerprintManager 24 import com.android.keyguard.KeyguardUpdateMonitor 25 import com.android.keyguard.KeyguardUpdateMonitor.BIOMETRIC_HELP_FINGERPRINT_NOT_RECOGNIZED 26 import com.android.systemui.biometrics.data.repository.FingerprintPropertyRepository 27 import com.android.systemui.biometrics.shared.model.FingerprintSensorType 28 import com.android.systemui.dagger.SysUISingleton 29 import com.android.systemui.dagger.qualifiers.Main 30 import com.android.systemui.keyguard.data.repository.DeviceEntryFingerprintAuthRepository 31 import com.android.systemui.keyguard.shared.model.ErrorFingerprintAuthenticationStatus 32 import com.android.systemui.keyguard.shared.model.FailFingerprintAuthenticationStatus 33 import com.android.systemui.keyguard.shared.model.HelpFingerprintAuthenticationStatus 34 import com.android.systemui.keyguard.util.IndicationHelper 35 import javax.inject.Inject 36 import kotlinx.coroutines.ExperimentalCoroutinesApi 37 import kotlinx.coroutines.flow.Flow 38 import kotlinx.coroutines.flow.filter 39 import kotlinx.coroutines.flow.filterNot 40 import kotlinx.coroutines.flow.flatMapLatest 41 import kotlinx.coroutines.flow.map 42 43 /** 44 * BiometricMessage business logic. Filters biometric error/acquired/fail/success events for 45 * authentication events that should never surface a message to the user at the current device 46 * state. 47 */ 48 @ExperimentalCoroutinesApi 49 @SysUISingleton 50 class BiometricMessageInteractor 51 @Inject 52 constructor( 53 @Main private val resources: Resources, 54 private val fingerprintAuthRepository: DeviceEntryFingerprintAuthRepository, 55 private val fingerprintPropertyRepository: FingerprintPropertyRepository, 56 private val indicationHelper: IndicationHelper, 57 private val keyguardUpdateMonitor: KeyguardUpdateMonitor, 58 ) { 59 val fingerprintErrorMessage: Flow<BiometricMessage> = 60 fingerprintAuthRepository.authenticationStatus 61 .filter { 62 it is ErrorFingerprintAuthenticationStatus && 63 !indicationHelper.shouldSuppressErrorMsg(FINGERPRINT, it.msgId) 64 } 65 .map { 66 val errorStatus = it as ErrorFingerprintAuthenticationStatus 67 BiometricMessage( 68 FINGERPRINT, 69 BiometricMessageType.ERROR, 70 errorStatus.msgId, 71 errorStatus.msg, 72 ) 73 } 74 75 val fingerprintHelpMessage: Flow<BiometricMessage> = 76 fingerprintAuthRepository.authenticationStatus 77 .filter { it is HelpFingerprintAuthenticationStatus } 78 .filterNot { isPrimaryAuthRequired() } 79 .map { 80 val helpStatus = it as HelpFingerprintAuthenticationStatus 81 BiometricMessage( 82 FINGERPRINT, 83 BiometricMessageType.HELP, 84 helpStatus.msgId, 85 helpStatus.msg, 86 ) 87 } 88 89 val fingerprintFailMessage: Flow<BiometricMessage> = 90 isUdfps().flatMapLatest { isUdfps -> 91 fingerprintAuthRepository.authenticationStatus 92 .filter { it is FailFingerprintAuthenticationStatus } 93 .filterNot { isPrimaryAuthRequired() } 94 .map { 95 BiometricMessage( 96 FINGERPRINT, 97 BiometricMessageType.FAIL, 98 BIOMETRIC_HELP_FINGERPRINT_NOT_RECOGNIZED, 99 if (isUdfps) { 100 resources.getString( 101 com.android.internal.R.string.fingerprint_udfps_error_not_match 102 ) 103 } else { 104 resources.getString( 105 com.android.internal.R.string.fingerprint_error_not_match 106 ) 107 }, 108 ) 109 } 110 } 111 112 private fun isUdfps() = 113 fingerprintPropertyRepository.sensorType.map { 114 it == FingerprintSensorType.UDFPS_OPTICAL || 115 it == FingerprintSensorType.UDFPS_ULTRASONIC 116 } 117 118 private fun isPrimaryAuthRequired(): Boolean { 119 // Only checking if unlocking with Biometric is allowed (no matter strong or non-strong 120 // as long as primary auth, i.e. PIN/pattern/password, is required), so it's ok to 121 // pass true for isStrongBiometric to isUnlockingWithBiometricAllowed() to bypass the 122 // check of whether non-strong biometric is allowed since strong biometrics can still be 123 // used. 124 return !keyguardUpdateMonitor.isUnlockingWithBiometricAllowed(true /* isStrongBiometric */) 125 } 126 } 127 128 data class BiometricMessage( 129 val source: BiometricSourceType, 130 val type: BiometricMessageType, 131 val id: Int, 132 val message: String?, 133 ) { 134 fun isFingerprintLockoutMessage(): Boolean { 135 return source == FINGERPRINT && 136 type == BiometricMessageType.ERROR && 137 (id == FingerprintManager.FINGERPRINT_ERROR_LOCKOUT || 138 id == FingerprintManager.FINGERPRINT_ERROR_LOCKOUT_PERMANENT) 139 } 140 } 141 142 enum class BiometricMessageType { 143 HELP, 144 ERROR, 145 FAIL, 146 } 147