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.biometrics.data.repository
18 
19 import android.hardware.biometrics.PromptInfo
20 import android.util.Log
21 import com.android.systemui.biometrics.AuthController
22 import com.android.systemui.biometrics.shared.model.PromptKind
23 import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging
24 import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
25 import com.android.systemui.dagger.SysUISingleton
26 import javax.inject.Inject
27 import kotlinx.coroutines.channels.awaitClose
28 import kotlinx.coroutines.flow.Flow
29 import kotlinx.coroutines.flow.MutableStateFlow
30 import kotlinx.coroutines.flow.StateFlow
31 import kotlinx.coroutines.flow.asStateFlow
32 import kotlinx.coroutines.flow.combine
33 import kotlinx.coroutines.flow.distinctUntilChanged
34 import kotlinx.coroutines.flow.flatMapLatest
35 import kotlinx.coroutines.flow.map
36 
37 /**
38  * A repository for the global state of BiometricPrompt.
39  *
40  * There is never more than one instance of the prompt at any given time.
41  */
42 interface PromptRepository {
43 
44     /** If the prompt is showing. */
45     val isShowing: Flow<Boolean>
46 
47     /** The app-specific details to show in the prompt. */
48     val promptInfo: StateFlow<PromptInfo?>
49 
50     /** The user that the prompt is for. */
51     val userId: StateFlow<Int?>
52 
53     /** The request that the prompt is for. */
54     val requestId: StateFlow<Long?>
55 
56     /** The gatekeeper challenge, if one is associated with this prompt. */
57     val challenge: StateFlow<Long?>
58 
59     /** The kind of prompt to use (biometric, pin, pattern, etc.). */
60     val promptKind: StateFlow<PromptKind>
61 
62     /** The package name that the prompt is called from. */
63     val opPackageName: StateFlow<String?>
64 
65     /**
66      * If explicit confirmation is required.
67      *
68      * Note: overlaps/conflicts with [PromptInfo.isConfirmationRequested], which needs clean up.
69      */
70     val isConfirmationRequired: Flow<Boolean>
71 
72     /** Update the prompt configuration, which should be set before [isShowing]. */
73     fun setPrompt(
74         promptInfo: PromptInfo,
75         userId: Int,
76         requestId: Long,
77         gatekeeperChallenge: Long?,
78         kind: PromptKind,
79         opPackageName: String,
80     )
81 
82     /** Unset the prompt info. */
83     fun unsetPrompt(requestId: Long)
84 }
85 
86 @SysUISingleton
87 class PromptRepositoryImpl
88 @Inject
89 constructor(
90     private val faceSettings: FaceSettingsRepository,
91     private val authController: AuthController,
92 ) : PromptRepository {
93 
<lambda>null94     override val isShowing: Flow<Boolean> = conflatedCallbackFlow {
95         val callback =
96             object : AuthController.Callback {
97                 override fun onBiometricPromptShown() =
98                     trySendWithFailureLogging(true, TAG, "set isShowing")
99 
100                 override fun onBiometricPromptDismissed() =
101                     trySendWithFailureLogging(false, TAG, "unset isShowing")
102             }
103         authController.addCallback(callback)
104         trySendWithFailureLogging(authController.isShowing, TAG, "update isShowing")
105         awaitClose { authController.removeCallback(callback) }
106     }
107 
108     private val _promptInfo: MutableStateFlow<PromptInfo?> = MutableStateFlow(null)
109     override val promptInfo = _promptInfo.asStateFlow()
110 
111     private val _challenge: MutableStateFlow<Long?> = MutableStateFlow(null)
112     override val challenge: StateFlow<Long?> = _challenge.asStateFlow()
113 
114     private val _userId: MutableStateFlow<Int?> = MutableStateFlow(null)
115     override val userId = _userId.asStateFlow()
116 
117     private val _requestId: MutableStateFlow<Long?> = MutableStateFlow(null)
118     override val requestId = _requestId.asStateFlow()
119 
120     private val _promptKind: MutableStateFlow<PromptKind> = MutableStateFlow(PromptKind.None)
121     override val promptKind = _promptKind.asStateFlow()
122 
123     private val _opPackageName: MutableStateFlow<String?> = MutableStateFlow(null)
124     override val opPackageName = _opPackageName.asStateFlow()
125 
126     private val _faceSettings =
idnull127         _userId.map { id -> faceSettings.forUser(id) }.distinctUntilChanged()
128     private val _faceSettingAlwaysRequireConfirmation =
<lambda>null129         _faceSettings.flatMapLatest { it.alwaysRequireConfirmationInApps }.distinctUntilChanged()
130 
<lambda>null131     private val _isConfirmationRequired = _promptInfo.map { it?.isConfirmationRequested ?: false }
132     override val isConfirmationRequired =
133         combine(_isConfirmationRequired, _faceSettingAlwaysRequireConfirmation) {
appRequiresConfirmationnull134                 appRequiresConfirmation,
135                 forceRequireConfirmation ->
136                 forceRequireConfirmation || appRequiresConfirmation
137             }
138             .distinctUntilChanged()
139 
140     override fun setPrompt(
141         promptInfo: PromptInfo,
142         userId: Int,
143         requestId: Long,
144         gatekeeperChallenge: Long?,
145         kind: PromptKind,
146         opPackageName: String,
147     ) {
148         _promptKind.value = kind
149         _userId.value = userId
150         _requestId.value = requestId
151         _challenge.value = gatekeeperChallenge
152         _promptInfo.value = promptInfo
153         _opPackageName.value = opPackageName
154     }
155 
unsetPromptnull156     override fun unsetPrompt(requestId: Long) {
157         if (requestId == _requestId.value) {
158             _promptInfo.value = null
159             _userId.value = null
160             _requestId.value = null
161             _challenge.value = null
162             _promptKind.value = PromptKind.None
163             _opPackageName.value = null
164         } else {
165             Log.w(TAG, "Ignoring unsetPrompt - requestId mismatch")
166         }
167     }
168 
169     companion object {
170         private const val TAG = "PromptRepositoryImpl"
171     }
172 }
173