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