Loading packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/data/repository/DeviceEntryFaceAuthRepositoryTest.kt +36 −0 Original line number Diff line number Diff line Loading @@ -155,6 +155,7 @@ class DeviceEntryFaceAuthRepositoryTest : SysuiTestCase() { private lateinit var authStatus: FlowValue<FaceAuthenticationStatus?> private lateinit var detectStatus: FlowValue<FaceDetectionStatus?> private lateinit var authRunning: FlowValue<Boolean?> private lateinit var detectRunning: FlowValue<Boolean?> private lateinit var bypassEnabled: FlowValue<Boolean?> private lateinit var lockedOut: FlowValue<Boolean?> private lateinit var canFaceAuthRun: FlowValue<Boolean?> Loading Loading @@ -377,6 +378,38 @@ class DeviceEntryFaceAuthRepositoryTest : SysuiTestCase() { .isEqualTo(AUTHENTICATE_REASON_NOTIFICATION_PANEL_CLICKED) } @Test fun faceDetectionRunsAndSucceeds_detectRunningStateUpdates() = testScope.runTest { whenever(faceManager.sensorPropertiesInternal) .thenReturn(listOf(createFaceSensorProperties(supportsFaceDetection = true))) underTest = createDeviceEntryFaceAuthRepositoryImpl() initCollectors() underTest.detect(FACE_AUTH_TRIGGERED_NOTIFICATION_PANEL_CLICKED) faceDetectIsCalled() assertThat(detectRunning()).isTrue() detectionCallback.value.onFaceDetected(1, 1, true) assertThat(detectRunning()).isFalse() } @Test fun faceDetectionRunsAndCancels_detectRunningStateUpdates() = testScope.runTest { whenever(faceManager.sensorPropertiesInternal) .thenReturn(listOf(createFaceSensorProperties(supportsFaceDetection = true))) underTest = createDeviceEntryFaceAuthRepositoryImpl() initCollectors() underTest.detect(FACE_AUTH_TRIGGERED_NOTIFICATION_PANEL_CLICKED) faceDetectIsCalled() assertThat(detectRunning()).isTrue() underTest.cancel() assertThat(detectRunning()).isFalse() } @Test fun faceDetectDoesNotRunIfDetectionIsNotSupported() = testScope.runTest { Loading @@ -390,6 +423,7 @@ class DeviceEntryFaceAuthRepositoryTest : SysuiTestCase() { verify(faceManager, never()) .detectFace(any(), any(), any(FaceAuthenticateOptions::class.java)) assertThat(detectRunning()).isFalse() } @Test Loading Loading @@ -786,6 +820,7 @@ class DeviceEntryFaceAuthRepositoryTest : SysuiTestCase() { assertThat(authStatus()).isNull() assertThat(detectStatus()).isNull() assertThat(authRunning()).isNotNull() assertThat(detectRunning()).isNotNull() assertThat(bypassEnabled()).isNotNull() assertThat(lockedOut()).isNotNull() assertThat(canFaceAuthRun()).isNotNull() Loading Loading @@ -1388,6 +1423,7 @@ class DeviceEntryFaceAuthRepositoryTest : SysuiTestCase() { authStatus = collectLastValue(underTest.authenticationStatus) detectStatus = collectLastValue(underTest.detectionStatus) authRunning = collectLastValue(underTest.isAuthRunning) detectRunning = collectLastValue(underTest.isDetectRunning) lockedOut = collectLastValue(underTest.isLockedOut) canFaceAuthRun = collectLastValue(underTest.canRunFaceAuth) authenticated = collectLastValue(underTest.isAuthenticated) Loading packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFaceAuthInteractorTest.kt +15 −1 Original line number Diff line number Diff line Loading @@ -189,11 +189,12 @@ class DeviceEntryFaceAuthInteractorTest : SysuiTestCase() { } @Test fun whenFaceIsLockedOutAnyAttemptsToTriggerFaceAuthMustProvideLockoutError() = fun whenFaceIsLockedOutAndNonBypassAnyAttemptsToTriggerFaceAuthMustProvideLockoutError() = testScope.runTest { underTest.start() val authenticationStatus = collectLastValue(underTest.authenticationStatus) faceAuthRepository.setLockedOut(true) kosmos.fakeDeviceEntryFaceAuthRepository.isBypassEnabled.value = false underTest.onDeviceLifted() Loading @@ -204,6 +205,19 @@ class DeviceEntryFaceAuthInteractorTest : SysuiTestCase() { assertThat(faceAuthRepository.runningAuthRequest.value).isNull() } @Test fun whenFaceIsLockedOutAndBypass_DetectRuns() = testScope.runTest { underTest.start() val authenticationStatus = collectLastValue(underTest.authenticationStatus) faceAuthRepository.setLockedOut(true) kosmos.fakeDeviceEntryFaceAuthRepository.isBypassEnabled.value = true underTest.onDeviceLifted() assertThat(faceAuthRepository.runningAuthRequest.value).isNotNull() } @Test fun faceAuthIsRequestedWhenLockscreenBecomesVisibleFromAodState() = testScope.runTest { Loading packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java +3 −1 Original line number Diff line number Diff line Loading @@ -1310,7 +1310,9 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, CoreSt */ @Deprecated public boolean isFaceDetectionRunning() { return getFaceAuthInteractor() != null && getFaceAuthInteractor().isRunning(); return getFaceAuthInteractor() != null && (getFaceAuthInteractor().isAuthRunning() || getFaceAuthInteractor().isDetectRunning()); } private @Nullable DeviceEntryFaceAuthInteractor getFaceAuthInteractor() { Loading packages/SystemUI/src/com/android/systemui/deviceentry/data/repository/DeviceEntryFaceAuthRepository.kt +39 −23 Original line number Diff line number Diff line Loading @@ -113,8 +113,11 @@ interface DeviceEntryFaceAuthRepository { /** Current state of whether face authentication is running. */ val isAuthRunning: StateFlow<Boolean> /** Current state of whether face detection is running. */ val isDetectRunning: StateFlow<Boolean> /** Whether bypass is currently enabled */ val isBypassEnabled: Flow<Boolean> val isBypassEnabled: StateFlow<Boolean> /** Set whether face authentication should be locked out or not */ fun setLockedOut(isLockedOut: Boolean) Loading Loading @@ -197,6 +200,9 @@ constructor( override val isAuthRunning: StateFlow<Boolean> get() = _isAuthRunning private val _isDetectRunning = MutableStateFlow(false) override val isDetectRunning: StateFlow<Boolean> = _isDetectRunning private val keyguardSessionId: InstanceId? get() = sessionTracker.getSessionId(StatusBarManager.SESSION_KEYGUARD) Loading @@ -209,8 +215,8 @@ constructor( private var cancellationInProgress = MutableStateFlow(false) override val isBypassEnabled: Flow<Boolean> = keyguardBypassController?.let { override val isBypassEnabled: StateFlow<Boolean> = (keyguardBypassController?.let { conflatedCallbackFlow { val callback = object : KeyguardBypassController.OnBypassStateChangedListener { Loading @@ -222,7 +228,12 @@ constructor( trySendWithFailureLogging(it.bypassEnabled, TAG, "BypassStateChanged") awaitClose { it.unregisterOnBypassStateChangedListener(callback) } } } ?: flowOf(false) } ?: flowOf(false)) .stateIn( scope = applicationScope, started = SharingStarted.WhileSubscribed(), initialValue = keyguardBypassController?.isBypassEnabled() ?: false, ) override fun setLockedOut(isLockedOut: Boolean) { _isLockedOut.value = isLockedOut Loading Loading @@ -397,14 +408,15 @@ constructor( .map { it.isTransitioning(to = Scenes.Gone) || it.isIdle(Scenes.Gone) } .isFalse() } else { (keyguardTransitionInteractor.isFinishedIn(KeyguardState.GONE) (keyguardTransitionInteractor .isFinishedIn(KeyguardState.GONE) .or( keyguardTransitionInteractor.isInTransition( Edge.create(to = Scenes.Gone), Edge.create(to = KeyguardState.GONE) Edge.create(to = KeyguardState.GONE), ) ) ).isFalse() )) .isFalse() }, "keyguardNotGoneOrTransitioningToGone", ), Loading Loading @@ -552,6 +564,7 @@ constructor( private val detectionCallback = FaceManager.FaceDetectionCallback { sensorId, userId, isStrong -> _isDetectRunning.value = false faceAuthLogger.faceDetected() _detectionStatus.value = FaceDetectionStatus(sensorId, userId, isStrong) } Loading Loading @@ -669,6 +682,7 @@ constructor( return } withContext(mainDispatcher) { _isDetectRunning.value = true // We always want to invoke face detect in the main thread. faceAuthLogger.faceDetectionStarted() detectCancellationSignal?.cancel() Loading @@ -688,12 +702,13 @@ constructor( get() = userRepository.getSelectedUserInfo().id private fun cancelDetection() { _isDetectRunning.value = false detectCancellationSignal?.cancel() detectCancellationSignal = null } override fun cancel() { if (authCancellationSignal == null) return if (authCancellationSignal == null && detectCancellationSignal == null) return authCancellationSignal?.cancel() cancelNotReceivedHandlerJob?.cancel() Loading @@ -711,6 +726,7 @@ constructor( } cancellationInProgress.value = true _isAuthRunning.value = false _isDetectRunning.value = false } companion object { Loading packages/SystemUI/src/com/android/systemui/deviceentry/data/repository/NoopDeviceEntryFaceAuthRepository.kt +4 −2 Original line number Diff line number Diff line Loading @@ -49,8 +49,10 @@ class NoopDeviceEntryFaceAuthRepository @Inject constructor() : DeviceEntryFaceA override val isAuthRunning: StateFlow<Boolean> = MutableStateFlow(false).asStateFlow() override val isBypassEnabled: Flow<Boolean> get() = emptyFlow() override val isDetectRunning: StateFlow<Boolean> = MutableStateFlow(false).asStateFlow() override val isBypassEnabled: StateFlow<Boolean> get() = MutableStateFlow(false) override fun setLockedOut(isLockedOut: Boolean) = Unit Loading Loading
packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/data/repository/DeviceEntryFaceAuthRepositoryTest.kt +36 −0 Original line number Diff line number Diff line Loading @@ -155,6 +155,7 @@ class DeviceEntryFaceAuthRepositoryTest : SysuiTestCase() { private lateinit var authStatus: FlowValue<FaceAuthenticationStatus?> private lateinit var detectStatus: FlowValue<FaceDetectionStatus?> private lateinit var authRunning: FlowValue<Boolean?> private lateinit var detectRunning: FlowValue<Boolean?> private lateinit var bypassEnabled: FlowValue<Boolean?> private lateinit var lockedOut: FlowValue<Boolean?> private lateinit var canFaceAuthRun: FlowValue<Boolean?> Loading Loading @@ -377,6 +378,38 @@ class DeviceEntryFaceAuthRepositoryTest : SysuiTestCase() { .isEqualTo(AUTHENTICATE_REASON_NOTIFICATION_PANEL_CLICKED) } @Test fun faceDetectionRunsAndSucceeds_detectRunningStateUpdates() = testScope.runTest { whenever(faceManager.sensorPropertiesInternal) .thenReturn(listOf(createFaceSensorProperties(supportsFaceDetection = true))) underTest = createDeviceEntryFaceAuthRepositoryImpl() initCollectors() underTest.detect(FACE_AUTH_TRIGGERED_NOTIFICATION_PANEL_CLICKED) faceDetectIsCalled() assertThat(detectRunning()).isTrue() detectionCallback.value.onFaceDetected(1, 1, true) assertThat(detectRunning()).isFalse() } @Test fun faceDetectionRunsAndCancels_detectRunningStateUpdates() = testScope.runTest { whenever(faceManager.sensorPropertiesInternal) .thenReturn(listOf(createFaceSensorProperties(supportsFaceDetection = true))) underTest = createDeviceEntryFaceAuthRepositoryImpl() initCollectors() underTest.detect(FACE_AUTH_TRIGGERED_NOTIFICATION_PANEL_CLICKED) faceDetectIsCalled() assertThat(detectRunning()).isTrue() underTest.cancel() assertThat(detectRunning()).isFalse() } @Test fun faceDetectDoesNotRunIfDetectionIsNotSupported() = testScope.runTest { Loading @@ -390,6 +423,7 @@ class DeviceEntryFaceAuthRepositoryTest : SysuiTestCase() { verify(faceManager, never()) .detectFace(any(), any(), any(FaceAuthenticateOptions::class.java)) assertThat(detectRunning()).isFalse() } @Test Loading Loading @@ -786,6 +820,7 @@ class DeviceEntryFaceAuthRepositoryTest : SysuiTestCase() { assertThat(authStatus()).isNull() assertThat(detectStatus()).isNull() assertThat(authRunning()).isNotNull() assertThat(detectRunning()).isNotNull() assertThat(bypassEnabled()).isNotNull() assertThat(lockedOut()).isNotNull() assertThat(canFaceAuthRun()).isNotNull() Loading Loading @@ -1388,6 +1423,7 @@ class DeviceEntryFaceAuthRepositoryTest : SysuiTestCase() { authStatus = collectLastValue(underTest.authenticationStatus) detectStatus = collectLastValue(underTest.detectionStatus) authRunning = collectLastValue(underTest.isAuthRunning) detectRunning = collectLastValue(underTest.isDetectRunning) lockedOut = collectLastValue(underTest.isLockedOut) canFaceAuthRun = collectLastValue(underTest.canRunFaceAuth) authenticated = collectLastValue(underTest.isAuthenticated) Loading
packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFaceAuthInteractorTest.kt +15 −1 Original line number Diff line number Diff line Loading @@ -189,11 +189,12 @@ class DeviceEntryFaceAuthInteractorTest : SysuiTestCase() { } @Test fun whenFaceIsLockedOutAnyAttemptsToTriggerFaceAuthMustProvideLockoutError() = fun whenFaceIsLockedOutAndNonBypassAnyAttemptsToTriggerFaceAuthMustProvideLockoutError() = testScope.runTest { underTest.start() val authenticationStatus = collectLastValue(underTest.authenticationStatus) faceAuthRepository.setLockedOut(true) kosmos.fakeDeviceEntryFaceAuthRepository.isBypassEnabled.value = false underTest.onDeviceLifted() Loading @@ -204,6 +205,19 @@ class DeviceEntryFaceAuthInteractorTest : SysuiTestCase() { assertThat(faceAuthRepository.runningAuthRequest.value).isNull() } @Test fun whenFaceIsLockedOutAndBypass_DetectRuns() = testScope.runTest { underTest.start() val authenticationStatus = collectLastValue(underTest.authenticationStatus) faceAuthRepository.setLockedOut(true) kosmos.fakeDeviceEntryFaceAuthRepository.isBypassEnabled.value = true underTest.onDeviceLifted() assertThat(faceAuthRepository.runningAuthRequest.value).isNotNull() } @Test fun faceAuthIsRequestedWhenLockscreenBecomesVisibleFromAodState() = testScope.runTest { Loading
packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java +3 −1 Original line number Diff line number Diff line Loading @@ -1310,7 +1310,9 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, CoreSt */ @Deprecated public boolean isFaceDetectionRunning() { return getFaceAuthInteractor() != null && getFaceAuthInteractor().isRunning(); return getFaceAuthInteractor() != null && (getFaceAuthInteractor().isAuthRunning() || getFaceAuthInteractor().isDetectRunning()); } private @Nullable DeviceEntryFaceAuthInteractor getFaceAuthInteractor() { Loading
packages/SystemUI/src/com/android/systemui/deviceentry/data/repository/DeviceEntryFaceAuthRepository.kt +39 −23 Original line number Diff line number Diff line Loading @@ -113,8 +113,11 @@ interface DeviceEntryFaceAuthRepository { /** Current state of whether face authentication is running. */ val isAuthRunning: StateFlow<Boolean> /** Current state of whether face detection is running. */ val isDetectRunning: StateFlow<Boolean> /** Whether bypass is currently enabled */ val isBypassEnabled: Flow<Boolean> val isBypassEnabled: StateFlow<Boolean> /** Set whether face authentication should be locked out or not */ fun setLockedOut(isLockedOut: Boolean) Loading Loading @@ -197,6 +200,9 @@ constructor( override val isAuthRunning: StateFlow<Boolean> get() = _isAuthRunning private val _isDetectRunning = MutableStateFlow(false) override val isDetectRunning: StateFlow<Boolean> = _isDetectRunning private val keyguardSessionId: InstanceId? get() = sessionTracker.getSessionId(StatusBarManager.SESSION_KEYGUARD) Loading @@ -209,8 +215,8 @@ constructor( private var cancellationInProgress = MutableStateFlow(false) override val isBypassEnabled: Flow<Boolean> = keyguardBypassController?.let { override val isBypassEnabled: StateFlow<Boolean> = (keyguardBypassController?.let { conflatedCallbackFlow { val callback = object : KeyguardBypassController.OnBypassStateChangedListener { Loading @@ -222,7 +228,12 @@ constructor( trySendWithFailureLogging(it.bypassEnabled, TAG, "BypassStateChanged") awaitClose { it.unregisterOnBypassStateChangedListener(callback) } } } ?: flowOf(false) } ?: flowOf(false)) .stateIn( scope = applicationScope, started = SharingStarted.WhileSubscribed(), initialValue = keyguardBypassController?.isBypassEnabled() ?: false, ) override fun setLockedOut(isLockedOut: Boolean) { _isLockedOut.value = isLockedOut Loading Loading @@ -397,14 +408,15 @@ constructor( .map { it.isTransitioning(to = Scenes.Gone) || it.isIdle(Scenes.Gone) } .isFalse() } else { (keyguardTransitionInteractor.isFinishedIn(KeyguardState.GONE) (keyguardTransitionInteractor .isFinishedIn(KeyguardState.GONE) .or( keyguardTransitionInteractor.isInTransition( Edge.create(to = Scenes.Gone), Edge.create(to = KeyguardState.GONE) Edge.create(to = KeyguardState.GONE), ) ) ).isFalse() )) .isFalse() }, "keyguardNotGoneOrTransitioningToGone", ), Loading Loading @@ -552,6 +564,7 @@ constructor( private val detectionCallback = FaceManager.FaceDetectionCallback { sensorId, userId, isStrong -> _isDetectRunning.value = false faceAuthLogger.faceDetected() _detectionStatus.value = FaceDetectionStatus(sensorId, userId, isStrong) } Loading Loading @@ -669,6 +682,7 @@ constructor( return } withContext(mainDispatcher) { _isDetectRunning.value = true // We always want to invoke face detect in the main thread. faceAuthLogger.faceDetectionStarted() detectCancellationSignal?.cancel() Loading @@ -688,12 +702,13 @@ constructor( get() = userRepository.getSelectedUserInfo().id private fun cancelDetection() { _isDetectRunning.value = false detectCancellationSignal?.cancel() detectCancellationSignal = null } override fun cancel() { if (authCancellationSignal == null) return if (authCancellationSignal == null && detectCancellationSignal == null) return authCancellationSignal?.cancel() cancelNotReceivedHandlerJob?.cancel() Loading @@ -711,6 +726,7 @@ constructor( } cancellationInProgress.value = true _isAuthRunning.value = false _isDetectRunning.value = false } companion object { Loading
packages/SystemUI/src/com/android/systemui/deviceentry/data/repository/NoopDeviceEntryFaceAuthRepository.kt +4 −2 Original line number Diff line number Diff line Loading @@ -49,8 +49,10 @@ class NoopDeviceEntryFaceAuthRepository @Inject constructor() : DeviceEntryFaceA override val isAuthRunning: StateFlow<Boolean> = MutableStateFlow(false).asStateFlow() override val isBypassEnabled: Flow<Boolean> get() = emptyFlow() override val isDetectRunning: StateFlow<Boolean> = MutableStateFlow(false).asStateFlow() override val isBypassEnabled: StateFlow<Boolean> get() = MutableStateFlow(false) override fun setLockedOut(isLockedOut: Boolean) = Unit Loading