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.biometrics.data.repository.FingerprintPropertyRepository 20 import com.android.systemui.biometrics.shared.model.FingerprintSensorType 21 import com.android.systemui.dagger.SysUISingleton 22 import com.android.systemui.keyevent.domain.interactor.KeyEventInteractor 23 import com.android.systemui.keyguard.data.repository.BiometricSettingsRepository 24 import com.android.systemui.power.domain.interactor.PowerInteractor 25 import com.android.systemui.power.shared.model.WakeSleepReason 26 import com.android.systemui.util.kotlin.sample 27 import com.android.systemui.util.time.SystemClock 28 import javax.inject.Inject 29 import kotlinx.coroutines.ExperimentalCoroutinesApi 30 import kotlinx.coroutines.flow.Flow 31 import kotlinx.coroutines.flow.combine 32 import kotlinx.coroutines.flow.combineTransform 33 import kotlinx.coroutines.flow.distinctUntilChanged 34 import kotlinx.coroutines.flow.filter 35 import kotlinx.coroutines.flow.map 36 import kotlinx.coroutines.flow.merge 37 import kotlinx.coroutines.flow.onStart 38 39 /** 40 * Business logic for device entry haptic events. Determines whether the haptic should play. In 41 * particular, there are extra guards for whether device entry error and successes haptics should 42 * play when the physical fingerprint sensor is located on the power button. 43 */ 44 @ExperimentalCoroutinesApi 45 @SysUISingleton 46 class DeviceEntryHapticsInteractor 47 @Inject 48 constructor( 49 deviceEntrySourceInteractor: DeviceEntrySourceInteractor, 50 deviceEntryFingerprintAuthInteractor: DeviceEntryFingerprintAuthInteractor, 51 deviceEntryBiometricAuthInteractor: DeviceEntryBiometricAuthInteractor, 52 fingerprintPropertyRepository: FingerprintPropertyRepository, 53 biometricSettingsRepository: BiometricSettingsRepository, 54 keyEventInteractor: KeyEventInteractor, 55 powerInteractor: PowerInteractor, 56 private val systemClock: SystemClock, 57 private val logger: BiometricUnlockLogger, 58 ) { 59 private val powerButtonSideFpsEnrolled = 60 combineTransform( 61 fingerprintPropertyRepository.sensorType, 62 biometricSettingsRepository.isFingerprintEnrolledAndEnabled, 63 ) { sensorType, enrolledAndEnabled -> 64 if (sensorType == FingerprintSensorType.POWER_BUTTON) { 65 emit(enrolledAndEnabled) 66 } else { 67 emit(false) 68 } 69 } 70 .distinctUntilChanged() 71 private val powerButtonDown: Flow<Boolean> = keyEventInteractor.isPowerButtonDown 72 private val lastPowerButtonWakeup: Flow<Long> = 73 powerInteractor.detailedWakefulness 74 .filter { it.isAwakeFrom(WakeSleepReason.POWER_BUTTON) } 75 .map { systemClock.uptimeMillis() } 76 .onStart { 77 // If the power button hasn't been pressed, we still want this to evaluate to true: 78 // `uptimeMillis() - lastPowerButtonWakeup > recentPowerButtonPressThresholdMs` 79 emit(recentPowerButtonPressThresholdMs * -1L - 1L) 80 } 81 82 val playSuccessHaptic: Flow<Unit> = 83 deviceEntrySourceInteractor.deviceEntryFromBiometricSource 84 .sample( 85 combine( 86 powerButtonSideFpsEnrolled, 87 powerButtonDown, 88 lastPowerButtonWakeup, 89 ::Triple 90 ) 91 ) 92 .filter { (sideFpsEnrolled, powerButtonDown, lastPowerButtonWakeup) -> 93 val sideFpsAllowsHaptic = 94 !powerButtonDown && 95 systemClock.uptimeMillis() - lastPowerButtonWakeup > 96 recentPowerButtonPressThresholdMs 97 val allowHaptic = !sideFpsEnrolled || sideFpsAllowsHaptic 98 if (!allowHaptic) { 99 logger.d("Skip success haptic. Recent power button press or button is down.") 100 } 101 allowHaptic 102 } 103 .map {} // map to Unit 104 105 private val playErrorHapticForBiometricFailure: Flow<Unit> = 106 merge( 107 deviceEntryFingerprintAuthInteractor.fingerprintFailure, 108 deviceEntryBiometricAuthInteractor.faceOnlyFaceFailure, 109 ) 110 .map {} // map to Unit 111 val playErrorHaptic: Flow<Unit> = 112 playErrorHapticForBiometricFailure 113 .sample(combine(powerButtonSideFpsEnrolled, powerButtonDown, ::Pair)) 114 .filter { (sideFpsEnrolled, powerButtonDown) -> 115 val allowHaptic = !sideFpsEnrolled || !powerButtonDown 116 if (!allowHaptic) { 117 logger.d("Skip error haptic. Power button is down.") 118 } 119 allowHaptic 120 } 121 .map {} // map to Unit 122 123 private val recentPowerButtonPressThresholdMs = 400L 124 } 125