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 @file:OptIn(ExperimentalCoroutinesApi::class) 18 19 package com.android.systemui.authentication.data.repository 20 21 import android.app.admin.DevicePolicyManager 22 import android.content.IntentFilter 23 import android.os.UserHandle 24 import com.android.internal.widget.LockPatternChecker 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.data.model.AuthenticationMethodModel 29 import com.android.systemui.authentication.shared.model.AuthenticationResultModel 30 import com.android.systemui.authentication.shared.model.AuthenticationThrottlingModel 31 import com.android.systemui.broadcast.BroadcastDispatcher 32 import com.android.systemui.dagger.qualifiers.Application 33 import com.android.systemui.dagger.qualifiers.Background 34 import com.android.systemui.keyguard.data.repository.KeyguardRepository 35 import com.android.systemui.user.data.repository.UserRepository 36 import com.android.systemui.util.kotlin.pairwise 37 import com.android.systemui.util.time.SystemClock 38 import dagger.Binds 39 import dagger.Module 40 import java.util.function.Function 41 import javax.inject.Inject 42 import kotlin.coroutines.resume 43 import kotlin.coroutines.suspendCoroutine 44 import kotlinx.coroutines.CoroutineDispatcher 45 import kotlinx.coroutines.CoroutineScope 46 import kotlinx.coroutines.ExperimentalCoroutinesApi 47 import kotlinx.coroutines.flow.Flow 48 import kotlinx.coroutines.flow.MutableStateFlow 49 import kotlinx.coroutines.flow.StateFlow 50 import kotlinx.coroutines.flow.asStateFlow 51 import kotlinx.coroutines.flow.combine 52 import kotlinx.coroutines.flow.distinctUntilChanged 53 import kotlinx.coroutines.flow.filter 54 import kotlinx.coroutines.flow.flatMapLatest 55 import kotlinx.coroutines.flow.map 56 import kotlinx.coroutines.flow.onStart 57 import kotlinx.coroutines.launch 58 import kotlinx.coroutines.withContext 59 60 /** Defines interface for classes that can access authentication-related application state. */ 61 interface AuthenticationRepository { 62 63 /** 64 * Whether the device is unlocked. 65 * 66 * A device that is not yet unlocked requires unlocking by completing an authentication 67 * challenge according to the current authentication method, unless in cases when the current 68 * authentication method is not "secure" (for example, None); in such cases, the value of this 69 * flow will always be `true`, even if the lockscreen is showing and still needs to be dismissed 70 * by the user to proceed. 71 */ 72 val isUnlocked: StateFlow<Boolean> 73 74 /** 75 * Whether the auto confirm feature is enabled for the currently-selected user. 76 * 77 * Note that the length of the PIN is also important to take into consideration, please see 78 * [hintedPinLength]. 79 */ 80 val isAutoConfirmEnabled: StateFlow<Boolean> 81 82 /** 83 * The exact length a PIN should be for us to enable PIN length hinting. 84 * 85 * A PIN that's shorter or longer than this is not eligible for the UI to render hints showing 86 * how many digits the current PIN is, even if [isAutoConfirmEnabled] is enabled. 87 * 88 * Note that PIN length hinting is only available if the PIN auto confirmation feature is 89 * available. 90 */ 91 val hintedPinLength: Int 92 93 /** Whether the pattern should be visible for the currently-selected user. */ 94 val isPatternVisible: StateFlow<Boolean> 95 96 /** The current throttling state, as cached via [setThrottling]. */ 97 val throttling: StateFlow<AuthenticationThrottlingModel> 98 99 /** 100 * The currently-configured authentication method. This determines how the authentication 101 * challenge needs to be completed in order to unlock an otherwise locked device. 102 * 103 * Note: there may be other ways to unlock the device that "bypass" the need for this 104 * authentication challenge (notably, biometrics like fingerprint or face unlock). 105 * 106 * Note: by design, this is a [Flow] and not a [StateFlow]; a consumer who wishes to get a 107 * snapshot of the current authentication method without establishing a collector of the flow 108 * can do so by invoking [getAuthenticationMethod]. 109 */ 110 val authenticationMethod: Flow<AuthenticationMethodModel> 111 112 /** 113 * Returns the currently-configured authentication method. This determines how the 114 * authentication challenge needs to be completed in order to unlock an otherwise locked device. 115 * 116 * Note: there may be other ways to unlock the device that "bypass" the need for this 117 * authentication challenge (notably, biometrics like fingerprint or face unlock). 118 * 119 * Note: by design, this is offered as a convenience method alongside [authenticationMethod]. 120 * The flow should be used for code that wishes to stay up-to-date its logic as the 121 * authentication changes over time and this method should be used for simple code that only 122 * needs to check the current value. 123 */ 124 suspend fun getAuthenticationMethod(): AuthenticationMethodModel 125 126 /** Returns the length of the PIN or `0` if the current auth method is not PIN. */ 127 suspend fun getPinLength(): Int 128 129 /** 130 * Returns whether the lockscreen is enabled. 131 * 132 * When the lockscreen is not enabled, it shouldn't show in cases when the authentication method 133 * is considered not secure (for example, "swipe" is considered to be "none"). 134 */ 135 suspend fun isLockscreenEnabled(): Boolean 136 137 /** Reports an authentication attempt. */ 138 suspend fun reportAuthenticationAttempt(isSuccessful: Boolean) 139 140 /** Returns the current number of failed authentication attempts. */ 141 suspend fun getFailedAuthenticationAttemptCount(): Int 142 143 /** 144 * Returns the timestamp for when the current throttling will end, allowing the user to attempt 145 * authentication again. 146 * 147 * Note that this is in milliseconds and it matches [SystemClock.elapsedRealtime]. 148 */ 149 suspend fun getThrottlingEndTimestamp(): Long 150 151 /** Sets the cached throttling state, updating the [throttling] flow. */ 152 fun setThrottling(throttlingModel: AuthenticationThrottlingModel) 153 154 /** 155 * Sets the throttling timeout duration (time during which the user should not be allowed to 156 * attempt authentication). 157 */ 158 suspend fun setThrottleDuration(durationMs: Int) 159 160 /** 161 * Checks the given [LockscreenCredential] to see if it's correct, returning an 162 * [AuthenticationResultModel] representing what happened. 163 */ 164 suspend fun checkCredential(credential: LockscreenCredential): AuthenticationResultModel 165 } 166 167 class AuthenticationRepositoryImpl 168 @Inject 169 constructor( 170 @Application private val applicationScope: CoroutineScope, 171 private val getSecurityMode: Function<Int, KeyguardSecurityModel.SecurityMode>, 172 @Background private val backgroundDispatcher: CoroutineDispatcher, 173 private val userRepository: UserRepository, 174 keyguardRepository: KeyguardRepository, 175 private val lockPatternUtils: LockPatternUtils, 176 broadcastDispatcher: BroadcastDispatcher, 177 ) : AuthenticationRepository { 178 179 override val isUnlocked = keyguardRepository.isKeyguardUnlocked 180 181 override suspend fun isLockscreenEnabled(): Boolean { 182 return withContext(backgroundDispatcher) { 183 val selectedUserId = userRepository.selectedUserId 184 !lockPatternUtils.isLockScreenDisabled(selectedUserId) 185 } 186 } 187 188 override val isAutoConfirmEnabled: StateFlow<Boolean> = 189 refreshingFlow( 190 initialValue = false, 191 getFreshValue = lockPatternUtils::isAutoPinConfirmEnabled, 192 ) 193 194 override val hintedPinLength: Int = 6 195 196 override val isPatternVisible: StateFlow<Boolean> = 197 refreshingFlow( 198 initialValue = true, 199 getFreshValue = lockPatternUtils::isVisiblePatternEnabled, 200 ) 201 202 private val _throttling = MutableStateFlow(AuthenticationThrottlingModel()) 203 override val throttling: StateFlow<AuthenticationThrottlingModel> = _throttling.asStateFlow() 204 205 private val UserRepository.selectedUserId: Int 206 get() = getSelectedUserInfo().id 207 208 override val authenticationMethod: Flow<AuthenticationMethodModel> = 209 userRepository.selectedUserInfo 210 .map { it.id } 211 .distinctUntilChanged() 212 .flatMapLatest { selectedUserId -> 213 broadcastDispatcher 214 .broadcastFlow( 215 filter = 216 IntentFilter( 217 DevicePolicyManager.ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED 218 ), 219 user = UserHandle.of(selectedUserId), 220 ) 221 .onStart { emit(Unit) } 222 .map { selectedUserId } 223 } 224 .map { selectedUserId -> 225 withContext(backgroundDispatcher) { 226 blockingAuthenticationMethodInternal(selectedUserId) 227 } 228 } 229 230 override suspend fun getAuthenticationMethod(): AuthenticationMethodModel { 231 return withContext(backgroundDispatcher) { 232 blockingAuthenticationMethodInternal(userRepository.selectedUserId) 233 } 234 } 235 236 override suspend fun getPinLength(): Int { 237 return withContext(backgroundDispatcher) { 238 val selectedUserId = userRepository.selectedUserId 239 lockPatternUtils.getPinLength(selectedUserId) 240 } 241 } 242 243 override suspend fun reportAuthenticationAttempt(isSuccessful: Boolean) { 244 val selectedUserId = userRepository.selectedUserId 245 withContext(backgroundDispatcher) { 246 if (isSuccessful) { 247 lockPatternUtils.reportSuccessfulPasswordAttempt(selectedUserId) 248 } else { 249 lockPatternUtils.reportFailedPasswordAttempt(selectedUserId) 250 } 251 } 252 } 253 254 override suspend fun getFailedAuthenticationAttemptCount(): Int { 255 return withContext(backgroundDispatcher) { 256 val selectedUserId = userRepository.selectedUserId 257 lockPatternUtils.getCurrentFailedPasswordAttempts(selectedUserId) 258 } 259 } 260 261 override suspend fun getThrottlingEndTimestamp(): Long { 262 return withContext(backgroundDispatcher) { 263 val selectedUserId = userRepository.selectedUserId 264 lockPatternUtils.getLockoutAttemptDeadline(selectedUserId) 265 } 266 } 267 268 override fun setThrottling(throttlingModel: AuthenticationThrottlingModel) { 269 _throttling.value = throttlingModel 270 } 271 272 override suspend fun setThrottleDuration(durationMs: Int) { 273 withContext(backgroundDispatcher) { 274 lockPatternUtils.setLockoutAttemptDeadline( 275 userRepository.selectedUserId, 276 durationMs, 277 ) 278 } 279 } 280 281 override suspend fun checkCredential( 282 credential: LockscreenCredential 283 ): AuthenticationResultModel { 284 return suspendCoroutine { continuation -> 285 LockPatternChecker.checkCredential( 286 lockPatternUtils, 287 credential, 288 userRepository.selectedUserId, 289 object : LockPatternChecker.OnCheckCallback { 290 override fun onChecked(matched: Boolean, throttleTimeoutMs: Int) { 291 continuation.resume( 292 AuthenticationResultModel( 293 isSuccessful = matched, 294 throttleDurationMs = throttleTimeoutMs, 295 ) 296 ) 297 } 298 299 override fun onCancelled() { 300 continuation.resume(AuthenticationResultModel(isSuccessful = false)) 301 } 302 303 override fun onEarlyMatched() = Unit 304 } 305 ) 306 } 307 } 308 309 /** 310 * Returns a [StateFlow] that's automatically kept fresh. The passed-in [getFreshValue] is 311 * invoked on a background thread every time the selected user is changed and every time a new 312 * downstream subscriber is added to the flow. 313 * 314 * Initially, the flow will emit [initialValue] while it refreshes itself in the background by 315 * invoking the [getFreshValue] function and emitting the fresh value when that's done. 316 * 317 * Every time the selected user is changed, the flow will re-invoke [getFreshValue] and emit the 318 * new value. 319 * 320 * Every time a new downstream subscriber is added to the flow it first receives the latest 321 * cached value that's either the [initialValue] or the latest previously fetched value. In 322 * addition, adding a new downstream subscriber also triggers another [getFreshValue] call and a 323 * subsequent emission of that newest value. 324 */ 325 private fun <T> refreshingFlow( 326 initialValue: T, 327 getFreshValue: suspend (selectedUserId: Int) -> T, 328 ): StateFlow<T> { 329 val flow = MutableStateFlow(initialValue) 330 applicationScope.launch { 331 combine( 332 // Emits a value initially and every time the selected user is changed. 333 userRepository.selectedUserInfo.map { it.id }.distinctUntilChanged(), 334 // Emits a value only when the number of downstream subscribers of this flow 335 // increases. 336 flow.subscriptionCount.pairwise(initialValue = 0).filter { (previous, current) 337 -> 338 current > previous 339 }, 340 ) { selectedUserId, _ -> 341 selectedUserId 342 } 343 .collect { selectedUserId -> 344 flow.value = withContext(backgroundDispatcher) { getFreshValue(selectedUserId) } 345 } 346 } 347 348 return flow.asStateFlow() 349 } 350 351 /** 352 * Returns the authentication method for the given user ID. 353 * 354 * WARNING: this is actually a blocking IPC/"binder" call that's expensive to do on the main 355 * thread. We keep it not marked as `suspend` because we want to be able to run this without a 356 * `runBlocking` which has a ton of performance/blocking problems. 357 */ 358 private fun blockingAuthenticationMethodInternal( 359 userId: Int, 360 ): AuthenticationMethodModel { 361 return when (getSecurityMode.apply(userId)) { 362 KeyguardSecurityModel.SecurityMode.PIN, 363 KeyguardSecurityModel.SecurityMode.SimPin, 364 KeyguardSecurityModel.SecurityMode.SimPuk -> AuthenticationMethodModel.Pin 365 KeyguardSecurityModel.SecurityMode.Password -> AuthenticationMethodModel.Password 366 KeyguardSecurityModel.SecurityMode.Pattern -> AuthenticationMethodModel.Pattern 367 KeyguardSecurityModel.SecurityMode.None -> AuthenticationMethodModel.None 368 KeyguardSecurityModel.SecurityMode.Invalid -> error("Invalid security mode!") 369 } 370 } 371 } 372 373 @Module 374 interface AuthenticationRepositoryModule { 375 @Binds fun repository(impl: AuthenticationRepositoryImpl): AuthenticationRepository 376 } 377