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