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