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 
17 package com.android.systemui.biometrics.domain.interactor
18 
19 import android.content.Context
20 import android.hardware.biometrics.SensorLocationInternal
21 import android.view.WindowManager
22 import com.android.systemui.biometrics.FingerprintInteractiveToAuthProvider
23 import com.android.systemui.biometrics.data.repository.FingerprintPropertyRepository
24 import com.android.systemui.biometrics.domain.model.SideFpsSensorLocation
25 import com.android.systemui.biometrics.shared.model.DisplayRotation
26 import com.android.systemui.biometrics.shared.model.FingerprintSensorType
27 import com.android.systemui.biometrics.shared.model.isDefaultOrientation
28 import com.android.systemui.dagger.SysUISingleton
29 import com.android.systemui.keyguard.data.repository.BiometricSettingsRepository
30 import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
31 import com.android.systemui.keyguard.shared.model.KeyguardState
32 import com.android.systemui.log.SideFpsLogger
33 import com.android.systemui.res.R
34 import java.util.Optional
35 import javax.inject.Inject
36 import kotlinx.coroutines.ExperimentalCoroutinesApi
37 import kotlinx.coroutines.flow.Flow
38 import kotlinx.coroutines.flow.combine
39 import kotlinx.coroutines.flow.distinctUntilChanged
40 import kotlinx.coroutines.flow.filterNotNull
41 import kotlinx.coroutines.flow.flatMapLatest
42 import kotlinx.coroutines.flow.flowOf
43 import kotlinx.coroutines.flow.map
44 import kotlinx.coroutines.flow.onEach
45 
46 @ExperimentalCoroutinesApi
47 @SysUISingleton
48 class SideFpsSensorInteractor
49 @Inject
50 constructor(
51     private val context: Context,
52     fingerprintPropertyRepository: FingerprintPropertyRepository,
53     windowManager: WindowManager,
54     displayStateInteractor: DisplayStateInteractor,
55     fingerprintInteractiveToAuthProvider: Optional<FingerprintInteractiveToAuthProvider>,
56     biometricSettingsRepository: BiometricSettingsRepository,
57     keyguardTransitionInteractor: KeyguardTransitionInteractor,
58     private val logger: SideFpsLogger,
59 ) {
60 
61     private val isProlongedTouchEnabledForDevice =
62         context.resources.getBoolean(R.bool.config_restToUnlockSupported)
63 
64     private val sensorLocationForCurrentDisplay =
65         combine(
66                 displayStateInteractor.displayChanges,
67                 fingerprintPropertyRepository.sensorLocations,
68                 ::Pair
69             )
70             .map { (_, locations) -> locations[context.display?.uniqueId] }
71             .filterNotNull()
72 
73     val isAvailable: Flow<Boolean> =
74         fingerprintPropertyRepository.sensorType.map { it == FingerprintSensorType.POWER_BUTTON }
75 
76     val authenticationDuration: Flow<Long> =
77         keyguardTransitionInteractor
78             .isFinishedInStateWhere { it == KeyguardState.OFF || it == KeyguardState.DOZING }
79             .map {
80                 if (it)
81                     context.resources
82                         ?.getInteger(R.integer.config_restToUnlockDurationScreenOff)
83                         ?.toLong()
84                 else
85                     context.resources
86                         ?.getInteger(R.integer.config_restToUnlockDurationDefault)
87                         ?.toLong()
88             }
89             .map { it ?: 0L }
90             .onEach { logger.authDurationChanged(it) }
91 
92     private val isSettingEnabled: Flow<Boolean> =
93         biometricSettingsRepository.isFingerprintEnrolledAndEnabled
94             .flatMapLatest { enabledAndEnrolled ->
95                 if (!enabledAndEnrolled || fingerprintInteractiveToAuthProvider.isEmpty) {
96                     flowOf(false)
97                 } else {
98                     fingerprintInteractiveToAuthProvider.get().enabledForCurrentUser
99                 }
100             }
101             .onEach { logger.restToUnlockSettingEnabledChanged(it) }
102 
103     val isProlongedTouchRequiredForAuthentication: Flow<Boolean> =
104         if (!isProlongedTouchEnabledForDevice) {
105             flowOf(false)
106         } else {
107             combine(
108                 isAvailable,
109                 isSettingEnabled,
110             ) { sfpsAvailable, isSettingEnabled ->
111                 sfpsAvailable && isSettingEnabled
112             }
113         }
114 
115     val sensorLocation: Flow<SideFpsSensorLocation> =
116         combine(displayStateInteractor.currentRotation, sensorLocationForCurrentDisplay, ::Pair)
117             .map { (rotation, sensorLocation: SensorLocationInternal) ->
118                 val isSensorVerticalInDefaultOrientation = sensorLocation.sensorLocationY != 0
119                 // device dimensions in the current rotation
120                 val windowMetrics = windowManager.maximumWindowMetrics
121                 val size = windowMetrics.bounds
122                 val isDefaultOrientation = rotation.isDefaultOrientation()
123                 // Width and height are flipped is device is not in rotation_0 or rotation_180
124                 // Flipping it to the width and height of the device in default orientation.
125                 val displayWidth = if (isDefaultOrientation) size.width() else size.height()
126                 val displayHeight = if (isDefaultOrientation) size.height() else size.width()
127                 val sensorLengthInPx = sensorLocation.sensorRadius * 2
128 
129                 val (sensorLeft, sensorTop) =
130                     if (isSensorVerticalInDefaultOrientation) {
131                         when (rotation) {
132                             DisplayRotation.ROTATION_0 -> {
133                                 Pair(displayWidth, sensorLocation.sensorLocationY)
134                             }
135                             DisplayRotation.ROTATION_90 -> {
136                                 Pair(sensorLocation.sensorLocationY, 0)
137                             }
138                             DisplayRotation.ROTATION_180 -> {
139                                 Pair(
140                                     0,
141                                     displayHeight -
142                                         sensorLocation.sensorLocationY -
143                                         sensorLengthInPx
144                                 )
145                             }
146                             DisplayRotation.ROTATION_270 -> {
147                                 Pair(
148                                     displayHeight -
149                                         sensorLocation.sensorLocationY -
150                                         sensorLengthInPx,
151                                     displayWidth
152                                 )
153                             }
154                         }
155                     } else {
156                         when (rotation) {
157                             DisplayRotation.ROTATION_0 -> {
158                                 Pair(sensorLocation.sensorLocationX, 0)
159                             }
160                             DisplayRotation.ROTATION_90 -> {
161                                 Pair(
162                                     0,
163                                     displayWidth - sensorLocation.sensorLocationX - sensorLengthInPx
164                                 )
165                             }
166                             DisplayRotation.ROTATION_180 -> {
167                                 Pair(
168                                     displayWidth -
169                                         sensorLocation.sensorLocationX -
170                                         sensorLengthInPx,
171                                     displayHeight
172                                 )
173                             }
174                             DisplayRotation.ROTATION_270 -> {
175                                 Pair(displayHeight, sensorLocation.sensorLocationX)
176                             }
177                         }
178                     }
179                 SideFpsSensorLocation(
180                     left = sensorLeft,
181                     top = sensorTop,
182                     length = sensorLengthInPx,
183                     isSensorVerticalInDefaultOrientation = isSensorVerticalInDefaultOrientation
184                 )
185             }
186             .distinctUntilChanged(
187                 areEquivalent = { old: SideFpsSensorLocation, new: SideFpsSensorLocation ->
188                     old.left == new.left &&
189                         old.top == new.top &&
190                         old.length == new.length &&
191                         old.isSensorVerticalInDefaultOrientation ==
192                             new.isSensorVerticalInDefaultOrientation
193                 }
194             )
195             .onEach {
196                 logger.sensorLocationStateChanged(
197                     it.left,
198                     it.top,
199                     it.length,
200                     it.isSensorVerticalInDefaultOrientation
201                 )
202             }
203 }
204