diff options
7 files changed, 275 insertions, 210 deletions
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepository.kt index 6db21b2c1e9e..233acd90aee3 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepository.kt @@ -54,13 +54,13 @@ import com.android.systemui.log.table.TableLogBuffer import com.android.systemui.statusbar.phone.KeyguardBypassController import com.android.systemui.user.data.model.SelectionStatus import com.android.systemui.user.data.repository.UserRepository +import com.google.errorprone.annotations.CompileTimeConstant import java.io.PrintWriter import java.util.Arrays import java.util.stream.Collectors import javax.inject.Inject import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.Job import kotlinx.coroutines.channels.awaitClose import kotlinx.coroutines.delay @@ -112,33 +112,27 @@ interface DeviceEntryFaceAuthRepository { fun setLockedOut(isLockedOut: Boolean) /** - * Cancel current face authentication and prevent it from running until [resumeFaceAuth] is - * invoked. - */ - fun pauseFaceAuth() - - /** - * Allow face auth paused using [pauseFaceAuth] to run again. The next invocation to - * [authenticate] will run as long as other gating conditions don't stop it from running. - */ - fun resumeFaceAuth() - - /** - * Trigger face authentication. + * Request face authentication or detection to be run. * * [uiEvent] provided should be logged whenever face authentication runs. Invocation should be * ignored if face authentication is already running. Results should be propagated through * [authenticationStatus] * * Run only face detection when [fallbackToDetection] is true and [canRunFaceAuth] is false. + * + * Method returns immediately and the face auth request is processed as soon as possible. */ - suspend fun authenticate(uiEvent: FaceAuthUiEvent, fallbackToDetection: Boolean = false) + fun requestAuthenticate(uiEvent: FaceAuthUiEvent, fallbackToDetection: Boolean = false) /** Stop currently running face authentication or detection. */ fun cancel() } -@OptIn(ExperimentalCoroutinesApi::class) +private data class AuthenticationRequest( + val uiEvent: FaceAuthUiEvent, + val fallbackToDetection: Boolean +) + @SysUISingleton class DeviceEntryFaceAuthRepositoryImpl @Inject @@ -171,6 +165,8 @@ constructor( private var faceAcquiredInfoIgnoreList: Set<Int> private var retryCount = 0 + private var pendingAuthenticateRequest = MutableStateFlow<AuthenticationRequest?>(null) + private var cancelNotReceivedHandlerJob: Job? = null private var halErrorRetryJob: Job? = null @@ -193,15 +189,6 @@ constructor( override val isAuthRunning: StateFlow<Boolean> get() = _isAuthRunning - private val faceAuthPaused = MutableStateFlow(false) - override fun pauseFaceAuth() { - faceAuthPaused.value = true - } - - override fun resumeFaceAuth() { - faceAuthPaused.value = false - } - private val keyguardSessionId: InstanceId? get() = sessionTracker.getSessionId(StatusBarManager.SESSION_KEYGUARD) @@ -213,6 +200,8 @@ constructor( override val isAuthenticated: Flow<Boolean> get() = _isAuthenticated + private var cancellationInProgress = MutableStateFlow(false) + override val isBypassEnabled: Flow<Boolean> = keyguardBypassController?.let { conflatedCallbackFlow { @@ -302,6 +291,7 @@ constructor( observeFaceDetectGatingChecks() observeFaceAuthResettingConditions() listenForSchedulingWatchdog() + processPendingAuthRequests() } else { canRunFaceAuth = MutableStateFlow(false).asStateFlow() canRunDetection = MutableStateFlow(false).asStateFlow() @@ -338,6 +328,7 @@ constructor( ) .onEach { anyOfThemIsTrue -> if (anyOfThemIsTrue) { + clearPendingAuthRequest("Resetting auth status") _isAuthenticated.value = false retryCount = 0 halErrorRetryJob?.cancel() @@ -346,6 +337,15 @@ constructor( .launchIn(applicationScope) } + private fun clearPendingAuthRequest(@CompileTimeConstant loggingContext: String) { + faceAuthLogger.clearingPendingAuthRequest( + loggingContext, + pendingAuthenticateRequest.value?.uiEvent, + pendingAuthenticateRequest.value?.fallbackToDetection + ) + pendingAuthenticateRequest.value = null + } + private fun observeFaceDetectGatingChecks() { canRunDetection .onEach { @@ -378,7 +378,6 @@ constructor( biometricSettingsRepository.isFaceAuthEnrolledAndEnabled, "isFaceAuthEnrolledAndEnabled" ), - Pair(faceAuthPaused.isFalse(), "faceAuthIsNotPaused"), Pair(keyguardRepository.isKeyguardGoingAway.isFalse(), "keyguardNotGoingAway"), Pair( keyguardRepository.wakefulness.map { it.isStartingToSleep() }.isFalse(), @@ -402,7 +401,13 @@ constructor( biometricSettingsRepository.isCurrentUserInLockdown.isFalse(), "userHasNotLockedDownDevice" ), - Pair(keyguardRepository.isKeyguardShowing, "isKeyguardShowing") + Pair(keyguardRepository.isKeyguardShowing, "isKeyguardShowing"), + Pair( + userRepository.selectedUser + .map { it.selectionStatus == SelectionStatus.SELECTION_IN_PROGRESS } + .isFalse(), + "userSwitchingInProgress" + ) ) } @@ -440,9 +445,6 @@ constructor( } _authenticationStatus.value = errorStatus _isAuthenticated.value = false - if (errorStatus.isCancellationError()) { - handleFaceCancellationError() - } if (errorStatus.isHardwareError()) { faceAuthLogger.hardwareError(errorStatus) handleFaceHardwareError() @@ -471,16 +473,6 @@ constructor( } } - private fun handleFaceCancellationError() { - applicationScope.launch { - faceAuthRequestedWhileCancellation?.let { - faceAuthLogger.launchingQueuedFaceAuthRequest(it) - authenticate(it) - } - faceAuthRequestedWhileCancellation = null - } - } - private fun handleFaceHardwareError() { if (retryCount < HAL_ERROR_RETRY_MAX) { retryCount++ @@ -490,7 +482,7 @@ constructor( delay(HAL_ERROR_RETRY_TIMEOUT) if (retryCount < HAL_ERROR_RETRY_MAX) { faceAuthLogger.attemptingRetryAfterHardwareError(retryCount) - authenticate( + requestAuthenticate( FaceAuthUiEvent.FACE_AUTH_TRIGGERED_RETRY_AFTER_HW_UNAVAILABLE, fallbackToDetection = false ) @@ -501,7 +493,7 @@ constructor( private fun onFaceAuthRequestCompleted() { cancelNotReceivedHandlerJob?.cancel() - cancellationInProgress = false + cancellationInProgress.value = false _isAuthRunning.value = false authCancellationSignal = null } @@ -512,24 +504,60 @@ constructor( _detectionStatus.value = FaceDetectionStatus(sensorId, userId, isStrong) } - private var cancellationInProgress = false - private var faceAuthRequestedWhileCancellation: FaceAuthUiEvent? = null + override fun requestAuthenticate(uiEvent: FaceAuthUiEvent, fallbackToDetection: Boolean) { + if (pendingAuthenticateRequest.value != null) { + faceAuthLogger.ignoredFaceAuthTrigger( + pendingAuthenticateRequest.value?.uiEvent, + "Previously queued trigger skipped due to new request" + ) + } + faceAuthLogger.queueingRequest(uiEvent, fallbackToDetection) + pendingAuthenticateRequest.value = AuthenticationRequest(uiEvent, fallbackToDetection) + } - override suspend fun authenticate(uiEvent: FaceAuthUiEvent, fallbackToDetection: Boolean) { + private fun processPendingAuthRequests() { + combine( + pendingAuthenticateRequest, + canRunFaceAuth, + canRunDetection, + cancellationInProgress, + ) { pending, canRunAuth, canRunDetect, cancelInProgress -> + if ( + pending != null && + !(canRunAuth || (canRunDetect && pending.fallbackToDetection)) || + cancelInProgress + ) { + faceAuthLogger.notProcessingRequestYet( + pending?.uiEvent, + canRunAuth, + canRunDetect, + cancelInProgress + ) + return@combine null + } else { + return@combine pending + } + } + .onEach { + it?.let { + faceAuthLogger.processingRequest(it.uiEvent, it.fallbackToDetection) + clearPendingAuthRequest("Authenticate was invoked") + authenticate(it.uiEvent, it.fallbackToDetection) + } + } + .flowOn(mainDispatcher) + .launchIn(applicationScope) + } + + private suspend fun authenticate(uiEvent: FaceAuthUiEvent, fallbackToDetection: Boolean) { if (_isAuthRunning.value) { faceAuthLogger.ignoredFaceAuthTrigger(uiEvent, "face auth is currently running") return } - if (cancellationInProgress) { - faceAuthLogger.queuingRequestWhileCancelling( - faceAuthRequestedWhileCancellation, - uiEvent - ) - faceAuthRequestedWhileCancellation = uiEvent + if (cancellationInProgress.value) { + faceAuthLogger.ignoredFaceAuthTrigger(uiEvent, "cancellation in progress") return - } else { - faceAuthRequestedWhileCancellation = null } if (canRunFaceAuth.value) { @@ -553,12 +581,19 @@ constructor( FaceAuthenticateOptions.Builder().setUserId(currentUserId).build() ) } - } else if (fallbackToDetection && canRunDetection.value) { - faceAuthLogger.ignoredFaceAuthTrigger( - uiEvent, - "face auth gating check is false, falling back to detection." - ) - detect() + } else if (canRunDetection.value) { + if (fallbackToDetection) { + faceAuthLogger.ignoredFaceAuthTrigger( + uiEvent, + "face auth gating check is false, falling back to detection." + ) + detect() + } else { + faceAuthLogger.ignoredFaceAuthTrigger( + uiEvent = uiEvent, + "face auth gating check is false and fallback to detection is not requested" + ) + } } else { faceAuthLogger.ignoredFaceAuthTrigger( uiEvent, @@ -608,13 +643,13 @@ constructor( faceAuthLogger.cancelSignalNotReceived( _isAuthRunning.value, _isLockedOut.value, - cancellationInProgress, - faceAuthRequestedWhileCancellation + cancellationInProgress.value, + pendingAuthenticateRequest.value?.uiEvent ) _authenticationStatus.value = ErrorFaceAuthenticationStatus.cancelNotReceivedError() onFaceAuthRequestCompleted() } - cancellationInProgress = true + cancellationInProgress.value = true _isAuthRunning.value = false } @@ -647,9 +682,7 @@ constructor( " supportsFaceDetection: " + "${faceManager?.sensorPropertiesInternal?.firstOrNull()?.supportsFaceDetection}" ) - pw.println( - " faceAuthRequestedWhileCancellation: ${faceAuthRequestedWhileCancellation?.reason}" - ) + pw.println(" _pendingAuthenticateRequest: ${pendingAuthenticateRequest.value}") pw.println(" authCancellationSignal: $authCancellationSignal") pw.println(" detectCancellationSignal: $detectCancellationSignal") pw.println(" faceAcquiredInfoIgnoreList: $faceAcquiredInfoIgnoreList") diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/NoopDeviceEntryFaceAuthRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/NoopDeviceEntryFaceAuthRepository.kt index 46135fa89451..c8cb9e6aa0dd 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/NoopDeviceEntryFaceAuthRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/NoopDeviceEntryFaceAuthRepository.kt @@ -56,9 +56,6 @@ class NoopDeviceEntryFaceAuthRepository @Inject constructor() : DeviceEntryFaceA get() = emptyFlow() override fun setLockedOut(isLockedOut: Boolean) = Unit - override fun pauseFaceAuth() = Unit - - override fun resumeFaceAuth() = Unit /** * Trigger face authentication. @@ -69,7 +66,7 @@ class NoopDeviceEntryFaceAuthRepository @Inject constructor() : DeviceEntryFaceA * * Run only face detection when [fallbackToDetection] is true and [canRunFaceAuth] is false. */ - override suspend fun authenticate(uiEvent: FaceAuthUiEvent, fallbackToDetection: Boolean) {} + override fun requestAuthenticate(uiEvent: FaceAuthUiEvent, fallbackToDetection: Boolean) = Unit /** Stop currently running face authentication or detection. */ override fun cancel() {} diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/SystemUIKeyguardFaceAuthInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/SystemUIKeyguardFaceAuthInteractor.kt index ccc2080d8fee..f0df3a2e6a6f 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/SystemUIKeyguardFaceAuthInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/SystemUIKeyguardFaceAuthInteractor.kt @@ -52,8 +52,6 @@ import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.merge import kotlinx.coroutines.flow.onEach -import kotlinx.coroutines.launch -import kotlinx.coroutines.withContext import kotlinx.coroutines.yield /** @@ -144,14 +142,11 @@ constructor( .onEach { (previous, curr) -> val wasSwitching = previous.selectionStatus == SelectionStatus.SELECTION_IN_PROGRESS val isSwitching = curr.selectionStatus == SelectionStatus.SELECTION_IN_PROGRESS - if (!wasSwitching && isSwitching) { - repository.pauseFaceAuth() - } else if (wasSwitching && !isSwitching) { + if (wasSwitching && !isSwitching) { val lockoutMode = facePropertyRepository.getLockoutMode(curr.userInfo.id) repository.setLockedOut( lockoutMode == LockoutMode.PERMANENT || lockoutMode == LockoutMode.TIMED ) - repository.resumeFaceAuth() yield() runFaceAuth( FaceAuthUiEvent.FACE_AUTH_UPDATED_USER_SWITCHING, @@ -232,12 +227,8 @@ constructor( ) } else { faceAuthenticationStatusOverride.value = null - applicationScope.launch { - withContext(mainDispatcher) { - faceAuthenticationLogger.authRequested(uiEvent) - repository.authenticate(uiEvent, fallbackToDetection = fallbackToDetect) - } - } + faceAuthenticationLogger.authRequested(uiEvent) + repository.requestAuthenticate(uiEvent, fallbackToDetection = fallbackToDetect) } } else { faceAuthenticationLogger.ignoredFaceAuthTrigger( diff --git a/packages/SystemUI/src/com/android/systemui/log/FaceAuthenticationLogger.kt b/packages/SystemUI/src/com/android/systemui/log/FaceAuthenticationLogger.kt index 66067b11a18c..8143f99c4d0a 100644 --- a/packages/SystemUI/src/com/android/systemui/log/FaceAuthenticationLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/log/FaceAuthenticationLogger.kt @@ -29,36 +29,18 @@ class FaceAuthenticationLogger constructor( @FaceAuthLog private val logBuffer: LogBuffer, ) { - fun ignoredFaceAuthTrigger(uiEvent: FaceAuthUiEvent, ignoredReason: String) { + fun ignoredFaceAuthTrigger(uiEvent: FaceAuthUiEvent?, ignoredReason: String) { logBuffer.log( TAG, DEBUG, { - str1 = uiEvent.reason + str1 = "${uiEvent?.reason}" str2 = ignoredReason }, { "Ignoring trigger because $str2, Trigger reason: $str1" } ) } - fun queuingRequestWhileCancelling( - alreadyQueuedRequest: FaceAuthUiEvent?, - newRequest: FaceAuthUiEvent - ) { - logBuffer.log( - TAG, - DEBUG, - { - str1 = alreadyQueuedRequest?.reason - str2 = newRequest.reason - }, - { - "Face auth requested while previous request is being cancelled, " + - "already queued request: $str1 queueing the new request: $str2" - } - ) - } - fun authenticating(uiEvent: FaceAuthUiEvent) { logBuffer.log(TAG, DEBUG, { str1 = uiEvent.reason }, { "Running authenticate for $str1" }) } @@ -161,15 +143,6 @@ constructor( ) } - fun launchingQueuedFaceAuthRequest(faceAuthRequestedWhileCancellation: FaceAuthUiEvent?) { - logBuffer.log( - TAG, - DEBUG, - { str1 = "${faceAuthRequestedWhileCancellation?.reason}" }, - { "Received cancellation error and starting queued face auth request: $str1" } - ) - } - fun faceAuthSuccess(result: FaceManager.AuthenticationResult) { logBuffer.log( TAG, @@ -182,31 +155,10 @@ constructor( ) } - fun observedConditionChanged(newValue: Boolean, context: String) { - logBuffer.log( - TAG, - DEBUG, - { - bool1 = newValue - str1 = context - }, - { "Observed condition changed: $str1, new value: $bool1" } - ) - } - fun canFaceAuthRunChanged(canRun: Boolean) { logBuffer.log(TAG, DEBUG, { bool1 = canRun }, { "canFaceAuthRun value changed to $bool1" }) } - fun canRunDetectionChanged(canRunDetection: Boolean) { - logBuffer.log( - TAG, - DEBUG, - { bool1 = canRunDetection }, - { "canRunDetection value changed to $bool1" } - ) - } - fun cancellingFaceAuth() { logBuffer.log(TAG, DEBUG, "cancelling face auth because a gating condition became false") } @@ -236,7 +188,7 @@ constructor( logBuffer.log( TAG, DEBUG, - { str1 = "$uiEvent" }, + { str1 = uiEvent.reason }, { "Requesting face auth for trigger: $str1" } ) } @@ -269,4 +221,77 @@ constructor( fun faceLockedOut(@CompileTimeConstant reason: String) { logBuffer.log(TAG, DEBUG, "Face auth has been locked out: $reason") } + + fun queueingRequest(uiEvent: FaceAuthUiEvent, fallbackToDetection: Boolean) { + logBuffer.log( + TAG, + DEBUG, + { + str1 = "$uiEvent" + bool1 = fallbackToDetection + }, + { "Queueing $str1 request for face auth, fallbackToDetection: $bool1" } + ) + } + + fun notProcessingRequestYet( + uiEvent: FaceAuthUiEvent?, + canRunAuth: Boolean, + canRunDetect: Boolean, + cancelInProgress: Boolean + ) { + uiEvent?.let { + logBuffer.log( + TAG, + DEBUG, + { + str1 = uiEvent.reason + bool1 = canRunAuth + bool2 = canRunDetect + bool3 = cancelInProgress + }, + { + "Waiting to process request: reason: $str1, " + + "canRunAuth: $bool1, " + + "canRunDetect: $bool2, " + + "cancelInProgress: $bool3" + } + ) + } + } + + fun processingRequest(uiEvent: FaceAuthUiEvent?, fallbackToDetection: Boolean) { + logBuffer.log( + TAG, + DEBUG, + { + str1 = "${uiEvent?.reason}" + bool1 = fallbackToDetection + }, + { "Processing face auth request: $str1, fallbackToDetect: $bool1" } + ) + } + + fun clearingPendingAuthRequest( + @CompileTimeConstant loggingContext: String, + uiEvent: FaceAuthUiEvent?, + fallbackToDetection: Boolean? + ) { + uiEvent?.let { + logBuffer.log( + TAG, + DEBUG, + { + str1 = uiEvent.reason + str2 = "$fallbackToDetection" + str3 = loggingContext + }, + { + "Clearing pending auth: $str1, " + + "fallbackToDetection: $str2, " + + "reason: $str3" + } + ) + } + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepositoryTest.kt index a76c88579dca..6b194f243b2c 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepositoryTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepositoryTest.kt @@ -36,6 +36,7 @@ import com.android.internal.logging.InstanceId.fakeInstanceId import com.android.internal.logging.UiEventLogger import com.android.keyguard.FaceAuthUiEvent import com.android.keyguard.FaceAuthUiEvent.FACE_AUTH_TRIGGERED_ALTERNATE_BIOMETRIC_BOUNCER_SHOWN +import com.android.keyguard.FaceAuthUiEvent.FACE_AUTH_TRIGGERED_NOTIFICATION_PANEL_CLICKED import com.android.keyguard.FaceAuthUiEvent.FACE_AUTH_TRIGGERED_SWIPE_UP_ON_BOUNCER import com.android.keyguard.KeyguardUpdateMonitor import com.android.systemui.R @@ -285,7 +286,7 @@ class DeviceEntryFaceAuthRepositoryTest : SysuiTestCase() { allPreconditionsToRunFaceAuthAreTrue() FACE_AUTH_TRIGGERED_SWIPE_UP_ON_BOUNCER.extraInfo = 10 - underTest.authenticate(FACE_AUTH_TRIGGERED_SWIPE_UP_ON_BOUNCER) + underTest.requestAuthenticate(FACE_AUTH_TRIGGERED_SWIPE_UP_ON_BOUNCER) faceAuthenticateIsCalled() uiEventIsLogged(FACE_AUTH_TRIGGERED_SWIPE_UP_ON_BOUNCER) @@ -318,12 +319,12 @@ class DeviceEntryFaceAuthRepositoryTest : SysuiTestCase() { initCollectors() allPreconditionsToRunFaceAuthAreTrue() - underTest.authenticate(FACE_AUTH_TRIGGERED_SWIPE_UP_ON_BOUNCER) + underTest.requestAuthenticate(FACE_AUTH_TRIGGERED_SWIPE_UP_ON_BOUNCER) faceAuthenticateIsCalled() clearInvocations(faceManager) clearInvocations(uiEventLogger) - underTest.authenticate(FACE_AUTH_TRIGGERED_SWIPE_UP_ON_BOUNCER) + underTest.requestAuthenticate(FACE_AUTH_TRIGGERED_SWIPE_UP_ON_BOUNCER) verifyNoMoreInteractions(faceManager) verifyNoMoreInteractions(uiEventLogger) } @@ -335,7 +336,7 @@ class DeviceEntryFaceAuthRepositoryTest : SysuiTestCase() { verify(faceManager).addLockoutResetCallback(faceLockoutResetCallback.capture()) allPreconditionsToRunFaceAuthAreTrue() - underTest.authenticate(FACE_AUTH_TRIGGERED_SWIPE_UP_ON_BOUNCER) + underTest.requestAuthenticate(FACE_AUTH_TRIGGERED_SWIPE_UP_ON_BOUNCER) faceAuthenticateIsCalled() authenticationCallback.value.onAuthenticationError( @@ -389,7 +390,7 @@ class DeviceEntryFaceAuthRepositoryTest : SysuiTestCase() { initCollectors() allPreconditionsToRunFaceAuthAreTrue() - underTest.authenticate(FACE_AUTH_TRIGGERED_SWIPE_UP_ON_BOUNCER) + underTest.requestAuthenticate(FACE_AUTH_TRIGGERED_SWIPE_UP_ON_BOUNCER) faceAuthenticateIsCalled() var wasAuthCancelled = false @@ -443,7 +444,7 @@ class DeviceEntryFaceAuthRepositoryTest : SysuiTestCase() { initCollectors() allPreconditionsToRunFaceAuthAreTrue() - underTest.authenticate(FACE_AUTH_TRIGGERED_SWIPE_UP_ON_BOUNCER) + underTest.requestAuthenticate(FACE_AUTH_TRIGGERED_SWIPE_UP_ON_BOUNCER) faceAuthenticateIsCalled() // Enter cancelling state @@ -451,7 +452,7 @@ class DeviceEntryFaceAuthRepositoryTest : SysuiTestCase() { clearInvocations(faceManager) // Auth is while cancelling. - underTest.authenticate(FACE_AUTH_TRIGGERED_ALTERNATE_BIOMETRIC_BOUNCER_SHOWN) + underTest.requestAuthenticate(FACE_AUTH_TRIGGERED_ALTERNATE_BIOMETRIC_BOUNCER_SHOWN) // Auth is not started verifyNoMoreInteractions(faceManager) @@ -474,14 +475,14 @@ class DeviceEntryFaceAuthRepositoryTest : SysuiTestCase() { initCollectors() allPreconditionsToRunFaceAuthAreTrue() - underTest.authenticate(FACE_AUTH_TRIGGERED_SWIPE_UP_ON_BOUNCER) + underTest.requestAuthenticate(FACE_AUTH_TRIGGERED_SWIPE_UP_ON_BOUNCER) faceAuthenticateIsCalled() clearInvocations(faceManager) underTest.cancel() advanceTimeBy(DeviceEntryFaceAuthRepositoryImpl.DEFAULT_CANCEL_SIGNAL_TIMEOUT + 1) - underTest.authenticate(FACE_AUTH_TRIGGERED_SWIPE_UP_ON_BOUNCER) + underTest.requestAuthenticate(FACE_AUTH_TRIGGERED_SWIPE_UP_ON_BOUNCER) faceAuthenticateIsCalled() } @@ -492,7 +493,7 @@ class DeviceEntryFaceAuthRepositoryTest : SysuiTestCase() { allPreconditionsToRunFaceAuthAreTrue() val emittedValues by collectValues(underTest.authenticationStatus) - underTest.authenticate(FACE_AUTH_TRIGGERED_SWIPE_UP_ON_BOUNCER) + underTest.requestAuthenticate(FACE_AUTH_TRIGGERED_SWIPE_UP_ON_BOUNCER) underTest.cancel() advanceTimeBy(100) underTest.cancel() @@ -519,7 +520,7 @@ class DeviceEntryFaceAuthRepositoryTest : SysuiTestCase() { initCollectors() allPreconditionsToRunFaceAuthAreTrue() - underTest.authenticate(FACE_AUTH_TRIGGERED_SWIPE_UP_ON_BOUNCER) + underTest.requestAuthenticate(FACE_AUTH_TRIGGERED_SWIPE_UP_ON_BOUNCER) faceAuthenticateIsCalled() authenticationCallback.value.onAuthenticationHelp(9, "help msg") @@ -562,8 +563,26 @@ class DeviceEntryFaceAuthRepositoryTest : SysuiTestCase() { } @Test - fun authenticateDoesNotRunIfFaceAuthIsCurrentlyPaused() = - testScope.runTest { testGatingCheckForFaceAuth { underTest.pauseFaceAuth() } } + fun authenticateDoesNotRunIfUserSwitchingIsCurrentlyInProgress() = + testScope.runTest { + testGatingCheckForFaceAuth { + fakeUserRepository.setSelectedUserInfo( + primaryUser, + SelectionStatus.SELECTION_IN_PROGRESS + ) + } + } + + @Test + fun detectDoesNotRunIfUserSwitchingIsCurrentlyInProgress() = + testScope.runTest { + testGatingCheckForDetect { + fakeUserRepository.setSelectedUserInfo( + userInfo = primaryUser, + selectionStatus = SelectionStatus.SELECTION_IN_PROGRESS + ) + } + } @Test fun authenticateDoesNotRunIfKeyguardIsNotShowing() = @@ -582,12 +601,6 @@ class DeviceEntryFaceAuthRepositoryTest : SysuiTestCase() { testScope.runTest { testGatingCheckForFaceAuth { underTest.setLockedOut(true) } } @Test - fun authenticateDoesNotRunWhenUserIsCurrentlyTrusted() = - testScope.runTest { - testGatingCheckForFaceAuth { trustRepository.setCurrentUserTrusted(true) } - } - - @Test fun authenticateDoesNotRunWhenKeyguardIsGoingAway() = testScope.runTest { testGatingCheckForFaceAuth { keyguardRepository.setKeyguardGoingAway(true) } @@ -608,14 +621,6 @@ class DeviceEntryFaceAuthRepositoryTest : SysuiTestCase() { } @Test - fun authenticateDoesNotRunWhenFaceAuthIsNotCurrentlyAllowedToRun() = - testScope.runTest { - testGatingCheckForFaceAuth { - biometricSettingsRepository.setIsFaceAuthCurrentlyAllowed(false) - } - } - - @Test fun authenticateDoesNotRunWhenSecureCameraIsActive() = testScope.runTest { testGatingCheckForFaceAuth { @@ -672,7 +677,7 @@ class DeviceEntryFaceAuthRepositoryTest : SysuiTestCase() { // Flip one precondition to false. biometricSettingsRepository.setIsFaceAuthCurrentlyAllowed(false) assertThat(canFaceAuthRun()).isFalse() - underTest.authenticate( + underTest.requestAuthenticate( FACE_AUTH_TRIGGERED_SWIPE_UP_ON_BOUNCER, fallbackToDetection = true ) @@ -693,7 +698,7 @@ class DeviceEntryFaceAuthRepositoryTest : SysuiTestCase() { trustRepository.setCurrentUserTrusted(true) assertThat(canFaceAuthRun()).isFalse() - underTest.authenticate( + underTest.requestAuthenticate( FACE_AUTH_TRIGGERED_SWIPE_UP_ON_BOUNCER, fallbackToDetection = true ) @@ -884,10 +889,6 @@ class DeviceEntryFaceAuthRepositoryTest : SysuiTestCase() { } @Test - fun detectDoesNotRunWhenUserSwitchingInProgress() = - testScope.runTest { testGatingCheckForDetect { underTest.pauseFaceAuth() } } - - @Test fun detectDoesNotRunWhenKeyguardGoingAway() = testScope.runTest { testGatingCheckForDetect { keyguardRepository.setKeyguardGoingAway(true) } @@ -1075,6 +1076,28 @@ class DeviceEntryFaceAuthRepositoryTest : SysuiTestCase() { faceAuthenticateIsCalled() } + @Test + fun queuedAuthOnlyRequestShouldNotBeProcessedIfOnlyDetectionCanBeRun() = + testScope.runTest { + initCollectors() + allPreconditionsToRunFaceAuthAreTrue() + + // This will prevent auth from running but not detection + biometricSettingsRepository.setIsFaceAuthCurrentlyAllowed(false) + + runCurrent() + assertThat(canFaceAuthRun()).isFalse() + + underTest.requestAuthenticate(FACE_AUTH_TRIGGERED_NOTIFICATION_PANEL_CLICKED, false) + runCurrent() + + faceDetectIsNotCalled() + faceAuthenticateIsNotCalled() + + biometricSettingsRepository.setIsFaceAuthCurrentlyAllowed(true) + faceAuthenticateIsCalled() + } + private suspend fun TestScope.testGatingCheckForFaceAuth( gatingCheckModifier: suspend () -> Unit ) { @@ -1087,10 +1110,18 @@ class DeviceEntryFaceAuthRepositoryTest : SysuiTestCase() { // gating check doesn't allow face auth to run. assertThat(underTest.canRunFaceAuth.value).isFalse() + // request face auth just before gating conditions become true, this ensures any race + // conditions won't prevent face auth from running + underTest.requestAuthenticate(FACE_AUTH_TRIGGERED_SWIPE_UP_ON_BOUNCER, false) + faceAuthenticateIsNotCalled() + // flip the gating check back on. allPreconditionsToRunFaceAuthAreTrue() + assertThat(underTest.canRunFaceAuth.value).isTrue() - triggerFaceAuth(false) + faceAuthenticateIsCalled() + assertThat(authRunning()).isTrue() + cancellationSignal.value.setOnCancelListener { wasAuthCancelled = true } // Flip gating check off gatingCheckModifier() @@ -1101,13 +1132,17 @@ class DeviceEntryFaceAuthRepositoryTest : SysuiTestCase() { clearInvocations(faceManager) // Try auth again - underTest.authenticate(FACE_AUTH_TRIGGERED_SWIPE_UP_ON_BOUNCER) + underTest.requestAuthenticate(FACE_AUTH_TRIGGERED_SWIPE_UP_ON_BOUNCER) + + runCurrent() // Auth can't run again faceAuthenticateIsNotCalled() } - private suspend fun TestScope.testGatingCheckForDetect(gatingCheckModifier: () -> Unit) { + private suspend fun TestScope.testGatingCheckForDetect( + gatingCheckModifier: suspend () -> Unit + ) { initCollectors() allPreconditionsToRunFaceAuthAreTrue() @@ -1118,7 +1153,11 @@ class DeviceEntryFaceAuthRepositoryTest : SysuiTestCase() { assertThat(canFaceAuthRun()).isFalse() // Trigger authenticate with detection fallback - underTest.authenticate(FACE_AUTH_TRIGGERED_SWIPE_UP_ON_BOUNCER, fallbackToDetection = true) + underTest.requestAuthenticate( + FACE_AUTH_TRIGGERED_SWIPE_UP_ON_BOUNCER, + fallbackToDetection = true + ) + runCurrent() faceAuthenticateIsNotCalled() faceDetectIsCalled() @@ -1133,15 +1172,21 @@ class DeviceEntryFaceAuthRepositoryTest : SysuiTestCase() { clearInvocations(faceManager) // Try to run detect again - underTest.authenticate(FACE_AUTH_TRIGGERED_SWIPE_UP_ON_BOUNCER, fallbackToDetection = true) + underTest.requestAuthenticate( + FACE_AUTH_TRIGGERED_SWIPE_UP_ON_BOUNCER, + fallbackToDetection = true + ) // Detect won't run because preconditions are not true anymore. faceDetectIsNotCalled() } - private suspend fun triggerFaceAuth(fallbackToDetect: Boolean) { + private fun TestScope.triggerFaceAuth(fallbackToDetect: Boolean) { assertThat(canFaceAuthRun()).isTrue() - underTest.authenticate(FACE_AUTH_TRIGGERED_SWIPE_UP_ON_BOUNCER, fallbackToDetect) + underTest.requestAuthenticate(FACE_AUTH_TRIGGERED_SWIPE_UP_ON_BOUNCER, fallbackToDetect) + + runCurrent() + faceAuthenticateIsCalled() assertThat(authRunning()).isTrue() cancellationSignal.value.setOnCancelListener { wasAuthCancelled = true } @@ -1150,7 +1195,6 @@ class DeviceEntryFaceAuthRepositoryTest : SysuiTestCase() { private suspend fun TestScope.allPreconditionsToRunFaceAuthAreTrue() { verify(faceManager, atLeastOnce()) .addLockoutResetCallback(faceLockoutResetCallback.capture()) - underTest.resumeFaceAuth() trustRepository.setCurrentUserTrusted(false) keyguardRepository.setKeyguardGoingAway(false) keyguardRepository.setWakefulnessModel( @@ -1164,7 +1208,7 @@ class DeviceEntryFaceAuthRepositoryTest : SysuiTestCase() { biometricSettingsRepository.setIsFaceAuthSupportedInCurrentPosture(true) biometricSettingsRepository.setIsFaceAuthCurrentlyAllowed(true) biometricSettingsRepository.setIsUserInLockdown(false) - fakeUserRepository.setSelectedUserInfo(primaryUser) + fakeUserRepository.setSelectedUserInfo(primaryUser, SelectionStatus.SELECTION_COMPLETE) faceLockoutResetCallback.value.onLockoutReset(0) bouncerRepository.setAlternateVisible(true) keyguardRepository.setKeyguardShowing(true) @@ -1187,7 +1231,9 @@ class DeviceEntryFaceAuthRepositoryTest : SysuiTestCase() { private fun successResult() = FaceManager.AuthenticationResult(null, null, primaryUserId, false) - private fun faceDetectIsCalled() { + private fun TestScope.faceDetectIsCalled() { + runCurrent() + verify(faceManager) .detectFace( cancellationSignal.capture(), @@ -1196,7 +1242,9 @@ class DeviceEntryFaceAuthRepositoryTest : SysuiTestCase() { ) } - private fun faceAuthenticateIsCalled() { + private fun TestScope.faceAuthenticateIsCalled() { + runCurrent() + verify(faceManager) .authenticate( isNull(), @@ -1207,7 +1255,9 @@ class DeviceEntryFaceAuthRepositoryTest : SysuiTestCase() { ) } - private fun faceAuthenticateIsNotCalled() { + private fun TestScope.faceAuthenticateIsNotCalled() { + runCurrent() + verify(faceManager, never()) .authenticate( isNull(), diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardFaceAuthInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardFaceAuthInteractorTest.kt index da70a9ff036f..2ed9de26dfa3 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardFaceAuthInteractorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardFaceAuthInteractorTest.kt @@ -224,23 +224,7 @@ class KeyguardFaceAuthInteractorTest : SysuiTestCase() { } @Test - fun faceAuthIsPausedWhenUserSwitchingIsInProgress() = - testScope.runTest { - underTest.start() - - fakeUserRepository.setSelectedUserInfo(primaryUser, SelectionStatus.SELECTION_COMPLETE) - runCurrent() - fakeUserRepository.setSelectedUserInfo( - secondaryUser, - SelectionStatus.SELECTION_IN_PROGRESS - ) - runCurrent() - - assertThat(faceAuthRepository.isFaceAuthPaused()).isTrue() - } - - @Test - fun faceAuthIsUnpausedWhenUserSwitchingIsInComplete() = + fun faceAuthLockedOutStateIsUpdatedAfterUserSwitch() = testScope.runTest { underTest.start() @@ -251,7 +235,6 @@ class KeyguardFaceAuthInteractorTest : SysuiTestCase() { SelectionStatus.SELECTION_IN_PROGRESS ) runCurrent() - assertThat(faceAuthRepository.isFaceAuthPaused()).isTrue() bouncerRepository.setPrimaryShow(true) // New user is not locked out. @@ -262,7 +245,6 @@ class KeyguardFaceAuthInteractorTest : SysuiTestCase() { ) runCurrent() - assertThat(faceAuthRepository.isFaceAuthPaused()).isFalse() assertThat(faceAuthRepository.isLockedOut.value).isFalse() runCurrent() diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeDeviceEntryFaceAuthRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeDeviceEntryFaceAuthRepository.kt index 2b13dcabb640..322fb284dad7 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeDeviceEntryFaceAuthRepository.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeDeviceEntryFaceAuthRepository.kt @@ -56,20 +56,7 @@ class FakeDeviceEntryFaceAuthRepository : DeviceEntryFaceAuthRepository { _isLockedOut.value = isLockedOut } - private val faceAuthPaused = MutableStateFlow(false) - override fun pauseFaceAuth() { - faceAuthPaused.value = true - } - - override fun resumeFaceAuth() { - faceAuthPaused.value = false - } - - fun isFaceAuthPaused(): Boolean { - return faceAuthPaused.value - } - - override suspend fun authenticate(uiEvent: FaceAuthUiEvent, fallbackToDetection: Boolean) { + override fun requestAuthenticate(uiEvent: FaceAuthUiEvent, fallbackToDetection: Boolean) { _runningAuthRequest.value = uiEvent to fallbackToDetection _isAuthRunning.value = true } |