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.bouncer.domain.interactor
18 
19 import android.hardware.biometrics.BiometricFaceConstants
20 import android.hardware.biometrics.BiometricSourceType
21 import android.os.CountDownTimer
22 import com.android.keyguard.KeyguardSecurityModel
23 import com.android.keyguard.KeyguardSecurityModel.SecurityMode
24 import com.android.keyguard.KeyguardUpdateMonitor
25 import com.android.keyguard.KeyguardUpdateMonitorCallback
26 import com.android.systemui.Flags
27 import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
28 import com.android.systemui.biometrics.data.repository.FacePropertyRepository
29 import com.android.systemui.biometrics.shared.model.SensorStrength
30 import com.android.systemui.bouncer.data.repository.BouncerMessageRepository
31 import com.android.systemui.bouncer.shared.model.BouncerMessageModel
32 import com.android.systemui.bouncer.shared.model.BouncerMessageStrings
33 import com.android.systemui.bouncer.shared.model.Message
34 import com.android.systemui.dagger.SysUISingleton
35 import com.android.systemui.dagger.qualifiers.Application
36 import com.android.systemui.deviceentry.domain.interactor.DeviceEntryBiometricsAllowedInteractor
37 import com.android.systemui.flags.SystemPropertiesHelper
38 import com.android.systemui.keyguard.data.repository.BiometricSettingsRepository
39 import com.android.systemui.keyguard.data.repository.TrustRepository
40 import com.android.systemui.user.data.repository.UserRepository
41 import com.android.systemui.util.kotlin.Septuple
42 import com.android.systemui.util.kotlin.combine
43 import javax.inject.Inject
44 import kotlin.math.roundToInt
45 import kotlinx.coroutines.CoroutineScope
46 import kotlinx.coroutines.flow.Flow
47 import kotlinx.coroutines.flow.SharingStarted
48 import kotlinx.coroutines.flow.combine
49 import kotlinx.coroutines.flow.filterNotNull
50 import kotlinx.coroutines.flow.launchIn
51 import kotlinx.coroutines.flow.map
52 import kotlinx.coroutines.flow.onEach
53 import kotlinx.coroutines.flow.stateIn
54 
55 private const val SYS_BOOT_REASON_PROP = "sys.boot.reason.last"
56 private const val REBOOT_MAINLINE_UPDATE = "reboot,mainline_update"
57 private const val TAG = "BouncerMessageInteractor"
58 
59 /** Handles business logic for the primary bouncer message area. */
60 @SysUISingleton
61 class BouncerMessageInteractor
62 @Inject
63 constructor(
64     private val repository: BouncerMessageRepository,
65     private val userRepository: UserRepository,
66     private val countDownTimerUtil: CountDownTimerUtil,
67     updateMonitor: KeyguardUpdateMonitor,
68     trustRepository: TrustRepository,
69     biometricSettingsRepository: BiometricSettingsRepository,
70     private val systemPropertiesHelper: SystemPropertiesHelper,
71     primaryBouncerInteractor: PrimaryBouncerInteractor,
72     @Application private val applicationScope: CoroutineScope,
73     private val facePropertyRepository: FacePropertyRepository,
74     private val securityModel: KeyguardSecurityModel,
75     deviceEntryBiometricsAllowedInteractor: DeviceEntryBiometricsAllowedInteractor,
76 ) {
77 
78     private val isFingerprintAuthCurrentlyAllowedOnBouncer =
79         deviceEntryBiometricsAllowedInteractor.isFingerprintCurrentlyAllowedOnBouncer.stateIn(
80             applicationScope,
81             SharingStarted.Eagerly,
82             false,
83         )
84 
85     private val currentSecurityMode
86         get() = securityModel.getSecurityMode(currentUserId)
87 
88     private val currentUserId
89         get() = userRepository.getSelectedUserInfo().id
90 
91     private val kumCallback =
92         object : KeyguardUpdateMonitorCallback() {
93             override fun onBiometricAuthFailed(biometricSourceType: BiometricSourceType?) {
94                 // Only show the biometric failure messages if the biometric is NOT locked out.
95                 // If the biometric is locked out, rely on the lock out message to show
96                 // the lockout message & don't override it with the failure message.
97                 if (
98                     (biometricSourceType == BiometricSourceType.FACE &&
99                         deviceEntryBiometricsAllowedInteractor.isFaceLockedOut.value) ||
100                         (biometricSourceType == BiometricSourceType.FINGERPRINT &&
101                             deviceEntryBiometricsAllowedInteractor.isFingerprintLockedOut.value)
102                 ) {
103                     return
104                 }
105                 repository.setMessage(
106                     when (biometricSourceType) {
107                         BiometricSourceType.FINGERPRINT ->
108                             BouncerMessageStrings.incorrectFingerprintInput(
109                                     currentSecurityMode.toAuthModel()
110                                 )
111                                 .toMessage()
112                         BiometricSourceType.FACE ->
113                             BouncerMessageStrings.incorrectFaceInput(
114                                     currentSecurityMode.toAuthModel(),
115                                     isFingerprintAuthCurrentlyAllowedOnBouncer.value,
116                                 )
117                                 .toMessage()
118                         else ->
119                             BouncerMessageStrings.defaultMessage(
120                                     currentSecurityMode.toAuthModel(),
121                                     isFingerprintAuthCurrentlyAllowedOnBouncer.value,
122                                 )
123                                 .toMessage()
124                     },
125                     biometricSourceType,
126                 )
127             }
128 
129             override fun onBiometricAcquired(
130                 biometricSourceType: BiometricSourceType?,
131                 acquireInfo: Int,
132             ) {
133                 if (
134                     repository.getMessageSource() == BiometricSourceType.FACE &&
135                         acquireInfo == BiometricFaceConstants.FACE_ACQUIRED_START
136                 ) {
137                     repository.setMessage(defaultMessage)
138                 }
139             }
140 
141             override fun onBiometricAuthenticated(
142                 userId: Int,
143                 biometricSourceType: BiometricSourceType?,
144                 isStrongBiometric: Boolean,
145             ) {
146                 repository.setMessage(defaultMessage, biometricSourceType)
147             }
148         }
149 
150     private val isAnyBiometricsEnabledAndEnrolled =
151         biometricSettingsRepository.isFaceAuthEnrolledAndEnabled.or(
152             biometricSettingsRepository.isFingerprintEnrolledAndEnabled
153         )
154 
155     private val wasRebootedForMainlineUpdate
156         get() = systemPropertiesHelper.get(SYS_BOOT_REASON_PROP) == REBOOT_MAINLINE_UPDATE
157 
158     private val isFaceAuthClass3
159         get() = facePropertyRepository.sensorInfo.value?.strength == SensorStrength.STRONG
160 
161     private val initialBouncerMessage: Flow<BouncerMessageModel> =
162         combine(
163                 primaryBouncerInteractor.lastShownSecurityMode, // required to update defaultMessage
164                 biometricSettingsRepository.authenticationFlags,
165                 trustRepository.isCurrentUserTrustManaged,
166                 isAnyBiometricsEnabledAndEnrolled,
167                 deviceEntryBiometricsAllowedInteractor.isFingerprintLockedOut,
168                 deviceEntryBiometricsAllowedInteractor.isFaceLockedOut,
169                 isFingerprintAuthCurrentlyAllowedOnBouncer,
170                 ::Septuple,
171             )
172             .map { (_, flags, _, biometricsEnrolledAndEnabled, fpLockedOut, faceLockedOut, _) ->
173                 val isTrustUsuallyManaged = trustRepository.isCurrentUserTrustUsuallyManaged.value
174                 val trustOrBiometricsAvailable =
175                     (isTrustUsuallyManaged || biometricsEnrolledAndEnabled)
176                 return@map if (
177                     trustOrBiometricsAvailable && flags.isPrimaryAuthRequiredAfterReboot
178                 ) {
179                     if (wasRebootedForMainlineUpdate) {
180                         BouncerMessageStrings.authRequiredForMainlineUpdate(
181                                 currentSecurityMode.toAuthModel()
182                             )
183                             .toMessage()
184                     } else {
185                         BouncerMessageStrings.authRequiredAfterReboot(
186                                 currentSecurityMode.toAuthModel()
187                             )
188                             .toMessage()
189                     }
190                 } else if (trustOrBiometricsAvailable && flags.isPrimaryAuthRequiredAfterTimeout) {
191                     BouncerMessageStrings.authRequiredAfterPrimaryAuthTimeout(
192                             currentSecurityMode.toAuthModel()
193                         )
194                         .toMessage()
195                 } else if (flags.isPrimaryAuthRequiredAfterDpmLockdown) {
196                     BouncerMessageStrings.authRequiredAfterAdminLockdown(
197                             currentSecurityMode.toAuthModel()
198                         )
199                         .toMessage()
200                 } else if (
201                     trustOrBiometricsAvailable && flags.isPrimaryAuthRequiredForUnattendedUpdate
202                 ) {
203                     BouncerMessageStrings.authRequiredForUnattendedUpdate(
204                             currentSecurityMode.toAuthModel()
205                         )
206                         .toMessage()
207                 } else if (
208                     biometricSettingsRepository.isFingerprintEnrolledAndEnabled.value && fpLockedOut
209                 ) {
210                     BouncerMessageStrings.class3AuthLockedOut(currentSecurityMode.toAuthModel())
211                         .toMessage()
212                 } else if (
213                     biometricSettingsRepository.isFaceAuthEnrolledAndEnabled.value && faceLockedOut
214                 ) {
215                     if (isFaceAuthClass3) {
216                         BouncerMessageStrings.class3AuthLockedOut(currentSecurityMode.toAuthModel())
217                             .toMessage()
218                     } else {
219                         BouncerMessageStrings.faceLockedOut(
220                                 currentSecurityMode.toAuthModel(),
221                                 isFingerprintAuthCurrentlyAllowedOnBouncer.value,
222                             )
223                             .toMessage()
224                     }
225                 } else if (flags.isSomeAuthRequiredAfterAdaptiveAuthRequest) {
226                     BouncerMessageStrings.authRequiredAfterAdaptiveAuthRequest(
227                             currentSecurityMode.toAuthModel(),
228                             isFingerprintAuthCurrentlyAllowedOnBouncer.value,
229                         )
230                         .toMessage()
231                 } else if (
232                     trustOrBiometricsAvailable &&
233                         flags.strongerAuthRequiredAfterNonStrongBiometricsTimeout
234                 ) {
235                     BouncerMessageStrings.nonStrongAuthTimeout(
236                             currentSecurityMode.toAuthModel(),
237                             isFingerprintAuthCurrentlyAllowedOnBouncer.value,
238                         )
239                         .toMessage()
240                 } else if (isTrustUsuallyManaged && flags.someAuthRequiredAfterUserRequest) {
241                     BouncerMessageStrings.trustAgentDisabled(
242                             currentSecurityMode.toAuthModel(),
243                             isFingerprintAuthCurrentlyAllowedOnBouncer.value,
244                         )
245                         .toMessage()
246                 } else if (isTrustUsuallyManaged && flags.someAuthRequiredAfterTrustAgentExpired) {
247                     BouncerMessageStrings.trustAgentDisabled(
248                             currentSecurityMode.toAuthModel(),
249                             isFingerprintAuthCurrentlyAllowedOnBouncer.value,
250                         )
251                         .toMessage()
252                 } else if (trustOrBiometricsAvailable && flags.isInUserLockdown) {
253                     BouncerMessageStrings.authRequiredAfterUserLockdown(
254                             currentSecurityMode.toAuthModel()
255                         )
256                         .toMessage()
257                 } else {
258                     defaultMessage
259                 }
260             }
261 
262     fun onPrimaryAuthLockedOut(secondsBeforeLockoutReset: Long) {
263         if (!Flags.revampedBouncerMessages()) return
264 
265         val callback =
266             object : CountDownTimerCallback {
267                 override fun onFinish() {
268                     repository.setMessage(defaultMessage)
269                 }
270 
271                 override fun onTick(millisUntilFinished: Long) {
272                     val secondsRemaining = (millisUntilFinished / 1000.0).roundToInt()
273                     val message =
274                         BouncerMessageStrings.primaryAuthLockedOut(
275                                 currentSecurityMode.toAuthModel()
276                             )
277                             .toMessage()
278                     message.message?.animate = false
279                     message.message?.formatterArgs =
280                         mutableMapOf<String, Any>(Pair("count", secondsRemaining))
281                     repository.setMessage(message)
282                 }
283             }
284         countDownTimerUtil.startNewTimer(secondsBeforeLockoutReset * 1000, 1000, callback)
285     }
286 
287     fun onPrimaryAuthIncorrectAttempt() {
288         if (!Flags.revampedBouncerMessages()) return
289 
290         repository.setMessage(
291             BouncerMessageStrings.incorrectSecurityInput(
292                     currentSecurityMode.toAuthModel(),
293                     isFingerprintAuthCurrentlyAllowedOnBouncer.value,
294                 )
295                 .toMessage()
296         )
297     }
298 
299     fun setFingerprintAcquisitionMessage(value: String?) {
300         if (!Flags.revampedBouncerMessages()) return
301         repository.setMessage(
302             defaultMessage(
303                 currentSecurityMode,
304                 value,
305                 isFingerprintAuthCurrentlyAllowedOnBouncer.value,
306             ),
307             BiometricSourceType.FINGERPRINT,
308         )
309     }
310 
311     fun setUnlockToContinueMessage(value: String) {
312         if (!Flags.revampedBouncerMessages()) return
313         repository.setMessage(
314             defaultMessage(
315                 currentSecurityMode,
316                 value,
317                 isFingerprintAuthCurrentlyAllowedOnBouncer.value,
318             )
319         )
320     }
321 
322     fun setFaceAcquisitionMessage(value: String?) {
323         if (!Flags.revampedBouncerMessages()) return
324         repository.setMessage(
325             defaultMessage(
326                 currentSecurityMode,
327                 value,
328                 isFingerprintAuthCurrentlyAllowedOnBouncer.value,
329             ),
330             BiometricSourceType.FACE,
331         )
332     }
333 
334     fun setCustomMessage(value: String?) {
335         if (!Flags.revampedBouncerMessages()) return
336 
337         repository.setMessage(
338             defaultMessage(
339                 currentSecurityMode,
340                 value,
341                 isFingerprintAuthCurrentlyAllowedOnBouncer.value,
342             )
343         )
344     }
345 
346     private val defaultMessage: BouncerMessageModel
347         get() =
348             BouncerMessageStrings.defaultMessage(
349                     currentSecurityMode.toAuthModel(),
350                     isFingerprintAuthCurrentlyAllowedOnBouncer.value,
351                 )
352                 .toMessage()
353 
354     fun onPrimaryBouncerUserInput() {
355         if (!Flags.revampedBouncerMessages()) return
356         repository.setMessage(defaultMessage)
357     }
358 
359     val bouncerMessage = repository.bouncerMessage
360 
361     init {
362         updateMonitor.registerCallback(kumCallback)
363 
364         combine(primaryBouncerInteractor.isShowing, initialBouncerMessage) { showing, bouncerMessage
365                 ->
366                 if (showing) {
367                     bouncerMessage
368                 } else {
369                     null
370                 }
371             }
372             .filterNotNull()
373             .onEach { repository.setMessage(it) }
374             .launchIn(applicationScope)
375     }
376 }
377 
378 interface CountDownTimerCallback {
onFinishnull379     fun onFinish()
380 
381     fun onTick(millisUntilFinished: Long)
382 }
383 
384 @SysUISingleton
385 open class CountDownTimerUtil @Inject constructor() {
386 
387     /**
388      * Start a new count down timer that runs for [millisInFuture] with a tick every
389      * [millisInterval]
390      */
391     fun startNewTimer(
392         millisInFuture: Long,
393         millisInterval: Long,
394         callback: CountDownTimerCallback,
395     ): CountDownTimer {
396         return object : CountDownTimer(millisInFuture, millisInterval) {
397                 override fun onFinish() = callback.onFinish()
398 
399                 override fun onTick(millisUntilFinished: Long) =
400                     callback.onTick(millisUntilFinished)
401             }
402             .start()
403     }
404 }
405 
ornull406 private fun Flow<Boolean>.or(anotherFlow: Flow<Boolean>) =
407     this.combine(anotherFlow) { a, b -> a || b }
408 
defaultMessagenull409 private fun defaultMessage(
410     securityMode: SecurityMode,
411     secondaryMessage: String?,
412     fpAuthIsAllowed: Boolean,
413 ): BouncerMessageModel {
414     return BouncerMessageModel(
415         message =
416             Message(
417                 messageResId =
418                     BouncerMessageStrings.defaultMessage(
419                             securityMode.toAuthModel(),
420                             fpAuthIsAllowed,
421                         )
422                         .toMessage()
423                         .message
424                         ?.messageResId,
425                 animate = false,
426             ),
427         secondaryMessage = Message(message = secondaryMessage, animate = false),
428     )
429 }
430 
toMessagenull431 private fun Pair<Int, Int>.toMessage(): BouncerMessageModel {
432     return BouncerMessageModel(
433         message = Message(messageResId = this.first, animate = false),
434         secondaryMessage = Message(messageResId = this.second, animate = false),
435     )
436 }
437 
SecurityModenull438 private fun SecurityMode.toAuthModel(): AuthenticationMethodModel {
439     return when (this) {
440         SecurityMode.Invalid -> AuthenticationMethodModel.None
441         SecurityMode.None -> AuthenticationMethodModel.None
442         SecurityMode.Pattern -> AuthenticationMethodModel.Pattern
443         SecurityMode.Password -> AuthenticationMethodModel.Password
444         SecurityMode.PIN -> AuthenticationMethodModel.Pin
445         SecurityMode.SimPin -> AuthenticationMethodModel.Sim
446         SecurityMode.SimPuk -> AuthenticationMethodModel.Sim
447     }
448 }
449