Loading packages/SystemUI/src/com/android/systemui/biometrics/data/repository/FacePropertyRepository.kt +18 −6 Original line number Diff line number Diff line Loading @@ -50,6 +50,7 @@ import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.flatMapLatest import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.withContext Loading @@ -64,13 +65,16 @@ interface FacePropertyRepository { /** The current face sensor location in current device rotation */ val sensorLocation: StateFlow<Point?> /** The info of current available camera. */ val cameraInfo: StateFlow<CameraInfo?> } /** Describes a biometric sensor */ data class FaceSensorInfo(val id: Int, val strength: SensorStrength) /** Data class for camera info */ private data class CameraInfo( data class CameraInfo( /** The logical id of the camera */ val cameraId: String, /** The physical id of the camera */ Loading Loading @@ -124,7 +128,7 @@ constructor( private val cameraInfoList: List<CameraInfo> = loadCameraInfoList() private var currentPhysicalCameraId: String? = null private val defaultSensorLocation: StateFlow<Point?> = override val cameraInfo: StateFlow<CameraInfo?> = ConflatedCallbackFlow.conflatedCallbackFlow { val callback = object : CameraManager.AvailabilityCallback() { Loading @@ -142,7 +146,7 @@ constructor( physicalCameraId == it.cameraPhysicalId } trySendWithFailureLogging( cameraInfo?.cameraLocation, cameraInfo, TAG, "Update face sensor location to $cameraInfo." ) Loading @@ -168,7 +172,7 @@ constructor( } currentPhysicalCameraId = cameraInfo?.cameraPhysicalId trySendWithFailureLogging( cameraInfo?.cameraLocation, cameraInfo, TAG, "Update face sensor location to $cameraInfo." ) Loading @@ -181,8 +185,16 @@ constructor( .stateIn( applicationScope, started = SharingStarted.WhileSubscribed(), initialValue = if (cameraInfoList.isNotEmpty()) cameraInfoList[0].cameraLocation else null initialValue = if (cameraInfoList.isNotEmpty()) cameraInfoList[0] else null ) private val defaultSensorLocation: StateFlow<Point?> = cameraInfo .map { it?.cameraLocation } .stateIn( applicationScope, started = SharingStarted.WhileSubscribed(), initialValue = null ) override val sensorLocation: StateFlow<Point?> = Loading packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/SystemUIDeviceEntryFaceAuthInteractor.kt +12 −0 Original line number Diff line number Diff line Loading @@ -189,6 +189,18 @@ constructor( } } .launchIn(applicationScope) facePropertyRepository.cameraInfo .onEach { if (it != null && isRunning()) { repository.cancel() runFaceAuth( FaceAuthUiEvent.FACE_AUTH_CAMERA_AVAILABLE_CHANGED, fallbackToDetect = true ) } } .launchIn(applicationScope) } private suspend fun resetLockedOutState(currentUserId: Int) { Loading packages/SystemUI/src/com/android/systemui/deviceentry/shared/FaceAuthReason.kt +5 −1 Original line number Diff line number Diff line Loading @@ -31,6 +31,7 @@ import com.android.systemui.deviceentry.shared.InternalFaceAuthReasons.ALTERNATE import com.android.systemui.deviceentry.shared.InternalFaceAuthReasons.ASSISTANT_VISIBILITY_CHANGED import com.android.systemui.deviceentry.shared.InternalFaceAuthReasons.AUTH_REQUEST_DURING_CANCELLATION import com.android.systemui.deviceentry.shared.InternalFaceAuthReasons.BIOMETRIC_ENABLED import com.android.systemui.deviceentry.shared.InternalFaceAuthReasons.CAMERA_AVAILABLE_CHANGED import com.android.systemui.deviceentry.shared.InternalFaceAuthReasons.CAMERA_LAUNCHED import com.android.systemui.deviceentry.shared.InternalFaceAuthReasons.DEVICE_WOKEN_UP_ON_REACH_GESTURE import com.android.systemui.deviceentry.shared.InternalFaceAuthReasons.DISPLAY_OFF Loading Loading @@ -130,6 +131,7 @@ private object InternalFaceAuthReasons { "Face auth stopped because non strong biometric allowed changed" const val POSTURE_CHANGED = "Face auth started/stopped due to device posture changed." const val DISPLAY_OFF = "Face auth stopped due to display state OFF." const val CAMERA_AVAILABLE_CHANGED = "Face auth started due to the available camera changed" } /** Loading Loading @@ -221,7 +223,9 @@ constructor(private val id: Int, val reason: String, var extraInfo: Int = 0) : @UiEvent(doc = NON_STRONG_BIOMETRIC_ALLOWED_CHANGED) FACE_AUTH_NON_STRONG_BIOMETRIC_ALLOWED_CHANGED(1256, NON_STRONG_BIOMETRIC_ALLOWED_CHANGED), @UiEvent(doc = ACCESSIBILITY_ACTION) FACE_AUTH_ACCESSIBILITY_ACTION(1454, ACCESSIBILITY_ACTION), @UiEvent(doc = DISPLAY_OFF) FACE_AUTH_DISPLAY_OFF(1461, DISPLAY_OFF); @UiEvent(doc = DISPLAY_OFF) FACE_AUTH_DISPLAY_OFF(1461, DISPLAY_OFF), @UiEvent(doc = CAMERA_AVAILABLE_CHANGED) FACE_AUTH_CAMERA_AVAILABLE_CHANGED(1623, CAMERA_AVAILABLE_CHANGED); override fun getId(): Int = this.id Loading packages/SystemUI/tests/src/com/android/systemui/biometrics/data/repository/FacePropertyRepositoryImplTest.kt +29 −0 Original line number Diff line number Diff line Loading @@ -237,6 +237,35 @@ class FacePropertyRepositoryImplTest : SysuiTestCase() { } } @Test fun providesTheCameraInfoOnCameraAvailableChange() { testScope.runTest { runCurrent() collectLastValue(underTest.cameraInfo) verify(faceManager).addAuthenticatorsRegisteredCallback(callback.capture()) callback.value.onAllAuthenticatorsRegistered( listOf(createSensorProperties(1, SensorProperties.STRENGTH_STRONG)) ) runCurrent() verify(cameraManager) .registerAvailabilityCallback(any(Executor::class.java), cameraCallback.capture()) cameraCallback.value.onPhysicalCameraAvailable("0", PHYSICAL_CAMERA_ID_OUTER_FRONT) runCurrent() val cameraInfo by collectLastValue(underTest.cameraInfo) assertThat(cameraInfo) .isEqualTo( CameraInfo( "0", PHYSICAL_CAMERA_ID_OUTER_FRONT, Point(OUTER_FRONT_SENSOR_LOCATION[0], OUTER_FRONT_SENSOR_LOCATION[1]) ) ) } } private fun createSensorProperties(id: Int, strength: Int) = FaceSensorPropertiesInternal(id, strength, 0, emptyList(), 1, false, false, false) } packages/SystemUI/tests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFaceAuthInteractorTest.kt +42 −0 Original line number Diff line number Diff line Loading @@ -25,6 +25,7 @@ import androidx.test.filters.SmallTest import com.android.keyguard.keyguardUpdateMonitor import com.android.keyguard.trustManager import com.android.systemui.SysuiTestCase import com.android.systemui.biometrics.data.repository.CameraInfo import com.android.systemui.biometrics.data.repository.FaceSensorInfo import com.android.systemui.biometrics.data.repository.facePropertyRepository import com.android.systemui.biometrics.shared.model.LockoutMode Loading Loading @@ -490,6 +491,47 @@ class DeviceEntryFaceAuthInteractorTest : SysuiTestCase() { verify(trustManager).clearAllBiometricRecognized(eq(BiometricSourceType.FACE), anyInt()) } @Test fun faceAuthIsRequestedWhenAuthIsRunningWhileCameraInfoChanged() = testScope.runTest { facePropertyRepository.setCameraIno(null) underTest.start() faceAuthRepository.requestAuthenticate( FaceAuthUiEvent.FACE_AUTH_UPDATED_KEYGUARD_VISIBILITY_CHANGED, true ) facePropertyRepository.setCameraIno(CameraInfo("0", "1", null)) runCurrent() assertThat(faceAuthRepository.runningAuthRequest.value) .isEqualTo(Pair(FaceAuthUiEvent.FACE_AUTH_CAMERA_AVAILABLE_CHANGED, true)) } @Test fun faceAuthIsNotRequestedWhenNoAuthRunningWhileCameraInfoChanged() = testScope.runTest { facePropertyRepository.setCameraIno(null) underTest.start() facePropertyRepository.setCameraIno(CameraInfo("0", "1", null)) runCurrent() assertThat(faceAuthRepository.runningAuthRequest.value).isNull() } @Test fun faceAuthIsNotRequestedWhenAuthIsRunningWhileCameraInfoIsNull() = testScope.runTest { facePropertyRepository.setCameraIno(null) underTest.start() facePropertyRepository.setCameraIno(null) runCurrent() assertThat(faceAuthRepository.runningAuthRequest.value).isNull() } companion object { private const val primaryUserId = 1 private val primaryUser = UserInfo(primaryUserId, "test user", UserInfo.FLAG_PRIMARY) Loading Loading
packages/SystemUI/src/com/android/systemui/biometrics/data/repository/FacePropertyRepository.kt +18 −6 Original line number Diff line number Diff line Loading @@ -50,6 +50,7 @@ import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.flatMapLatest import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.withContext Loading @@ -64,13 +65,16 @@ interface FacePropertyRepository { /** The current face sensor location in current device rotation */ val sensorLocation: StateFlow<Point?> /** The info of current available camera. */ val cameraInfo: StateFlow<CameraInfo?> } /** Describes a biometric sensor */ data class FaceSensorInfo(val id: Int, val strength: SensorStrength) /** Data class for camera info */ private data class CameraInfo( data class CameraInfo( /** The logical id of the camera */ val cameraId: String, /** The physical id of the camera */ Loading Loading @@ -124,7 +128,7 @@ constructor( private val cameraInfoList: List<CameraInfo> = loadCameraInfoList() private var currentPhysicalCameraId: String? = null private val defaultSensorLocation: StateFlow<Point?> = override val cameraInfo: StateFlow<CameraInfo?> = ConflatedCallbackFlow.conflatedCallbackFlow { val callback = object : CameraManager.AvailabilityCallback() { Loading @@ -142,7 +146,7 @@ constructor( physicalCameraId == it.cameraPhysicalId } trySendWithFailureLogging( cameraInfo?.cameraLocation, cameraInfo, TAG, "Update face sensor location to $cameraInfo." ) Loading @@ -168,7 +172,7 @@ constructor( } currentPhysicalCameraId = cameraInfo?.cameraPhysicalId trySendWithFailureLogging( cameraInfo?.cameraLocation, cameraInfo, TAG, "Update face sensor location to $cameraInfo." ) Loading @@ -181,8 +185,16 @@ constructor( .stateIn( applicationScope, started = SharingStarted.WhileSubscribed(), initialValue = if (cameraInfoList.isNotEmpty()) cameraInfoList[0].cameraLocation else null initialValue = if (cameraInfoList.isNotEmpty()) cameraInfoList[0] else null ) private val defaultSensorLocation: StateFlow<Point?> = cameraInfo .map { it?.cameraLocation } .stateIn( applicationScope, started = SharingStarted.WhileSubscribed(), initialValue = null ) override val sensorLocation: StateFlow<Point?> = Loading
packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/SystemUIDeviceEntryFaceAuthInteractor.kt +12 −0 Original line number Diff line number Diff line Loading @@ -189,6 +189,18 @@ constructor( } } .launchIn(applicationScope) facePropertyRepository.cameraInfo .onEach { if (it != null && isRunning()) { repository.cancel() runFaceAuth( FaceAuthUiEvent.FACE_AUTH_CAMERA_AVAILABLE_CHANGED, fallbackToDetect = true ) } } .launchIn(applicationScope) } private suspend fun resetLockedOutState(currentUserId: Int) { Loading
packages/SystemUI/src/com/android/systemui/deviceentry/shared/FaceAuthReason.kt +5 −1 Original line number Diff line number Diff line Loading @@ -31,6 +31,7 @@ import com.android.systemui.deviceentry.shared.InternalFaceAuthReasons.ALTERNATE import com.android.systemui.deviceentry.shared.InternalFaceAuthReasons.ASSISTANT_VISIBILITY_CHANGED import com.android.systemui.deviceentry.shared.InternalFaceAuthReasons.AUTH_REQUEST_DURING_CANCELLATION import com.android.systemui.deviceentry.shared.InternalFaceAuthReasons.BIOMETRIC_ENABLED import com.android.systemui.deviceentry.shared.InternalFaceAuthReasons.CAMERA_AVAILABLE_CHANGED import com.android.systemui.deviceentry.shared.InternalFaceAuthReasons.CAMERA_LAUNCHED import com.android.systemui.deviceentry.shared.InternalFaceAuthReasons.DEVICE_WOKEN_UP_ON_REACH_GESTURE import com.android.systemui.deviceentry.shared.InternalFaceAuthReasons.DISPLAY_OFF Loading Loading @@ -130,6 +131,7 @@ private object InternalFaceAuthReasons { "Face auth stopped because non strong biometric allowed changed" const val POSTURE_CHANGED = "Face auth started/stopped due to device posture changed." const val DISPLAY_OFF = "Face auth stopped due to display state OFF." const val CAMERA_AVAILABLE_CHANGED = "Face auth started due to the available camera changed" } /** Loading Loading @@ -221,7 +223,9 @@ constructor(private val id: Int, val reason: String, var extraInfo: Int = 0) : @UiEvent(doc = NON_STRONG_BIOMETRIC_ALLOWED_CHANGED) FACE_AUTH_NON_STRONG_BIOMETRIC_ALLOWED_CHANGED(1256, NON_STRONG_BIOMETRIC_ALLOWED_CHANGED), @UiEvent(doc = ACCESSIBILITY_ACTION) FACE_AUTH_ACCESSIBILITY_ACTION(1454, ACCESSIBILITY_ACTION), @UiEvent(doc = DISPLAY_OFF) FACE_AUTH_DISPLAY_OFF(1461, DISPLAY_OFF); @UiEvent(doc = DISPLAY_OFF) FACE_AUTH_DISPLAY_OFF(1461, DISPLAY_OFF), @UiEvent(doc = CAMERA_AVAILABLE_CHANGED) FACE_AUTH_CAMERA_AVAILABLE_CHANGED(1623, CAMERA_AVAILABLE_CHANGED); override fun getId(): Int = this.id Loading
packages/SystemUI/tests/src/com/android/systemui/biometrics/data/repository/FacePropertyRepositoryImplTest.kt +29 −0 Original line number Diff line number Diff line Loading @@ -237,6 +237,35 @@ class FacePropertyRepositoryImplTest : SysuiTestCase() { } } @Test fun providesTheCameraInfoOnCameraAvailableChange() { testScope.runTest { runCurrent() collectLastValue(underTest.cameraInfo) verify(faceManager).addAuthenticatorsRegisteredCallback(callback.capture()) callback.value.onAllAuthenticatorsRegistered( listOf(createSensorProperties(1, SensorProperties.STRENGTH_STRONG)) ) runCurrent() verify(cameraManager) .registerAvailabilityCallback(any(Executor::class.java), cameraCallback.capture()) cameraCallback.value.onPhysicalCameraAvailable("0", PHYSICAL_CAMERA_ID_OUTER_FRONT) runCurrent() val cameraInfo by collectLastValue(underTest.cameraInfo) assertThat(cameraInfo) .isEqualTo( CameraInfo( "0", PHYSICAL_CAMERA_ID_OUTER_FRONT, Point(OUTER_FRONT_SENSOR_LOCATION[0], OUTER_FRONT_SENSOR_LOCATION[1]) ) ) } } private fun createSensorProperties(id: Int, strength: Int) = FaceSensorPropertiesInternal(id, strength, 0, emptyList(), 1, false, false, false) }
packages/SystemUI/tests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFaceAuthInteractorTest.kt +42 −0 Original line number Diff line number Diff line Loading @@ -25,6 +25,7 @@ import androidx.test.filters.SmallTest import com.android.keyguard.keyguardUpdateMonitor import com.android.keyguard.trustManager import com.android.systemui.SysuiTestCase import com.android.systemui.biometrics.data.repository.CameraInfo import com.android.systemui.biometrics.data.repository.FaceSensorInfo import com.android.systemui.biometrics.data.repository.facePropertyRepository import com.android.systemui.biometrics.shared.model.LockoutMode Loading Loading @@ -490,6 +491,47 @@ class DeviceEntryFaceAuthInteractorTest : SysuiTestCase() { verify(trustManager).clearAllBiometricRecognized(eq(BiometricSourceType.FACE), anyInt()) } @Test fun faceAuthIsRequestedWhenAuthIsRunningWhileCameraInfoChanged() = testScope.runTest { facePropertyRepository.setCameraIno(null) underTest.start() faceAuthRepository.requestAuthenticate( FaceAuthUiEvent.FACE_AUTH_UPDATED_KEYGUARD_VISIBILITY_CHANGED, true ) facePropertyRepository.setCameraIno(CameraInfo("0", "1", null)) runCurrent() assertThat(faceAuthRepository.runningAuthRequest.value) .isEqualTo(Pair(FaceAuthUiEvent.FACE_AUTH_CAMERA_AVAILABLE_CHANGED, true)) } @Test fun faceAuthIsNotRequestedWhenNoAuthRunningWhileCameraInfoChanged() = testScope.runTest { facePropertyRepository.setCameraIno(null) underTest.start() facePropertyRepository.setCameraIno(CameraInfo("0", "1", null)) runCurrent() assertThat(faceAuthRepository.runningAuthRequest.value).isNull() } @Test fun faceAuthIsNotRequestedWhenAuthIsRunningWhileCameraInfoIsNull() = testScope.runTest { facePropertyRepository.setCameraIno(null) underTest.start() facePropertyRepository.setCameraIno(null) runCurrent() assertThat(faceAuthRepository.runningAuthRequest.value).isNull() } companion object { private const val primaryUserId = 1 private val primaryUser = UserInfo(primaryUserId, "test user", UserInfo.FLAG_PRIMARY) Loading