diff options
2 files changed, 70 insertions, 40 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 3cdff76881ee..6a0d5954fc44 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 @@ -56,6 +56,10 @@ 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.Job @@ -78,10 +82,6 @@ import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.launch import kotlinx.coroutines.withContext -import java.io.PrintWriter -import java.util.Arrays -import java.util.stream.Collectors -import javax.inject.Inject /** * API to run face authentication and detection for device entry / on keyguard (as opposed to the @@ -368,10 +368,12 @@ constructor( return arrayOf( Pair( and( - displayStateInteractor.isDefaultDisplayOff, - keyguardTransitionInteractor.isFinishedInStateWhere( - KeyguardState::deviceIsAwakeInState), - ).isFalse(), + displayStateInteractor.isDefaultDisplayOff, + keyguardTransitionInteractor.isFinishedInStateWhere( + KeyguardState::deviceIsAwakeInState + ), + ) + .isFalse(), // this can happen if an app is requesting for screen off, the display can // turn off without wakefulness.isStartingToSleepOrAsleep calls "displayIsNotOffWhileFullyTransitionedToAwake", @@ -381,10 +383,7 @@ constructor( "isFaceAuthEnrolledAndEnabled" ), Pair(keyguardRepository.isKeyguardGoingAway.isFalse(), "keyguardNotGoingAway"), - Pair( - powerInteractor.isAsleep.isFalse(), - "deviceNotAsleep" - ), + Pair(powerInteractor.isAsleep.isFalse(), "deviceNotAsleep"), Pair( keyguardInteractor.isSecureCameraActive .isFalse() @@ -430,10 +429,15 @@ constructor( private val faceAuthCallback = object : FaceManager.AuthenticationCallback() { override fun onAuthenticationFailed() { - _authenticationStatus.value = FailedFaceAuthenticationStatus() _isAuthenticated.value = false faceAuthLogger.authenticationFailed() - onFaceAuthRequestCompleted() + if (!_isLockedOut.value) { + // onAuthenticationError gets invoked before onAuthenticationFailed when the + // last auth attempt locks out face authentication. + // Skip updating the authentication status in such a scenario. + _authenticationStatus.value = FailedFaceAuthenticationStatus() + onFaceAuthRequestCompleted() + } } override fun onAuthenticationAcquired(acquireInfo: Int) { 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 819d08a2086e..9bb2434f84ac 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 @@ -43,7 +43,6 @@ import com.android.keyguard.FaceAuthUiEvent.FACE_AUTH_TRIGGERED_ALTERNATE_BIOMET 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.res.R import com.android.systemui.SysuiTestCase import com.android.systemui.biometrics.data.repository.FakeDisplayStateRepository import com.android.systemui.biometrics.data.repository.FakeFacePropertyRepository @@ -82,6 +81,7 @@ import com.android.systemui.power.domain.interactor.PowerInteractor import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.setAsleepForTest import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.setAwakeForTest import com.android.systemui.power.domain.interactor.PowerInteractorFactory +import com.android.systemui.res.R import com.android.systemui.statusbar.phone.FakeKeyguardStateController import com.android.systemui.statusbar.phone.KeyguardBypassController import com.android.systemui.user.data.model.SelectionStatus @@ -94,6 +94,8 @@ import com.android.systemui.util.mockito.whenever import com.android.systemui.util.time.FakeSystemClock import com.android.systemui.util.time.SystemClock import com.google.common.truth.Truth.assertThat +import java.io.PrintWriter +import java.io.StringWriter import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.test.StandardTestDispatcher import kotlinx.coroutines.test.TestDispatcher @@ -116,8 +118,6 @@ import org.mockito.Mockito.never import org.mockito.Mockito.verify import org.mockito.Mockito.verifyNoMoreInteractions import org.mockito.MockitoAnnotations -import java.io.PrintWriter -import java.io.StringWriter @OptIn(ExperimentalCoroutinesApi::class) @SmallTest @@ -195,9 +195,11 @@ class DeviceEntryFaceAuthRepositoryTest : SysuiTestCase() { } powerRepository = FakePowerRepository() - powerInteractor = PowerInteractorFactory.create( - repository = powerRepository, - ).powerInteractor + powerInteractor = + PowerInteractorFactory.create( + repository = powerRepository, + ) + .powerInteractor val withDeps = KeyguardInteractorFactory.create( @@ -210,10 +212,10 @@ class DeviceEntryFaceAuthRepositoryTest : SysuiTestCase() { keyguardTransitionRepository = FakeKeyguardTransitionRepository() keyguardTransitionInteractor = - KeyguardTransitionInteractorFactory.create( - scope = testScope.backgroundScope, - repository = keyguardTransitionRepository, - keyguardInteractor = keyguardInteractor, + KeyguardTransitionInteractorFactory.create( + scope = testScope.backgroundScope, + repository = keyguardTransitionRepository, + keyguardInteractor = keyguardInteractor, ) .keyguardTransitionInteractor @@ -635,11 +637,7 @@ class DeviceEntryFaceAuthRepositoryTest : SysuiTestCase() { @Test fun authenticateDoesNotRunWhenDeviceIsGoingToSleep() = - testScope.runTest { - testGatingCheckForFaceAuth { - powerInteractor.setAsleepForTest() - } - } + testScope.runTest { testGatingCheckForFaceAuth { powerInteractor.setAsleepForTest() } } @Test fun authenticateDoesNotRunWhenSecureCameraIsActive() = @@ -736,17 +734,21 @@ class DeviceEntryFaceAuthRepositoryTest : SysuiTestCase() { allPreconditionsToRunFaceAuthAreTrue() Log.i("TEST", "started waking") - keyguardTransitionRepository.sendTransitionStep(TransitionStep( + keyguardTransitionRepository.sendTransitionStep( + TransitionStep( from = KeyguardState.LOCKSCREEN, to = KeyguardState.OFF, transitionState = TransitionState.FINISHED, - )) + ) + ) runCurrent() - keyguardTransitionRepository.sendTransitionStep(TransitionStep( + keyguardTransitionRepository.sendTransitionStep( + TransitionStep( from = KeyguardState.OFF, to = KeyguardState.LOCKSCREEN, transitionState = TransitionState.STARTED, - )) + ) + ) runCurrent() Log.i("TEST", "sending display off") @@ -766,11 +768,13 @@ class DeviceEntryFaceAuthRepositoryTest : SysuiTestCase() { testScope.runTest { testGatingCheckForFaceAuth { powerInteractor.onFinishedWakingUp() - keyguardTransitionRepository.sendTransitionStep(TransitionStep( + keyguardTransitionRepository.sendTransitionStep( + TransitionStep( from = KeyguardState.OFF, to = KeyguardState.LOCKSCREEN, transitionState = TransitionState.FINISHED, - )) + ) + ) runCurrent() displayRepository.emit( @@ -919,11 +923,7 @@ class DeviceEntryFaceAuthRepositoryTest : SysuiTestCase() { @Test fun detectDoesNotRunWhenDeviceSleepingStartingToSleep() = - testScope.runTest { - testGatingCheckForDetect { - powerInteractor.setAsleepForTest() - } - } + testScope.runTest { testGatingCheckForDetect { powerInteractor.setAsleepForTest() } } @Test fun detectDoesNotRunWhenSecureCameraIsActive() = @@ -1114,6 +1114,32 @@ class DeviceEntryFaceAuthRepositoryTest : SysuiTestCase() { biometricSettingsRepository.setIsFaceAuthCurrentlyAllowed(true) faceAuthenticateIsCalled() } + @Test + fun authFailedCallAfterAuthLockedOutErrorShouldBeIgnored() = + testScope.runTest { + initCollectors() + allPreconditionsToRunFaceAuthAreTrue() + runCurrent() + assertThat(canFaceAuthRun()).isTrue() + + underTest.requestAuthenticate(FACE_AUTH_TRIGGERED_NOTIFICATION_PANEL_CLICKED, false) + runCurrent() + + faceAuthenticateIsCalled() + authenticationCallback.value.onAuthenticationError( + FACE_ERROR_LOCKOUT_PERMANENT, + "Too many attempts, face not available" + ) + + val lockoutError = authStatus() as ErrorFaceAuthenticationStatus + assertThat(lockedOut()).isTrue() + assertThat(lockoutError.isLockoutError()).isTrue() + + authenticationCallback.value.onAuthenticationFailed() + runCurrent() + + assertThat(authStatus()).isEqualTo(lockoutError) + } private suspend fun TestScope.testGatingCheckForFaceAuth( gatingCheckModifier: suspend () -> Unit |