• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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