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