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