• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
<lambda>null2  * Copyright (C) 2024 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.deviceentry.domain.interactor
18 
19 import android.content.res.Resources
20 import com.android.systemui.biometrics.domain.interactor.FingerprintPropertyInteractor
21 import com.android.systemui.dagger.SysUISingleton
22 import com.android.systemui.dagger.qualifiers.Main
23 import com.android.systemui.deviceentry.shared.model.ErrorFaceAuthenticationStatus
24 import com.android.systemui.deviceentry.shared.model.FaceFailureMessage
25 import com.android.systemui.deviceentry.shared.model.FaceLockoutMessage
26 import com.android.systemui.deviceentry.shared.model.FaceMessage
27 import com.android.systemui.deviceentry.shared.model.FaceTimeoutMessage
28 import com.android.systemui.deviceentry.shared.model.FailedFaceAuthenticationStatus
29 import com.android.systemui.deviceentry.shared.model.FingerprintFailureMessage
30 import com.android.systemui.deviceentry.shared.model.FingerprintLockoutMessage
31 import com.android.systemui.deviceentry.shared.model.FingerprintMessage
32 import com.android.systemui.deviceentry.shared.model.HelpFaceAuthenticationStatus
33 import com.android.systemui.keyguard.domain.interactor.DevicePostureInteractor
34 import com.android.systemui.keyguard.shared.model.DevicePosture
35 import com.android.systemui.keyguard.shared.model.ErrorFingerprintAuthenticationStatus
36 import com.android.systemui.res.R
37 import com.android.systemui.util.kotlin.sample
38 import javax.inject.Inject
39 import kotlinx.coroutines.flow.Flow
40 import kotlinx.coroutines.flow.combine
41 import kotlinx.coroutines.flow.filter
42 import kotlinx.coroutines.flow.filterIsInstance
43 import kotlinx.coroutines.flow.filterNot
44 import kotlinx.coroutines.flow.flatMapLatest
45 import kotlinx.coroutines.flow.flowOf
46 import kotlinx.coroutines.flow.map
47 import kotlinx.coroutines.flow.merge
48 
49 /**
50  * BiometricMessage business logic. Filters biometric error/fail/success events for authentication
51  * events that should never surface a message to the user at the current device state.
52  */
53 @SysUISingleton
54 class BiometricMessageInteractor
55 @Inject
56 constructor(
57     @Main private val resources: Resources,
58     fingerprintAuthInteractor: DeviceEntryFingerprintAuthInteractor,
59     fingerprintPropertyInteractor: FingerprintPropertyInteractor,
60     faceAuthInteractor: DeviceEntryFaceAuthInteractor,
61     private val biometricSettingsInteractor: DeviceEntryBiometricSettingsInteractor,
62     faceHelpMessageDeferralInteractor: FaceHelpMessageDeferralInteractor,
63     devicePostureInteractor: DevicePostureInteractor,
64 ) {
65     private val faceHelp: Flow<HelpFaceAuthenticationStatus> =
66         faceAuthInteractor.authenticationStatus.filterIsInstance<HelpFaceAuthenticationStatus>()
67     private val faceError: Flow<ErrorFaceAuthenticationStatus> =
68         faceAuthInteractor.authenticationStatus.filterIsInstance<ErrorFaceAuthenticationStatus>()
69     private val faceFailure: Flow<FailedFaceAuthenticationStatus> =
70         faceAuthInteractor.authenticationStatus.filterIsInstance<FailedFaceAuthenticationStatus>()
71 
72     /**
73      * The acquisition message ids to show message when both fingerprint and face are enrolled and
74      * enabled for device entry.
75      */
76     private val coExFaceAcquisitionMsgIdsToShowDefault: Set<Int> =
77         resources.getIntArray(R.array.config_face_help_msgs_when_fingerprint_enrolled).toSet()
78 
79     /**
80      * The acquisition message ids to show message when both fingerprint and face are enrolled and
81      * enabled for device entry and the device is unfolded.
82      */
83     private val coExFaceAcquisitionMsgIdsToShowUnfolded: Set<Int> =
84         resources
85             .getIntArray(R.array.config_face_help_msgs_when_fingerprint_enrolled_unfolded)
86             .toSet()
87 
88     private fun ErrorFingerprintAuthenticationStatus.shouldSuppressError(): Boolean {
89         return isCancellationError() || isPowerPressedError()
90     }
91 
92     private fun ErrorFaceAuthenticationStatus.shouldSuppressError(): Boolean {
93         return isCancellationError() || isUnableToProcessError()
94     }
95 
96     private val fingerprintErrorMessage: Flow<FingerprintMessage> =
97         fingerprintAuthInteractor.fingerprintError
98             .filterNot { it.shouldSuppressError() }
99             .sample(biometricSettingsInteractor.fingerprintAuthCurrentlyAllowed, ::Pair)
100             .filter { (errorStatus, fingerprintAuthAllowed) ->
101                 fingerprintAuthAllowed || errorStatus.isLockoutError()
102             }
103             .map { (errorStatus, _) ->
104                 when {
105                     errorStatus.isLockoutError() -> FingerprintLockoutMessage(errorStatus.msg)
106                     else -> FingerprintMessage(errorStatus.msg)
107                 }
108             }
109 
110     private val fingerprintHelpMessage: Flow<FingerprintMessage> =
111         fingerprintAuthInteractor.fingerprintHelp
112             .sample(biometricSettingsInteractor.fingerprintAuthCurrentlyAllowed, ::Pair)
113             .filter { (_, fingerprintAuthAllowed) -> fingerprintAuthAllowed }
114             .map { (helpStatus, _) -> FingerprintMessage(helpStatus.msg) }
115 
116     private val fingerprintFailMessage: Flow<FingerprintMessage> =
117         fingerprintPropertyInteractor.isUdfps.flatMapLatest { isUdfps ->
118             fingerprintAuthInteractor.fingerprintFailure
119                 .sample(biometricSettingsInteractor.fingerprintAuthCurrentlyAllowed)
120                 .filter { fingerprintAuthAllowed -> fingerprintAuthAllowed }
121                 .map {
122                     FingerprintFailureMessage(
123                         if (isUdfps) {
124                             resources.getString(
125                                 com.android.internal.R.string.fingerprint_udfps_error_not_match
126                             )
127                         } else {
128                             resources.getString(
129                                 com.android.internal.R.string.fingerprint_error_not_match
130                             )
131                         }
132                     )
133                 }
134         }
135 
136     val coExFaceAcquisitionMsgIdsToShow: Flow<Set<Int>> =
137         devicePostureInteractor.posture.map { devicePosture ->
138             when (devicePosture) {
139                 DevicePosture.OPENED -> coExFaceAcquisitionMsgIdsToShowUnfolded
140                 DevicePosture.UNKNOWN, // Devices without posture support (non-foldable) use UNKNOWN
141                 DevicePosture.CLOSED,
142                 DevicePosture.HALF_OPENED,
143                 DevicePosture.FLIPPED -> coExFaceAcquisitionMsgIdsToShowDefault
144             }
145         }
146 
147     val fingerprintMessage: Flow<FingerprintMessage> =
148         merge(
149             fingerprintErrorMessage,
150             fingerprintFailMessage,
151             fingerprintHelpMessage,
152         )
153 
154     private val filterConditionForFaceHelpMessages:
155         Flow<(HelpFaceAuthenticationStatus) -> Boolean> =
156         combine(
157                 biometricSettingsInteractor.isFingerprintAuthEnrolledAndEnabled,
158                 biometricSettingsInteractor.faceAuthCurrentlyAllowed,
159                 ::Pair
160             )
161             .flatMapLatest { (fingerprintEnrolled, faceAuthCurrentlyAllowed) ->
162                 if (fingerprintEnrolled && faceAuthCurrentlyAllowed) {
163                     // Show only some face help messages if fingerprint is also enrolled
164                     coExFaceAcquisitionMsgIdsToShow.map { msgIdsToShow ->
165                         { helpStatus: HelpFaceAuthenticationStatus ->
166                             msgIdsToShow.contains(helpStatus.msgId)
167                         }
168                     }
169                 } else if (faceAuthCurrentlyAllowed) {
170                     // Show all face help messages if only face is enrolled and currently allowed
171                     flowOf { _: HelpFaceAuthenticationStatus -> true }
172                 } else {
173                     flowOf { _: HelpFaceAuthenticationStatus -> false }
174                 }
175             }
176 
177     private val faceHelpMessage: Flow<FaceMessage> =
178         faceHelp
179             .filterNot {
180                 // Message deferred to potentially show at face timeout error instead
181                 faceHelpMessageDeferralInteractor.shouldDefer(it.msgId)
182             }
183             .sample(filterConditionForFaceHelpMessages, ::Pair)
184             .filter { (helpMessage, filterCondition) -> filterCondition(helpMessage) }
185             .map { (status, _) -> FaceMessage(status.msg) }
186 
187     private val faceFailureMessage: Flow<FaceMessage> =
188         faceFailure
189             .sample(biometricSettingsInteractor.faceAuthCurrentlyAllowed)
190             .filter { faceAuthCurrentlyAllowed -> faceAuthCurrentlyAllowed }
191             .map { FaceFailureMessage(resources.getString(R.string.keyguard_face_failed)) }
192 
193     private val faceErrorMessage: Flow<FaceMessage> =
194         faceError
195             .filterNot { it.shouldSuppressError() }
196             .sample(biometricSettingsInteractor.faceAuthCurrentlyAllowed, ::Pair)
197             .filter { (errorStatus, faceAuthCurrentlyAllowed) ->
198                 faceAuthCurrentlyAllowed || errorStatus.isLockoutError()
199             }
200             .map { (status, _) ->
201                 when {
202                     status.isTimeoutError() -> {
203                         val deferredMessage = faceHelpMessageDeferralInteractor.getDeferredMessage()
204                         if (deferredMessage != null) {
205                             FaceMessage(deferredMessage.toString())
206                         } else {
207                             FaceTimeoutMessage(status.msg)
208                         }
209                     }
210                     status.isLockoutError() -> FaceLockoutMessage(status.msg)
211                     else -> FaceMessage(status.msg)
212                 }
213             }
214 
215     val faceMessage: Flow<FaceMessage> =
216         merge(
217             faceHelpMessage,
218             faceFailureMessage,
219             faceErrorMessage,
220         )
221 }
222