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 package com.android.systemui.deviceentry.domain.interactor 17 18 import com.android.keyguard.logging.BiometricUnlockLogger 19 import com.android.systemui.Flags 20 import com.android.systemui.biometrics.data.repository.FingerprintPropertyRepository 21 import com.android.systemui.biometrics.shared.model.FingerprintSensorType 22 import com.android.systemui.dagger.SysUISingleton 23 import com.android.systemui.dump.DumpManager 24 import com.android.systemui.keyevent.domain.interactor.KeyEventInteractor 25 import com.android.systemui.keyguard.data.repository.BiometricSettingsRepository 26 import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor 27 import com.android.systemui.power.domain.interactor.PowerInteractor 28 import com.android.systemui.power.shared.model.WakeSleepReason 29 import com.android.systemui.util.kotlin.FlowDumperImpl 30 import com.android.systemui.util.kotlin.sample 31 import com.android.systemui.util.time.SystemClock 32 import javax.inject.Inject 33 import kotlinx.coroutines.flow.Flow 34 import kotlinx.coroutines.flow.combine 35 import kotlinx.coroutines.flow.combineTransform 36 import kotlinx.coroutines.flow.distinctUntilChanged 37 import kotlinx.coroutines.flow.filter 38 import kotlinx.coroutines.flow.map 39 import kotlinx.coroutines.flow.merge 40 import kotlinx.coroutines.flow.onStart 41 42 /** 43 * Business logic for device entry haptic events. Determines whether the haptic should play. In 44 * particular, there are extra guards for whether device entry error and successes haptics should 45 * play when the physical fingerprint sensor is located on the power button. 46 */ 47 @SysUISingleton 48 class DeviceEntryHapticsInteractor 49 @Inject 50 constructor( 51 biometricSettingsRepository: BiometricSettingsRepository, 52 deviceEntryBiometricAuthInteractor: DeviceEntryBiometricAuthInteractor, 53 deviceEntryFingerprintAuthInteractor: DeviceEntryFingerprintAuthInteractor, 54 deviceEntrySourceInteractor: DeviceEntrySourceInteractor, 55 fingerprintPropertyRepository: FingerprintPropertyRepository, 56 keyEventInteractor: KeyEventInteractor, 57 private val logger: BiometricUnlockLogger, 58 powerInteractor: PowerInteractor, 59 keyguardInteractor: KeyguardInteractor, 60 private val systemClock: SystemClock, 61 dumpManager: DumpManager, 62 ) : FlowDumperImpl(dumpManager) { 63 private val powerButtonSideFpsEnrolled = 64 combineTransform( 65 fingerprintPropertyRepository.sensorType, 66 biometricSettingsRepository.isFingerprintEnrolledAndEnabled, 67 ) { sensorType, enrolledAndEnabled -> 68 if (sensorType == FingerprintSensorType.POWER_BUTTON) { 69 emit(enrolledAndEnabled) 70 } else { 71 emit(false) 72 } 73 } 74 .distinctUntilChanged() 75 private val powerButtonDown: Flow<Boolean> = keyEventInteractor.isPowerButtonDown 76 private val lastPowerButtonWakeup: Flow<Long> = 77 powerInteractor.detailedWakefulness 78 .filter { it.isAwakeFrom(WakeSleepReason.POWER_BUTTON) } 79 .map { systemClock.uptimeMillis() } 80 .onStart { 81 // If the power button hasn't been pressed, we still want this to evaluate to true: 82 // `uptimeMillis() - lastPowerButtonWakeup > recentPowerButtonPressThresholdMs` 83 emit(recentPowerButtonPressThresholdMs * -1L - 1L) 84 } 85 86 private val playSuccessHapticOnDeviceEntryFromBiometricSource: Flow<Unit> = 87 deviceEntrySourceInteractor.deviceEntryFromBiometricSource 88 .sample( 89 combine( 90 powerButtonSideFpsEnrolled, 91 powerButtonDown, 92 lastPowerButtonWakeup, 93 ::Triple, 94 ) 95 ) 96 .filter { (sideFpsEnrolled, powerButtonDown, lastPowerButtonWakeup) -> 97 val sideFpsAllowsHaptic = 98 !powerButtonDown && 99 systemClock.uptimeMillis() - lastPowerButtonWakeup > 100 recentPowerButtonPressThresholdMs 101 val allowHaptic = !sideFpsEnrolled || sideFpsAllowsHaptic 102 if (!allowHaptic) { 103 logger.d("Skip success haptic. Recent power button press or button is down.") 104 } 105 allowHaptic 106 } 107 // map to Unit 108 .map {} 109 110 private val playSuccessHapticOnDeviceEntryFromDeviceEntryIcon: Flow<Unit> = 111 deviceEntrySourceInteractor.attemptEnterDeviceFromDeviceEntryIcon 112 .map { keyguardInteractor.isKeyguardDismissible.value } 113 .filter { it } // only play if the keyguard is dismissible 114 // map to Unit 115 .map {} 116 117 /** 118 * Indicates when success haptics should play when the device is entered. When entering via a 119 * biometric sources, this always occurs on successful fingerprint authentications. It also 120 * occurs on successful face authentication but only if the lockscreen is bypassed. 121 */ 122 val playSuccessHapticOnDeviceEntry: Flow<Unit> = 123 if (Flags.msdlFeedback()) { 124 merge( 125 playSuccessHapticOnDeviceEntryFromBiometricSource, 126 playSuccessHapticOnDeviceEntryFromDeviceEntryIcon, 127 ) 128 .dumpWhileCollecting("playSuccessHaptic") 129 } else { 130 playSuccessHapticOnDeviceEntryFromBiometricSource.dumpWhileCollecting( 131 "playSuccessHaptic" 132 ) 133 } 134 135 private val playErrorHapticForBiometricFailure: Flow<Unit> = 136 merge( 137 deviceEntryFingerprintAuthInteractor.fingerprintFailure, 138 deviceEntryBiometricAuthInteractor.faceOnlyFaceFailure, 139 ) 140 // map to Unit 141 .map {} 142 .dumpWhileCollecting("playErrorHapticForBiometricFailure") 143 144 val playErrorHaptic: Flow<Unit> = 145 playErrorHapticForBiometricFailure 146 .sample(combine(powerButtonSideFpsEnrolled, powerButtonDown, ::Pair)) 147 .filter { (sideFpsEnrolled, powerButtonDown) -> 148 val allowHaptic = !sideFpsEnrolled || !powerButtonDown 149 if (!allowHaptic) { 150 logger.d("Skip error haptic. Power button is down.") 151 } 152 allowHaptic 153 } 154 // map to Unit 155 .map {} 156 .dumpWhileCollecting("playErrorHaptic") 157 158 private val recentPowerButtonPressThresholdMs = 400L 159 } 160