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