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.bouncer.data.repository
18 
19 import android.hardware.biometrics.BiometricSourceType
20 import android.hardware.biometrics.BiometricSourceType.FACE
21 import android.hardware.biometrics.BiometricSourceType.FINGERPRINT
22 import com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_DEVICE_ADMIN
23 import com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_FACE_LOCKED_OUT
24 import com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_FINGERPRINT_LOCKED_OUT
25 import com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_INCORRECT_FACE_INPUT
26 import com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_INCORRECT_FINGERPRINT_INPUT
27 import com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_NONE
28 import com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_NON_STRONG_BIOMETRIC_TIMEOUT
29 import com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_PREPARE_FOR_UPDATE
30 import com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_RESTART
31 import com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_RESTART_FOR_MAINLINE_UPDATE
32 import com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_TIMEOUT
33 import com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_TRUSTAGENT_EXPIRED
34 import com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_USER_REQUEST
35 import com.android.keyguard.KeyguardUpdateMonitor
36 import com.android.keyguard.KeyguardUpdateMonitorCallback
37 import com.android.systemui.bouncer.data.factory.BouncerMessageFactory
38 import com.android.systemui.bouncer.shared.model.BouncerMessageModel
39 import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging
40 import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
41 import com.android.systemui.dagger.SysUISingleton
42 import com.android.systemui.flags.SystemPropertiesHelper
43 import com.android.systemui.keyguard.data.repository.BiometricSettingsRepository
44 import com.android.systemui.keyguard.data.repository.DeviceEntryFingerprintAuthRepository
45 import com.android.systemui.keyguard.data.repository.TrustRepository
46 import com.android.systemui.user.data.repository.UserRepository
47 import javax.inject.Inject
48 import kotlinx.coroutines.channels.awaitClose
49 import kotlinx.coroutines.flow.Flow
50 import kotlinx.coroutines.flow.MutableStateFlow
51 import kotlinx.coroutines.flow.combine
52 import kotlinx.coroutines.flow.distinctUntilChanged
53 import kotlinx.coroutines.flow.map
54 import kotlinx.coroutines.flow.onStart
55 
56 /** Provide different sources of messages that needs to be shown on the bouncer. */
57 interface BouncerMessageRepository {
58     /**
59      * Messages that are shown in response to the incorrect security attempts on the bouncer and
60      * primary authentication method being locked out, along with countdown messages before primary
61      * auth is active again.
62      */
63     val primaryAuthMessage: Flow<BouncerMessageModel?>
64 
65     /**
66      * Help messages that are shown to the user on how to successfully perform authentication using
67      * face.
68      */
69     val faceAcquisitionMessage: Flow<BouncerMessageModel?>
70 
71     /**
72      * Help messages that are shown to the user on how to successfully perform authentication using
73      * fingerprint.
74      */
75     val fingerprintAcquisitionMessage: Flow<BouncerMessageModel?>
76 
77     /** Custom message that is displayed when the bouncer is being shown to launch an app. */
78     val customMessage: Flow<BouncerMessageModel?>
79 
80     /**
81      * Messages that are shown in response to biometric authentication attempts through face or
82      * fingerprint.
83      */
84     val biometricAuthMessage: Flow<BouncerMessageModel?>
85 
86     /** Messages that are shown when certain auth flags are set. */
87     val authFlagsMessage: Flow<BouncerMessageModel?>
88 
89     /** Messages that are show after biometrics are locked out temporarily or permanently */
90     val biometricLockedOutMessage: Flow<BouncerMessageModel?>
91 
92     /** Set the value for [primaryAuthMessage] */
93     fun setPrimaryAuthMessage(value: BouncerMessageModel?)
94 
95     /** Set the value for [faceAcquisitionMessage] */
96     fun setFaceAcquisitionMessage(value: BouncerMessageModel?)
97     /** Set the value for [fingerprintAcquisitionMessage] */
98     fun setFingerprintAcquisitionMessage(value: BouncerMessageModel?)
99 
100     /** Set the value for [customMessage] */
101     fun setCustomMessage(value: BouncerMessageModel?)
102 
103     /**
104      * Clear any previously set messages for [primaryAuthMessage], [faceAcquisitionMessage],
105      * [fingerprintAcquisitionMessage] & [customMessage]
106      */
107     fun clearMessage()
108 }
109 
110 private const val SYS_BOOT_REASON_PROP = "sys.boot.reason.last"
111 private const val REBOOT_MAINLINE_UPDATE = "reboot,mainline_update"
112 
113 @SysUISingleton
114 class BouncerMessageRepositoryImpl
115 @Inject
116 constructor(
117     trustRepository: TrustRepository,
118     biometricSettingsRepository: BiometricSettingsRepository,
119     updateMonitor: KeyguardUpdateMonitor,
120     private val bouncerMessageFactory: BouncerMessageFactory,
121     private val userRepository: UserRepository,
122     private val systemPropertiesHelper: SystemPropertiesHelper,
123     fingerprintAuthRepository: DeviceEntryFingerprintAuthRepository,
124 ) : BouncerMessageRepository {
125 
126     private val isAnyBiometricsEnabledAndEnrolled =
127         or(
128             biometricSettingsRepository.isFaceAuthEnrolledAndEnabled,
129             biometricSettingsRepository.isFingerprintEnrolledAndEnabled,
130         )
131 
132     private val wasRebootedForMainlineUpdate
133         get() = systemPropertiesHelper.get(SYS_BOOT_REASON_PROP) == REBOOT_MAINLINE_UPDATE
134 
135     private val authFlagsBasedPromptReason: Flow<Int> =
136         combine(
137                 biometricSettingsRepository.authenticationFlags,
138                 trustRepository.isCurrentUserTrustManaged,
139                 isAnyBiometricsEnabledAndEnrolled,
140                 ::Triple
141             )
142             .map { (flags, isTrustManaged, biometricsEnrolledAndEnabled) ->
143                 val trustOrBiometricsAvailable = (isTrustManaged || biometricsEnrolledAndEnabled)
144                 return@map if (
145                     trustOrBiometricsAvailable && flags.isPrimaryAuthRequiredAfterReboot
146                 ) {
147                     if (wasRebootedForMainlineUpdate) PROMPT_REASON_RESTART_FOR_MAINLINE_UPDATE
148                     else PROMPT_REASON_RESTART
149                 } else if (trustOrBiometricsAvailable && flags.isPrimaryAuthRequiredAfterTimeout) {
150                     PROMPT_REASON_TIMEOUT
151                 } else if (flags.isPrimaryAuthRequiredAfterDpmLockdown) {
152                     PROMPT_REASON_DEVICE_ADMIN
153                 } else if (isTrustManaged && flags.someAuthRequiredAfterUserRequest) {
154                     PROMPT_REASON_TRUSTAGENT_EXPIRED
155                 } else if (isTrustManaged && flags.someAuthRequiredAfterTrustAgentExpired) {
156                     PROMPT_REASON_TRUSTAGENT_EXPIRED
157                 } else if (trustOrBiometricsAvailable && flags.isInUserLockdown) {
158                     PROMPT_REASON_USER_REQUEST
159                 } else if (
160                     trustOrBiometricsAvailable && flags.primaryAuthRequiredForUnattendedUpdate
161                 ) {
162                     PROMPT_REASON_PREPARE_FOR_UPDATE
163                 } else if (
164                     trustOrBiometricsAvailable &&
165                         flags.strongerAuthRequiredAfterNonStrongBiometricsTimeout
166                 ) {
167                     PROMPT_REASON_NON_STRONG_BIOMETRIC_TIMEOUT
168                 } else {
169                     PROMPT_REASON_NONE
170                 }
171             }
172 
173     private val biometricAuthReason: Flow<Int> =
174         conflatedCallbackFlow {
175                 val callback =
176                     object : KeyguardUpdateMonitorCallback() {
177                         override fun onBiometricAuthFailed(
178                             biometricSourceType: BiometricSourceType?
179                         ) {
180                             val promptReason =
181                                 if (biometricSourceType == FINGERPRINT)
182                                     PROMPT_REASON_INCORRECT_FINGERPRINT_INPUT
183                                 else if (
184                                     biometricSourceType == FACE && !updateMonitor.isFaceLockedOut
185                                 ) {
186                                     PROMPT_REASON_INCORRECT_FACE_INPUT
187                                 } else PROMPT_REASON_NONE
188                             trySendWithFailureLogging(promptReason, TAG, "onBiometricAuthFailed")
189                         }
190 
191                         override fun onBiometricsCleared() {
192                             trySendWithFailureLogging(
193                                 PROMPT_REASON_NONE,
194                                 TAG,
195                                 "onBiometricsCleared"
196                             )
197                         }
198 
199                         override fun onBiometricAcquired(
200                             biometricSourceType: BiometricSourceType?,
201                             acquireInfo: Int
202                         ) {
203                             trySendWithFailureLogging(
204                                 PROMPT_REASON_NONE,
205                                 TAG,
206                                 "clearBiometricPrompt for new auth session."
207                             )
208                         }
209 
210                         override fun onBiometricAuthenticated(
211                             userId: Int,
212                             biometricSourceType: BiometricSourceType?,
213                             isStrongBiometric: Boolean
214                         ) {
215                             trySendWithFailureLogging(
216                                 PROMPT_REASON_NONE,
217                                 TAG,
218                                 "onBiometricAuthenticated"
219                             )
220                         }
221                     }
222                 updateMonitor.registerCallback(callback)
223                 awaitClose { updateMonitor.removeCallback(callback) }
224             }
225             .distinctUntilChanged()
226 
227     private val _primaryAuthMessage = MutableStateFlow<BouncerMessageModel?>(null)
228     override val primaryAuthMessage: Flow<BouncerMessageModel?> = _primaryAuthMessage
229 
230     private val _faceAcquisitionMessage = MutableStateFlow<BouncerMessageModel?>(null)
231     override val faceAcquisitionMessage: Flow<BouncerMessageModel?> = _faceAcquisitionMessage
232 
233     private val _fingerprintAcquisitionMessage = MutableStateFlow<BouncerMessageModel?>(null)
234     override val fingerprintAcquisitionMessage: Flow<BouncerMessageModel?> =
235         _fingerprintAcquisitionMessage
236 
237     private val _customMessage = MutableStateFlow<BouncerMessageModel?>(null)
238     override val customMessage: Flow<BouncerMessageModel?> = _customMessage
239 
240     override val biometricAuthMessage: Flow<BouncerMessageModel?> =
241         biometricAuthReason
242             .map {
243                 if (it == PROMPT_REASON_NONE) null
244                 else
245                     bouncerMessageFactory.createFromPromptReason(
246                         it,
247                         userRepository.getSelectedUserInfo().id
248                     )
249             }
250             .onStart { emit(null) }
251             .distinctUntilChanged()
252 
253     override val authFlagsMessage: Flow<BouncerMessageModel?> =
254         authFlagsBasedPromptReason
255             .map {
256                 if (it == PROMPT_REASON_NONE) null
257                 else
258                     bouncerMessageFactory.createFromPromptReason(
259                         it,
260                         userRepository.getSelectedUserInfo().id
261                     )
262             }
263             .onStart { emit(null) }
264             .distinctUntilChanged()
265 
266     // TODO (b/262838215): Replace with DeviceEntryFaceAuthRepository when the new face auth system
267     // has been launched.
268     private val faceLockedOut: Flow<Boolean> = conflatedCallbackFlow {
269         val callback =
270             object : KeyguardUpdateMonitorCallback() {
271                 override fun onLockedOutStateChanged(biometricSourceType: BiometricSourceType?) {
272                     if (biometricSourceType == FACE) {
273                         trySendWithFailureLogging(
274                             updateMonitor.isFaceLockedOut,
275                             TAG,
276                             "face lock out state changed."
277                         )
278                     }
279                 }
280             }
281         updateMonitor.registerCallback(callback)
282         trySendWithFailureLogging(updateMonitor.isFaceLockedOut, TAG, "face lockout initial value")
283         awaitClose { updateMonitor.removeCallback(callback) }
284     }
285 
286     override val biometricLockedOutMessage: Flow<BouncerMessageModel?> =
287         combine(fingerprintAuthRepository.isLockedOut, faceLockedOut) { fp, face ->
288             return@combine if (fp) {
289                 bouncerMessageFactory.createFromPromptReason(
290                     PROMPT_REASON_FINGERPRINT_LOCKED_OUT,
291                     userRepository.getSelectedUserInfo().id
292                 )
293             } else if (face) {
294                 bouncerMessageFactory.createFromPromptReason(
295                     PROMPT_REASON_FACE_LOCKED_OUT,
296                     userRepository.getSelectedUserInfo().id
297                 )
298             } else null
299         }
300 
301     override fun setPrimaryAuthMessage(value: BouncerMessageModel?) {
302         _primaryAuthMessage.value = value
303     }
304 
305     override fun setFaceAcquisitionMessage(value: BouncerMessageModel?) {
306         _faceAcquisitionMessage.value = value
307     }
308 
309     override fun setFingerprintAcquisitionMessage(value: BouncerMessageModel?) {
310         _fingerprintAcquisitionMessage.value = value
311     }
312 
313     override fun setCustomMessage(value: BouncerMessageModel?) {
314         _customMessage.value = value
315     }
316 
317     override fun clearMessage() {
318         _fingerprintAcquisitionMessage.value = null
319         _faceAcquisitionMessage.value = null
320         _primaryAuthMessage.value = null
321         _customMessage.value = null
322     }
323 
324     companion object {
325         const val TAG = "BouncerDetailedMessageRepository"
326     }
327 }
328 
329 private fun or(flow: Flow<Boolean>, anotherFlow: Flow<Boolean>) =
330     flow.combine(anotherFlow) { a, b -> a || b }
331