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