1 /* <lambda>null2 * Copyright (C) 2024 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.deviceentry.domain.interactor 18 19 import android.content.res.Resources 20 import com.android.systemui.biometrics.domain.interactor.FingerprintPropertyInteractor 21 import com.android.systemui.dagger.SysUISingleton 22 import com.android.systemui.dagger.qualifiers.Main 23 import com.android.systemui.deviceentry.shared.model.ErrorFaceAuthenticationStatus 24 import com.android.systemui.deviceentry.shared.model.FaceFailureMessage 25 import com.android.systemui.deviceentry.shared.model.FaceLockoutMessage 26 import com.android.systemui.deviceentry.shared.model.FaceMessage 27 import com.android.systemui.deviceentry.shared.model.FaceTimeoutMessage 28 import com.android.systemui.deviceentry.shared.model.FailedFaceAuthenticationStatus 29 import com.android.systemui.deviceentry.shared.model.FingerprintFailureMessage 30 import com.android.systemui.deviceentry.shared.model.FingerprintLockoutMessage 31 import com.android.systemui.deviceentry.shared.model.FingerprintMessage 32 import com.android.systemui.deviceentry.shared.model.HelpFaceAuthenticationStatus 33 import com.android.systemui.keyguard.domain.interactor.DevicePostureInteractor 34 import com.android.systemui.keyguard.shared.model.DevicePosture 35 import com.android.systemui.keyguard.shared.model.ErrorFingerprintAuthenticationStatus 36 import com.android.systemui.res.R 37 import com.android.systemui.util.kotlin.sample 38 import javax.inject.Inject 39 import kotlinx.coroutines.ExperimentalCoroutinesApi 40 import kotlinx.coroutines.flow.Flow 41 import kotlinx.coroutines.flow.combine 42 import kotlinx.coroutines.flow.filter 43 import kotlinx.coroutines.flow.filterIsInstance 44 import kotlinx.coroutines.flow.filterNot 45 import kotlinx.coroutines.flow.flatMapLatest 46 import kotlinx.coroutines.flow.flowOf 47 import kotlinx.coroutines.flow.map 48 import kotlinx.coroutines.flow.merge 49 50 /** 51 * BiometricMessage business logic. Filters biometric error/fail/success events for authentication 52 * events that should never surface a message to the user at the current device state. 53 */ 54 @ExperimentalCoroutinesApi 55 @SysUISingleton 56 class BiometricMessageInteractor 57 @Inject 58 constructor( 59 @Main private val resources: Resources, 60 fingerprintAuthInteractor: DeviceEntryFingerprintAuthInteractor, 61 fingerprintPropertyInteractor: FingerprintPropertyInteractor, 62 faceAuthInteractor: DeviceEntryFaceAuthInteractor, 63 private val biometricSettingsInteractor: DeviceEntryBiometricSettingsInteractor, 64 faceHelpMessageDeferralInteractor: FaceHelpMessageDeferralInteractor, 65 devicePostureInteractor: DevicePostureInteractor, 66 ) { 67 private val faceHelp: Flow<HelpFaceAuthenticationStatus> = 68 faceAuthInteractor.authenticationStatus.filterIsInstance<HelpFaceAuthenticationStatus>() 69 private val faceError: Flow<ErrorFaceAuthenticationStatus> = 70 faceAuthInteractor.authenticationStatus.filterIsInstance<ErrorFaceAuthenticationStatus>() 71 private val faceFailure: Flow<FailedFaceAuthenticationStatus> = 72 faceAuthInteractor.authenticationStatus.filterIsInstance<FailedFaceAuthenticationStatus>() 73 74 /** 75 * The acquisition message ids to show message when both fingerprint and face are enrolled and 76 * enabled for device entry. 77 */ 78 private val coExFaceAcquisitionMsgIdsToShowDefault: Set<Int> = 79 resources.getIntArray(R.array.config_face_help_msgs_when_fingerprint_enrolled).toSet() 80 81 /** 82 * The acquisition message ids to show message when both fingerprint and face are enrolled and 83 * enabled for device entry and the device is unfolded. 84 */ 85 private val coExFaceAcquisitionMsgIdsToShowUnfolded: Set<Int> = 86 resources 87 .getIntArray(R.array.config_face_help_msgs_when_fingerprint_enrolled_unfolded) 88 .toSet() 89 90 private fun ErrorFingerprintAuthenticationStatus.shouldSuppressError(): Boolean { 91 return isCancellationError() || isPowerPressedError() 92 } 93 94 private fun ErrorFaceAuthenticationStatus.shouldSuppressError(): Boolean { 95 return isCancellationError() || isUnableToProcessError() 96 } 97 98 private val fingerprintErrorMessage: Flow<FingerprintMessage> = 99 fingerprintAuthInteractor.fingerprintError 100 .filterNot { it.shouldSuppressError() } 101 .sample(biometricSettingsInteractor.fingerprintAuthCurrentlyAllowed, ::Pair) 102 .filter { (errorStatus, fingerprintAuthAllowed) -> 103 fingerprintAuthAllowed || errorStatus.isLockoutError() 104 } 105 .map { (errorStatus, _) -> 106 when { 107 errorStatus.isLockoutError() -> FingerprintLockoutMessage(errorStatus.msg) 108 else -> FingerprintMessage(errorStatus.msg) 109 } 110 } 111 112 private val fingerprintHelpMessage: Flow<FingerprintMessage> = 113 fingerprintAuthInteractor.fingerprintHelp 114 .sample(biometricSettingsInteractor.fingerprintAuthCurrentlyAllowed, ::Pair) 115 .filter { (_, fingerprintAuthAllowed) -> fingerprintAuthAllowed } 116 .map { (helpStatus, _) -> FingerprintMessage(helpStatus.msg) } 117 118 private val fingerprintFailMessage: Flow<FingerprintMessage> = 119 fingerprintPropertyInteractor.isUdfps.flatMapLatest { isUdfps -> 120 fingerprintAuthInteractor.fingerprintFailure 121 .sample(biometricSettingsInteractor.fingerprintAuthCurrentlyAllowed) 122 .filter { fingerprintAuthAllowed -> fingerprintAuthAllowed } 123 .map { 124 FingerprintFailureMessage( 125 if (isUdfps) { 126 resources.getString( 127 com.android.internal.R.string.fingerprint_udfps_error_not_match 128 ) 129 } else { 130 resources.getString( 131 com.android.internal.R.string.fingerprint_error_not_match 132 ) 133 } 134 ) 135 } 136 } 137 138 val coExFaceAcquisitionMsgIdsToShow: Flow<Set<Int>> = 139 devicePostureInteractor.posture.map { devicePosture -> 140 when (devicePosture) { 141 DevicePosture.OPENED -> coExFaceAcquisitionMsgIdsToShowUnfolded 142 DevicePosture.UNKNOWN, // Devices without posture support (non-foldable) use UNKNOWN 143 DevicePosture.CLOSED, 144 DevicePosture.HALF_OPENED, 145 DevicePosture.FLIPPED -> coExFaceAcquisitionMsgIdsToShowDefault 146 } 147 } 148 149 val fingerprintMessage: Flow<FingerprintMessage> = 150 merge( 151 fingerprintErrorMessage, 152 fingerprintFailMessage, 153 fingerprintHelpMessage, 154 ) 155 156 private val filterConditionForFaceHelpMessages: 157 Flow<(HelpFaceAuthenticationStatus) -> Boolean> = 158 combine( 159 biometricSettingsInteractor.isFingerprintAuthEnrolledAndEnabled, 160 biometricSettingsInteractor.faceAuthCurrentlyAllowed, 161 ::Pair 162 ) 163 .flatMapLatest { (fingerprintEnrolled, faceAuthCurrentlyAllowed) -> 164 if (fingerprintEnrolled && faceAuthCurrentlyAllowed) { 165 // Show only some face help messages if fingerprint is also enrolled 166 coExFaceAcquisitionMsgIdsToShow.map { msgIdsToShow -> 167 { helpStatus: HelpFaceAuthenticationStatus -> 168 msgIdsToShow.contains(helpStatus.msgId) 169 } 170 } 171 } else if (faceAuthCurrentlyAllowed) { 172 // Show all face help messages if only face is enrolled and currently allowed 173 flowOf { _: HelpFaceAuthenticationStatus -> true } 174 } else { 175 flowOf { _: HelpFaceAuthenticationStatus -> false } 176 } 177 } 178 179 private val faceHelpMessage: Flow<FaceMessage> = 180 faceHelp 181 .filterNot { 182 // Message deferred to potentially show at face timeout error instead 183 faceHelpMessageDeferralInteractor.shouldDefer(it.msgId) 184 } 185 .sample(filterConditionForFaceHelpMessages, ::Pair) 186 .filter { (helpMessage, filterCondition) -> filterCondition(helpMessage) } 187 .map { (status, _) -> FaceMessage(status.msg) } 188 189 private val faceFailureMessage: Flow<FaceMessage> = 190 faceFailure 191 .sample(biometricSettingsInteractor.faceAuthCurrentlyAllowed) 192 .filter { faceAuthCurrentlyAllowed -> faceAuthCurrentlyAllowed } 193 .map { FaceFailureMessage(resources.getString(R.string.keyguard_face_failed)) } 194 195 private val faceErrorMessage: Flow<FaceMessage> = 196 faceError 197 .filterNot { it.shouldSuppressError() } 198 .sample(biometricSettingsInteractor.faceAuthCurrentlyAllowed, ::Pair) 199 .filter { (errorStatus, faceAuthCurrentlyAllowed) -> 200 faceAuthCurrentlyAllowed || errorStatus.isLockoutError() 201 } 202 .map { (status, _) -> 203 when { 204 status.isTimeoutError() -> { 205 val deferredMessage = faceHelpMessageDeferralInteractor.getDeferredMessage() 206 if (deferredMessage != null) { 207 FaceMessage(deferredMessage.toString()) 208 } else { 209 FaceTimeoutMessage(status.msg) 210 } 211 } 212 status.isLockoutError() -> FaceLockoutMessage(status.msg) 213 else -> FaceMessage(status.msg) 214 } 215 } 216 217 val faceMessage: Flow<FaceMessage> = 218 merge( 219 faceHelpMessage, 220 faceFailureMessage, 221 faceErrorMessage, 222 ) 223 } 224