• 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 
18 package com.android.systemui.biometrics.data.repository
19 
20 import android.content.Context
21 import android.graphics.Point
22 import android.hardware.camera2.CameraManager
23 import android.hardware.face.FaceManager
24 import android.hardware.face.FaceSensorPropertiesInternal
25 import android.hardware.face.IFaceAuthenticatorsRegisteredCallback
26 import android.util.Log
27 import android.util.RotationUtils
28 import android.util.Size
29 import com.android.systemui.biometrics.shared.model.DisplayRotation
30 import com.android.systemui.biometrics.shared.model.LockoutMode
31 import com.android.systemui.biometrics.shared.model.SensorStrength
32 import com.android.systemui.biometrics.shared.model.toLockoutMode
33 import com.android.systemui.biometrics.shared.model.toRotation
34 import com.android.systemui.biometrics.shared.model.toSensorStrength
35 import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging
36 import com.android.systemui.common.coroutine.ConflatedCallbackFlow
37 import com.android.systemui.common.ui.data.repository.ConfigurationRepository
38 import com.android.systemui.dagger.SysUISingleton
39 import com.android.systemui.dagger.qualifiers.Application
40 import com.android.systemui.dagger.qualifiers.Background
41 import com.android.systemui.dagger.qualifiers.Main
42 import com.android.systemui.keyguard.shared.model.DevicePosture
43 import com.android.systemui.res.R
44 import java.util.concurrent.Executor
45 import javax.inject.Inject
46 import kotlinx.coroutines.CoroutineDispatcher
47 import kotlinx.coroutines.CoroutineScope
48 import kotlinx.coroutines.channels.awaitClose
49 import kotlinx.coroutines.flow.SharingStarted
50 import kotlinx.coroutines.flow.StateFlow
51 import kotlinx.coroutines.flow.combine
52 import kotlinx.coroutines.flow.flatMapLatest
53 import kotlinx.coroutines.flow.flowOf
54 import kotlinx.coroutines.flow.map
55 import kotlinx.coroutines.flow.onEach
56 import kotlinx.coroutines.flow.stateIn
57 import kotlinx.coroutines.withContext
58 
59 /** A repository for the global state of Face sensor. */
60 interface FacePropertyRepository {
61     /** Face sensor information, null if it is not available. */
62     val sensorInfo: StateFlow<FaceSensorInfo?>
63 
64     /** Get the current lockout mode for the user. This makes a binder based service call. */
65     suspend fun getLockoutMode(userId: Int): LockoutMode
66 
67     /** The current face sensor location in current device rotation */
68     val sensorLocation: StateFlow<Point?>
69 
70     /** The info of current available camera. */
71     val cameraInfo: StateFlow<CameraInfo?>
72 
73     val supportedPostures: List<DevicePosture>
74 }
75 
76 /** Describes a biometric sensor */
77 data class FaceSensorInfo(val id: Int, val strength: SensorStrength)
78 
79 /** Data class for camera info */
80 data class CameraInfo(
81     /** The logical id of the camera */
82     val cameraId: String,
83     /** The physical id of the camera */
84     val cameraPhysicalId: String?,
85     /** The center point of the camera in natural orientation */
86     val cameraLocation: Point?,
87 )
88 
89 private const val TAG = "FaceSensorPropertyRepositoryImpl"
90 
91 @SysUISingleton
92 class FacePropertyRepositoryImpl
93 @Inject
94 constructor(
95     @Application val applicationContext: Context,
96     @Main mainExecutor: Executor,
97     @Application private val applicationScope: CoroutineScope,
98     @Background private val backgroundDispatcher: CoroutineDispatcher,
99     private val faceManager: FaceManager?,
100     private val cameraManager: CameraManager,
101     displayStateRepository: DisplayStateRepository,
102     configurationRepository: ConfigurationRepository,
103 ) : FacePropertyRepository {
104 
105     override val sensorInfo: StateFlow<FaceSensorInfo?> =
<lambda>null106         ConflatedCallbackFlow.conflatedCallbackFlow {
107                 val callback =
108                     object : IFaceAuthenticatorsRegisteredCallback.Stub() {
109                         override fun onAllAuthenticatorsRegistered(
110                             sensors: List<FaceSensorPropertiesInternal>,
111                         ) {
112                             if (sensors.isEmpty()) return
113                             trySendWithFailureLogging(
114                                 FaceSensorInfo(
115                                     sensors.first().sensorId,
116                                     sensors.first().sensorStrength.toSensorStrength()
117                                 ),
118                                 TAG,
119                                 "onAllAuthenticatorsRegistered"
120                             )
121                         }
122                     }
123                 withContext(backgroundDispatcher) {
124                     faceManager?.addAuthenticatorsRegisteredCallback(callback)
125                 }
126                 awaitClose {}
127             }
<lambda>null128             .onEach { Log.d(TAG, "sensorProps changed: $it") }
129             .stateIn(applicationScope, SharingStarted.Eagerly, null)
130 
131     private val cameraInfoList: List<CameraInfo> = loadCameraInfoList()
132     private var currentPhysicalCameraId: String? = null
133 
134     override val cameraInfo: StateFlow<CameraInfo?> =
<lambda>null135         ConflatedCallbackFlow.conflatedCallbackFlow {
136                 val callback =
137                     object : CameraManager.AvailabilityCallback() {
138 
139                         // This callback will only be called when there is more than one front
140                         // camera on the device (e.g. foldable device with cameras on both outer &
141                         // inner display).
142                         override fun onPhysicalCameraAvailable(
143                             cameraId: String,
144                             physicalCameraId: String
145                         ) {
146                             currentPhysicalCameraId = physicalCameraId
147                             val cameraInfo =
148                                 cameraInfoList.firstOrNull {
149                                     physicalCameraId == it.cameraPhysicalId
150                                 }
151                             trySendWithFailureLogging(
152                                 cameraInfo,
153                                 TAG,
154                                 "Update face sensor location to $cameraInfo."
155                             )
156                         }
157 
158                         // This callback will only be called when there is more than one front
159                         // camera on the device (e.g. foldable device with cameras on both outer &
160                         // inner display).
161                         //
162                         // By default, all cameras are available which means there will be no
163                         // onPhysicalCameraAvailable() invoked and depending on the device state
164                         // (Fold or unfold), only the onPhysicalCameraUnavailable() for another
165                         // camera will be invoke. So we need to use this method to decide the
166                         // initial physical ID for foldable devices.
167                         override fun onPhysicalCameraUnavailable(
168                             cameraId: String,
169                             physicalCameraId: String
170                         ) {
171                             if (currentPhysicalCameraId == null) {
172                                 val cameraInfo =
173                                     cameraInfoList.firstOrNull {
174                                         physicalCameraId != it.cameraPhysicalId
175                                     }
176                                 currentPhysicalCameraId = cameraInfo?.cameraPhysicalId
177                                 trySendWithFailureLogging(
178                                     cameraInfo,
179                                     TAG,
180                                     "Update face sensor location to $cameraInfo."
181                                 )
182                             }
183                         }
184                     }
185                 cameraManager.registerAvailabilityCallback(mainExecutor, callback)
186                 awaitClose { cameraManager.unregisterAvailabilityCallback(callback) }
187             }
188             .stateIn(
189                 applicationScope,
190                 started = SharingStarted.WhileSubscribed(),
191                 initialValue = if (cameraInfoList.isNotEmpty()) cameraInfoList[0] else null
192             )
193 
194     private val supportedPosture =
195         applicationContext.resources.getInteger(R.integer.config_face_auth_supported_posture)
196     override val supportedPostures: List<DevicePosture> =
197         if (supportedPosture == 0) {
198             DevicePosture.entries
199         } else {
200             listOf(DevicePosture.toPosture(supportedPosture))
201         }
202 
203     private val defaultSensorLocation: StateFlow<Point?> =
204         cameraInfo
<lambda>null205             .map { it?.cameraLocation }
206             .stateIn(
207                 applicationScope,
208                 started = SharingStarted.WhileSubscribed(),
209                 initialValue = null
210             )
211 
212     override val sensorLocation: StateFlow<Point?> =
213         sensorInfo
infonull214             .flatMapLatest { info ->
215                 if (info == null) {
216                     flowOf(null)
217                 } else {
218                     combine(
219                         defaultSensorLocation,
220                         displayStateRepository.currentRotation,
221                         displayStateRepository.currentDisplaySize,
222                         configurationRepository.scaleForResolution
223                     ) { defaultLocation, displayRotation, displaySize, scaleForResolution ->
224                         computeCurrentFaceLocation(
225                             defaultLocation,
226                             displayRotation,
227                             displaySize,
228                             scaleForResolution
229                         )
230                     }
231                 }
232             }
233             .stateIn(
234                 applicationScope,
235                 started = SharingStarted.WhileSubscribed(),
236                 initialValue = null
237             )
238 
computeCurrentFaceLocationnull239     private fun computeCurrentFaceLocation(
240         defaultLocation: Point?,
241         rotation: DisplayRotation,
242         displaySize: Size,
243         scaleForResolution: Float,
244     ): Point? {
245         if (defaultLocation == null) {
246             return null
247         }
248 
249         return rotateToCurrentOrientation(
250             Point(
251                 (defaultLocation.x * scaleForResolution).toInt(),
252                 (defaultLocation.y * scaleForResolution).toInt()
253             ),
254             rotation,
255             displaySize
256         )
257     }
258 
rotateToCurrentOrientationnull259     private fun rotateToCurrentOrientation(
260         inOutPoint: Point,
261         rotation: DisplayRotation,
262         displaySize: Size
263     ): Point {
264         RotationUtils.rotatePoint(
265             inOutPoint,
266             rotation.toRotation(),
267             displaySize.width,
268             displaySize.height
269         )
270         return inOutPoint
271     }
getLockoutModenull272     override suspend fun getLockoutMode(userId: Int): LockoutMode {
273         if (sensorInfo.value == null || faceManager == null) {
274             return LockoutMode.NONE
275         }
276         return faceManager.getLockoutModeForUser(sensorInfo.value!!.id, userId).toLockoutMode()
277     }
278 
loadCameraInfoListnull279     private fun loadCameraInfoList(): List<CameraInfo> {
280         val list = mutableListOf<CameraInfo>()
281 
282         val outer =
283             loadCameraInfo(
284                 R.string.config_protectedCameraId,
285                 R.string.config_protectedPhysicalCameraId,
286                 R.array.config_face_auth_props
287             )
288         if (outer != null) {
289             list.add(outer)
290         }
291 
292         val inner =
293             loadCameraInfo(
294                 R.string.config_protectedInnerCameraId,
295                 R.string.config_protectedInnerPhysicalCameraId,
296                 R.array.config_inner_face_auth_props
297             )
298         if (inner != null) {
299             list.add(inner)
300         }
301         return list
302     }
303 
loadCameraInfonull304     private fun loadCameraInfo(
305         cameraIdRes: Int,
306         cameraPhysicalIdRes: Int,
307         cameraLocationRes: Int
308     ): CameraInfo? {
309         val cameraId = applicationContext.getString(cameraIdRes)
310         if (cameraId.isNullOrEmpty()) {
311             return null
312         }
313         val physicalCameraId = applicationContext.getString(cameraPhysicalIdRes)
314         val cameraLocation: IntArray = applicationContext.resources.getIntArray(cameraLocationRes)
315         val location: Point?
316         if (cameraLocation.size < 2) {
317             location = null
318         } else {
319             location = Point(cameraLocation[0], cameraLocation[1])
320         }
321         return CameraInfo(cameraId, physicalCameraId, location)
322     }
323 }
324