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