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