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