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