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