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.authentication.data.repository 18 19 import android.annotation.UserIdInt 20 import android.app.admin.DevicePolicyManager 21 import android.content.IntentFilter 22 import android.os.UserHandle 23 import com.android.app.tracing.coroutines.launchTraced as launch 24 import com.android.internal.widget.LockPatternUtils 25 import com.android.internal.widget.LockscreenCredential 26 import com.android.keyguard.KeyguardSecurityModel 27 import com.android.systemui.authentication.shared.model.AuthenticationMethodModel 28 import com.android.systemui.authentication.shared.model.AuthenticationMethodModel.None 29 import com.android.systemui.authentication.shared.model.AuthenticationMethodModel.Password 30 import com.android.systemui.authentication.shared.model.AuthenticationMethodModel.Pattern 31 import com.android.systemui.authentication.shared.model.AuthenticationMethodModel.Pin 32 import com.android.systemui.authentication.shared.model.AuthenticationMethodModel.Sim 33 import com.android.systemui.authentication.shared.model.AuthenticationResultModel 34 import com.android.systemui.bouncer.shared.flag.ComposeBouncerFlags 35 import com.android.systemui.broadcast.BroadcastDispatcher 36 import com.android.systemui.dagger.SysUISingleton 37 import com.android.systemui.dagger.qualifiers.Application 38 import com.android.systemui.dagger.qualifiers.Background 39 import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionsRepository 40 import com.android.systemui.user.data.repository.UserRepository 41 import com.android.systemui.util.kotlin.onSubscriberAdded 42 import com.android.systemui.util.time.SystemClock 43 import dagger.Binds 44 import dagger.Module 45 import java.util.function.Function 46 import javax.inject.Inject 47 import kotlinx.coroutines.CoroutineDispatcher 48 import kotlinx.coroutines.CoroutineScope 49 import kotlinx.coroutines.flow.Flow 50 import kotlinx.coroutines.flow.MutableStateFlow 51 import kotlinx.coroutines.flow.StateFlow 52 import kotlinx.coroutines.flow.asStateFlow 53 import kotlinx.coroutines.flow.combine 54 import kotlinx.coroutines.flow.distinctUntilChanged 55 import kotlinx.coroutines.flow.flatMapLatest 56 import kotlinx.coroutines.flow.map 57 import kotlinx.coroutines.flow.onStart 58 import kotlinx.coroutines.withContext 59 60 /** Defines interface for classes that can access authentication-related application state. */ 61 interface AuthenticationRepository { 62 /** 63 * The exact length a PIN should be for us to enable PIN length hinting. 64 * 65 * A PIN that's shorter or longer than this is not eligible for the UI to render hints showing 66 * how many digits the current PIN is, even if [isAutoConfirmFeatureEnabled] is enabled. 67 * 68 * Note that PIN length hinting is only available if the PIN auto confirmation feature is 69 * available. 70 */ 71 val hintedPinLength: Int 72 73 /** Whether the pattern should be visible for the currently-selected user. */ 74 val isPatternVisible: StateFlow<Boolean> 75 76 /** 77 * Whether the auto confirm feature is enabled for the currently-selected user. 78 * 79 * Note that the length of the PIN is also important to take into consideration, please see 80 * [hintedPinLength]. 81 */ 82 val isAutoConfirmFeatureEnabled: StateFlow<Boolean> 83 84 /** 85 * The number of failed authentication attempts for the selected user since their last 86 * successful authentication. 87 */ 88 val failedAuthenticationAttempts: StateFlow<Int> 89 90 /** 91 * Timestamp for when the current lockout (aka "throttling") will end, allowing the user to 92 * attempt authentication again. Returns `null` if no lockout is active. 93 * 94 * Note that the value is in milliseconds and matches [SystemClock.elapsedRealtime]. 95 * 96 * Also note that the value may change when the selected user is changed. 97 */ 98 val lockoutEndTimestamp: Long? 99 100 /** 101 * Whether lockout has occurred at least once since the last successful authentication of any 102 * user. 103 */ 104 val hasLockoutOccurred: StateFlow<Boolean> 105 106 /** 107 * The currently-configured authentication method. This determines how the authentication 108 * challenge needs to be completed in order to unlock an otherwise locked device. 109 * 110 * Note: there may be other ways to unlock the device that "bypass" the need for this 111 * authentication challenge (notably, biometrics like fingerprint or face unlock). 112 * 113 * Note: by design, this is a [Flow] and not a [StateFlow]; a consumer who wishes to get a 114 * snapshot of the current authentication method without establishing a collector of the flow 115 * can do so by invoking [getAuthenticationMethod]. 116 */ 117 val authenticationMethod: Flow<AuthenticationMethodModel> 118 119 /** The minimal length of a pattern. */ 120 val minPatternLength: Int 121 122 /** The minimal length of a password. */ 123 val minPasswordLength: Int 124 125 /** Whether the "enhanced PIN privacy" setting is enabled for the current user. */ 126 val isPinEnhancedPrivacyEnabled: StateFlow<Boolean> 127 128 /** 129 * Checks the given [LockscreenCredential] to see if it's correct, returning an 130 * [AuthenticationResultModel] representing what happened. 131 */ 132 suspend fun checkCredential(credential: LockscreenCredential): AuthenticationResultModel 133 134 /** 135 * Returns the currently-configured authentication method. This determines how the 136 * authentication challenge needs to be completed in order to unlock an otherwise locked device. 137 * 138 * Note: there may be other ways to unlock the device that "bypass" the need for this 139 * authentication challenge (notably, biometrics like fingerprint or face unlock). 140 * 141 * Note: by design, this is offered as a convenience method alongside [authenticationMethod]. 142 * The flow should be used for code that wishes to stay up-to-date its logic as the 143 * authentication changes over time and this method should be used for simple code that only 144 * needs to check the current value. 145 */ 146 suspend fun getAuthenticationMethod(): AuthenticationMethodModel 147 148 /** Returns the length of the PIN or `0` if the current auth method is not PIN. */ 149 suspend fun getPinLength(): Int 150 151 /** Reports an authentication attempt. */ 152 suspend fun reportAuthenticationAttempt(isSuccessful: Boolean) 153 154 /** Reports that the user has entered a temporary device lockout (throttling). */ 155 suspend fun reportLockoutStarted(durationMs: Int) 156 157 /** 158 * Returns the current maximum number of login attempts that are allowed before the device or 159 * profile is wiped. 160 * 161 * If there is no wipe policy, returns `0`. 162 * 163 * @see [DevicePolicyManager.getMaximumFailedPasswordsForWipe] 164 */ 165 suspend fun getMaxFailedUnlockAttemptsForWipe(): Int 166 167 /** 168 * Returns the user that will be wiped first when too many failed attempts are made to unlock 169 * the device by the selected user. That user is either the same as the current user ID or 170 * belongs to the same profile group. 171 * 172 * When there is no such policy, returns [UserHandle.USER_NULL]. E.g. managed profile user may 173 * be wiped as a result of failed primary profile password attempts when using unified 174 * challenge. Primary user may be wiped as a result of failed password attempts on the managed 175 * profile of an organization-owned device. 176 */ 177 @UserIdInt suspend fun getProfileWithMinFailedUnlockAttemptsForWipe(): Int 178 179 /** 180 * Returns the device policy enforced maximum time to lock the device, in milliseconds. When the 181 * device goes to sleep, this is the maximum time the device policy allows to wait before 182 * locking the device, despite what the user setting might be set to. 183 */ 184 suspend fun getMaximumTimeToLock(): Long 185 186 /** Returns `true` if the power button should instantly lock the device, `false` otherwise. */ 187 suspend fun getPowerButtonInstantlyLocks(): Boolean 188 } 189 190 @SysUISingleton 191 class AuthenticationRepositoryImpl 192 @Inject 193 constructor( 194 @Application private val applicationScope: CoroutineScope, 195 @Background private val backgroundDispatcher: CoroutineDispatcher, 196 private val clock: SystemClock, 197 private val getSecurityMode: Function<Int, KeyguardSecurityModel.SecurityMode>, 198 private val userRepository: UserRepository, 199 private val lockPatternUtils: LockPatternUtils, 200 private val devicePolicyManager: DevicePolicyManager, 201 broadcastDispatcher: BroadcastDispatcher, 202 mobileConnectionsRepository: MobileConnectionsRepository, 203 ) : AuthenticationRepository { 204 205 override val hintedPinLength: Int = 6 206 207 override val isPatternVisible: StateFlow<Boolean> = 208 refreshingFlow( 209 initialValue = true, 210 getFreshValue = lockPatternUtils::isVisiblePatternEnabled, 211 ) 212 213 override val isAutoConfirmFeatureEnabled: StateFlow<Boolean> = 214 refreshingFlow( 215 initialValue = 216 lockPatternUtils.isAutoPinConfirmEnabled(userRepository.getSelectedUserInfo().id), 217 getFreshValue = lockPatternUtils::isAutoPinConfirmEnabled, 218 ) 219 220 override val authenticationMethod: Flow<AuthenticationMethodModel> = 221 combine(userRepository.selectedUserInfo, mobileConnectionsRepository.isAnySimSecure) { selectedUserInfonull222 selectedUserInfo, 223 _ -> 224 selectedUserInfo.id 225 } 226 .flatMapLatest { selectedUserId -> 227 broadcastDispatcher 228 .broadcastFlow( 229 filter = 230 IntentFilter( 231 DevicePolicyManager.ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED 232 ), 233 user = UserHandle.of(selectedUserId), 234 ) 235 .onStart { emit(Unit) } 236 .map { selectedUserId } 237 } 238 .map(::getAuthenticationMethod) 239 .distinctUntilChanged() 240 241 override val minPatternLength: Int = LockPatternUtils.MIN_LOCK_PATTERN_SIZE 242 243 override val minPasswordLength: Int = LockPatternUtils.MIN_LOCK_PASSWORD_SIZE 244 245 override val isPinEnhancedPrivacyEnabled: StateFlow<Boolean> = 246 refreshingFlow( 247 initialValue = true, userIdnull248 getFreshValue = { userId -> lockPatternUtils.isPinEnhancedPrivacyEnabled(userId) }, 249 ) 250 251 private val _failedAuthenticationAttempts = MutableStateFlow(0) 252 override val failedAuthenticationAttempts: StateFlow<Int> = 253 _failedAuthenticationAttempts.asStateFlow() 254 255 override val lockoutEndTimestamp: Long? 256 get() = <lambda>null257 lockPatternUtils.getLockoutAttemptDeadline(selectedUserId).takeIf { 258 clock.elapsedRealtime() < it 259 } 260 261 private val _hasLockoutOccurred = MutableStateFlow(false) 262 override val hasLockoutOccurred: StateFlow<Boolean> = _hasLockoutOccurred.asStateFlow() 263 264 init { 265 if (ComposeBouncerFlags.isComposeBouncerOrSceneContainerEnabled()) { 266 // Hydrate failedAuthenticationAttempts initially and whenever the selected user 267 // changes. <lambda>null268 applicationScope.launch { 269 userRepository.selectedUserInfo.collect { 270 _failedAuthenticationAttempts.value = getFailedAuthenticationAttemptCount() 271 } 272 } 273 } 274 } 275 checkCredentialnull276 override suspend fun checkCredential( 277 credential: LockscreenCredential 278 ): AuthenticationResultModel { 279 return withContext(backgroundDispatcher) { 280 try { 281 val matched = lockPatternUtils.checkCredential(credential, selectedUserId) {} 282 AuthenticationResultModel(isSuccessful = matched, lockoutDurationMs = 0) 283 } catch (ex: LockPatternUtils.RequestThrottledException) { 284 AuthenticationResultModel(isSuccessful = false, lockoutDurationMs = ex.timeoutMs) 285 } 286 } 287 } 288 getAuthenticationMethodnull289 override suspend fun getAuthenticationMethod(): AuthenticationMethodModel = 290 getAuthenticationMethod(selectedUserId) 291 292 override suspend fun getPinLength(): Int { 293 return withContext(backgroundDispatcher) { lockPatternUtils.getPinLength(selectedUserId) } 294 } 295 reportAuthenticationAttemptnull296 override suspend fun reportAuthenticationAttempt(isSuccessful: Boolean) { 297 withContext(backgroundDispatcher) { 298 if (isSuccessful) { 299 lockPatternUtils.userPresent(selectedUserId) 300 lockPatternUtils.reportSuccessfulPasswordAttempt(selectedUserId) 301 _hasLockoutOccurred.value = false 302 } else { 303 lockPatternUtils.reportFailedPasswordAttempt(selectedUserId) 304 } 305 _failedAuthenticationAttempts.value = getFailedAuthenticationAttemptCount() 306 } 307 } 308 reportLockoutStartednull309 override suspend fun reportLockoutStarted(durationMs: Int) { 310 lockPatternUtils.setLockoutAttemptDeadline(selectedUserId, durationMs) 311 withContext(backgroundDispatcher) { 312 lockPatternUtils.reportPasswordLockout(durationMs, selectedUserId) 313 } 314 _hasLockoutOccurred.value = true 315 } 316 getFailedAuthenticationAttemptCountnull317 private suspend fun getFailedAuthenticationAttemptCount(): Int { 318 return withContext(backgroundDispatcher) { 319 lockPatternUtils.getCurrentFailedPasswordAttempts(selectedUserId) 320 } 321 } 322 getMaxFailedUnlockAttemptsForWipenull323 override suspend fun getMaxFailedUnlockAttemptsForWipe(): Int { 324 return withContext(backgroundDispatcher) { 325 lockPatternUtils.getMaximumFailedPasswordsForWipe(selectedUserId) 326 } 327 } 328 getProfileWithMinFailedUnlockAttemptsForWipenull329 override suspend fun getProfileWithMinFailedUnlockAttemptsForWipe(): Int { 330 return withContext(backgroundDispatcher) { 331 devicePolicyManager.getProfileWithMinimumFailedPasswordsForWipe(selectedUserId) 332 } 333 } 334 getMaximumTimeToLocknull335 override suspend fun getMaximumTimeToLock(): Long { 336 return withContext(backgroundDispatcher) { 337 devicePolicyManager.getMaximumTimeToLock(/* admin= */ null, selectedUserId) 338 } 339 } 340 341 /** Returns `true` if the power button should instantly lock the device, `false` otherwise. */ getPowerButtonInstantlyLocksnull342 override suspend fun getPowerButtonInstantlyLocks(): Boolean { 343 return withContext(backgroundDispatcher) { 344 lockPatternUtils.getPowerButtonInstantlyLocks(selectedUserId) 345 } 346 } 347 348 private val selectedUserId: Int 349 @UserIdInt get() = userRepository.getSelectedUserInfo().id 350 351 /** 352 * Returns a [StateFlow] that's automatically kept fresh. The passed-in [getFreshValue] is 353 * invoked on a background thread every time the selected user is changed and every time a new 354 * downstream subscriber is added to the flow. 355 * 356 * Initially, the flow will emit [initialValue] while it refreshes itself in the background by 357 * invoking the [getFreshValue] function and emitting the fresh value when that's done. 358 * 359 * Every time the selected user is changed, the flow will re-invoke [getFreshValue] and emit the 360 * new value. 361 * 362 * Every time a new downstream subscriber is added to the flow it first receives the latest 363 * cached value that's either the [initialValue] or the latest previously fetched value. In 364 * addition, adding a new downstream subscriber also triggers another [getFreshValue] call and a 365 * subsequent emission of that newest value. 366 */ refreshingFlownull367 private fun <T> refreshingFlow( 368 initialValue: T, 369 getFreshValue: suspend (selectedUserId: Int) -> T, 370 ): StateFlow<T> { 371 val flow = MutableStateFlow(initialValue) 372 applicationScope.launch { 373 combine( 374 // Emits a value initially and every time the selected user is changed. 375 userRepository.selectedUserInfo.map { it.id }.distinctUntilChanged(), 376 // Emits a value only when the number of downstream subscribers of this flow 377 // increases. 378 flow.onSubscriberAdded(), 379 ) { selectedUserId, _ -> 380 selectedUserId 381 } 382 .collect { selectedUserId -> 383 flow.value = withContext(backgroundDispatcher) { getFreshValue(selectedUserId) } 384 } 385 } 386 387 return flow.asStateFlow() 388 } 389 390 /** Returns the authentication method for the given user ID. */ getAuthenticationMethodnull391 private suspend fun getAuthenticationMethod(@UserIdInt userId: Int): AuthenticationMethodModel { 392 return withContext(backgroundDispatcher) { 393 when (getSecurityMode.apply(userId)) { 394 KeyguardSecurityModel.SecurityMode.PIN -> Pin 395 KeyguardSecurityModel.SecurityMode.SimPin, 396 KeyguardSecurityModel.SecurityMode.SimPuk -> Sim 397 KeyguardSecurityModel.SecurityMode.Password -> Password 398 KeyguardSecurityModel.SecurityMode.Pattern -> Pattern 399 KeyguardSecurityModel.SecurityMode.None -> None 400 KeyguardSecurityModel.SecurityMode.Invalid -> error("Invalid security mode!") 401 } 402 } 403 } 404 } 405 406 @Module 407 interface AuthenticationRepositoryModule { repositorynull408 @Binds fun repository(impl: AuthenticationRepositoryImpl): AuthenticationRepository 409 } 410