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