• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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