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