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