1 /* <lambda>null2 * Copyright (C) 2024 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.graphics.Rect 21 import android.hardware.biometrics.SensorLocationInternal 22 import com.android.systemui.biometrics.data.repository.FingerprintPropertyRepository 23 import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractor 24 import com.android.systemui.dagger.SysUISingleton 25 import com.android.systemui.dagger.qualifiers.Application 26 import com.android.systemui.dagger.qualifiers.Main 27 import com.android.systemui.shared.customization.data.SensorLocation 28 import javax.inject.Inject 29 import kotlinx.coroutines.CoroutineScope 30 import kotlinx.coroutines.flow.Flow 31 import kotlinx.coroutines.flow.SharingStarted 32 import kotlinx.coroutines.flow.StateFlow 33 import kotlinx.coroutines.flow.combine 34 import kotlinx.coroutines.flow.distinctUntilChanged 35 import kotlinx.coroutines.flow.filterNotNull 36 import kotlinx.coroutines.flow.map 37 import kotlinx.coroutines.flow.stateIn 38 39 @SysUISingleton 40 class FingerprintPropertyInteractor 41 @Inject 42 constructor( 43 @Application private val applicationScope: CoroutineScope, 44 @Application private val context: Context, 45 private val repository: FingerprintPropertyRepository, 46 @Main private val configurationInteractor: ConfigurationInteractor, 47 displayStateInteractor: DisplayStateInteractor, 48 udfpsOverlayInteractor: UdfpsOverlayInteractor, 49 ) { 50 val propertiesInitialized: Flow<Boolean> = repository.propertiesInitialized 51 val isUdfps: StateFlow<Boolean> = 52 repository.sensorType 53 .map { it.isUdfps() } 54 .stateIn( 55 scope = applicationScope, 56 started = SharingStarted.Eagerly, 57 initialValue = repository.sensorType.value.isUdfps(), 58 ) 59 60 /** 61 * Devices with multiple physical displays use unique display ids to determine which sensor is 62 * on the active physical display. This value represents a unique physical display id. 63 */ 64 private val uniqueDisplayId: StateFlow<String> = 65 displayStateInteractor.displayChanges 66 .map { context.display.uniqueId } 67 .filterNotNull() 68 .distinctUntilChanged() 69 .stateIn( 70 scope = applicationScope, 71 started = SharingStarted.Eagerly, 72 initialValue = EMPTY_DISPLAY_ID, 73 ) 74 75 /** 76 * Sensor location for the: 77 * - current physical display 78 * - device's natural screen resolution 79 * - device's natural orientation 80 */ 81 private val unscaledSensorLocation: StateFlow<SensorLocationInternal> = 82 combineStates(repository.sensorLocations, uniqueDisplayId, applicationScope) { 83 locations, 84 displayId -> 85 // Devices without multiple physical displays do not use the display id as the key; 86 // instead, the key is an empty string. 87 locations.getOrDefault( 88 displayId, 89 locations.getOrDefault(EMPTY_DISPLAY_ID, SensorLocationInternal.DEFAULT), 90 ) 91 } 92 93 /** 94 * Sensor location for the: 95 * - current physical display 96 * - current screen resolution 97 * - device's natural orientation 98 */ 99 val sensorLocation: StateFlow<SensorLocation> = 100 combineStates( 101 unscaledSensorLocation, 102 configurationInteractor.scaleForResolution, 103 applicationScope, 104 ) { unscaledSensorLocation, scale -> 105 SensorLocation( 106 naturalCenterX = unscaledSensorLocation.sensorLocationX, 107 naturalCenterY = unscaledSensorLocation.sensorLocationY, 108 naturalRadius = unscaledSensorLocation.sensorRadius, 109 scale = scale, 110 ) 111 } 112 113 /** 114 * Sensor location for the: 115 * - current physical display 116 * - current screen resolution 117 * - device's current orientation 118 */ 119 val udfpsSensorBounds: Flow<Rect> = 120 udfpsOverlayInteractor.udfpsOverlayParams.map { it.sensorBounds }.distinctUntilChanged() 121 122 companion object { 123 124 private const val EMPTY_DISPLAY_ID = "" 125 126 /** Combine two state flows to another state flow. */ 127 private fun <T1, T2, R> combineStates( 128 flow1: StateFlow<T1>, 129 flow2: StateFlow<T2>, 130 scope: CoroutineScope, 131 transform: (T1, T2) -> R, 132 ): StateFlow<R> = 133 combine(flow1, flow2) { v1, v2 -> transform(v1, v2) } 134 .stateIn(scope, SharingStarted.Eagerly, transform(flow1.value, flow2.value)) 135 } 136 } 137