Loading packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/domain/interactor/BouncerMessageInteractorTest.kt +32 −5 Original line number Diff line number Diff line Loading @@ -39,7 +39,6 @@ import com.android.systemui.bouncer.data.repository.fakeKeyguardBouncerRepositor import com.android.systemui.bouncer.shared.model.BouncerMessageModel import com.android.systemui.coroutines.collectLastValue import com.android.systemui.deviceentry.domain.interactor.deviceEntryBiometricsAllowedInteractor import com.android.systemui.deviceentry.domain.interactor.deviceEntryFingerprintAuthInteractor import com.android.systemui.flags.SystemPropertiesHelper import com.android.systemui.keyguard.data.repository.fakeBiometricSettingsRepository import com.android.systemui.keyguard.data.repository.fakeDeviceEntryFaceAuthRepository Loading Loading @@ -114,8 +113,6 @@ class BouncerMessageInteractorTest : SysuiTestCase() { systemPropertiesHelper = systemPropertiesHelper, primaryBouncerInteractor = kosmos.primaryBouncerInteractor, facePropertyRepository = kosmos.fakeFacePropertyRepository, deviceEntryFingerprintAuthInteractor = kosmos.deviceEntryFingerprintAuthInteractor, faceAuthRepository = kosmos.fakeDeviceEntryFaceAuthRepository, securityModel = securityModel, deviceEntryBiometricsAllowedInteractor = kosmos.deviceEntryBiometricsAllowedInteractor, Loading Loading @@ -217,7 +214,7 @@ class BouncerMessageInteractorTest : SysuiTestCase() { fun resetMessageBackToDefault_faceAuthRestarts() = testScope.runTest { init() verify(updateMonitor).registerCallback(keyguardUpdateMonitorCaptor.capture()) captureKeyguardUpdateMonitorCallback() val bouncerMessage by collectLastValue(underTest.bouncerMessage) underTest.setFaceAcquisitionMessage("not empty") Loading @@ -240,7 +237,7 @@ class BouncerMessageInteractorTest : SysuiTestCase() { fun faceRestartDoesNotResetFingerprintMessage() = testScope.runTest { init() verify(updateMonitor).registerCallback(keyguardUpdateMonitorCaptor.capture()) captureKeyguardUpdateMonitorCallback() val bouncerMessage by collectLastValue(underTest.bouncerMessage) underTest.setFingerprintAcquisitionMessage("not empty") Loading Loading @@ -336,6 +333,32 @@ class BouncerMessageInteractorTest : SysuiTestCase() { assertThat(lockoutMessage?.secondaryMessage?.message).isNull() } @Test fun faceLockoutThenFaceFailure_doesNotUpdateMessage() = testScope.runTest { init() captureKeyguardUpdateMonitorCallback() val bouncerMessage by collectLastValue(underTest.bouncerMessage) kosmos.fakeBiometricSettingsRepository.setIsFaceAuthEnrolledAndEnabled(true) kosmos.fakeDeviceEntryFaceAuthRepository.setLockedOut(true) runCurrent() assertThat(primaryResMessage(bouncerMessage)) .isEqualTo("Unlock with PIN or fingerprint") assertThat(secondaryResMessage(bouncerMessage)) .isEqualTo("Can’t unlock with face. Too many attempts.") // WHEN face failure comes in during lockout keyguardUpdateMonitorCaptor.value.onBiometricAuthFailed(BiometricSourceType.FACE) // THEN lockout message does NOT update to face failure message assertThat(primaryResMessage(bouncerMessage)) .isEqualTo("Unlock with PIN or fingerprint") assertThat(secondaryResMessage(bouncerMessage)) .isEqualTo("Can’t unlock with face. Too many attempts.") } @Test fun onFaceLockoutStateChange_whenFaceIsNotEnrolled_isANoop() = testScope.runTest { Loading Loading @@ -629,6 +652,10 @@ class BouncerMessageInteractorTest : SysuiTestCase() { } } private fun captureKeyguardUpdateMonitorCallback() { verify(updateMonitor).registerCallback(keyguardUpdateMonitorCaptor.capture()) } companion object { private const val PRIMARY_USER_ID = 0 private val PRIMARY_USER = Loading packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/DeviceEntryFingerprintAuthRepositoryTest.kt +14 −0 Original line number Diff line number Diff line Loading @@ -369,4 +369,18 @@ class DeviceEntryFingerprintAuthRepositoryTest : SysuiTestCase() { invokeOnCallback { it.onStrongAuthStateChanged(0) } assertThat(shouldUpdateIndicatorVisibility).isTrue() } @Test fun isLockedOut_initialStateFalse() = testScope.runTest { whenever(keyguardUpdateMonitor.isFingerprintLockedOut).thenReturn(false) assertThat(underTest.isLockedOut.value).isEqualTo(false) } @Test fun isLockedOut_initialStateTrue() = testScope.runTest { whenever(keyguardUpdateMonitor.isFingerprintLockedOut).thenReturn(true) assertThat(underTest.isLockedOut.value).isEqualTo(true) } } packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerMessageInteractor.kt +13 −6 Original line number Diff line number Diff line Loading @@ -33,9 +33,7 @@ import com.android.systemui.bouncer.shared.model.BouncerMessageStrings import com.android.systemui.bouncer.shared.model.Message import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.deviceentry.data.repository.DeviceEntryFaceAuthRepository import com.android.systemui.deviceentry.domain.interactor.DeviceEntryBiometricsAllowedInteractor import com.android.systemui.deviceentry.domain.interactor.DeviceEntryFingerprintAuthInteractor import com.android.systemui.flags.SystemPropertiesHelper import com.android.systemui.keyguard.data.repository.BiometricSettingsRepository import com.android.systemui.keyguard.data.repository.TrustRepository Loading Loading @@ -75,8 +73,6 @@ constructor( primaryBouncerInteractor: PrimaryBouncerInteractor, @Application private val applicationScope: CoroutineScope, private val facePropertyRepository: FacePropertyRepository, private val deviceEntryFingerprintAuthInteractor: DeviceEntryFingerprintAuthInteractor, faceAuthRepository: DeviceEntryFaceAuthRepository, private val securityModel: KeyguardSecurityModel, deviceEntryBiometricsAllowedInteractor: DeviceEntryBiometricsAllowedInteractor, ) { Loading @@ -97,6 +93,17 @@ constructor( private val kumCallback = object : KeyguardUpdateMonitorCallback() { override fun onBiometricAuthFailed(biometricSourceType: BiometricSourceType?) { // Only show the biometric failure messages if the biometric is NOT locked out. // If the biometric is locked out, rely on the lock out message to show // the lockout message & don't override it with the failure message. if ( (biometricSourceType == BiometricSourceType.FACE && deviceEntryBiometricsAllowedInteractor.isFaceLockedOut.value) || (biometricSourceType == BiometricSourceType.FINGERPRINT && deviceEntryBiometricsAllowedInteractor.isFingerprintLockedOut.value) ) { return } repository.setMessage( when (biometricSourceType) { BiometricSourceType.FINGERPRINT -> Loading Loading @@ -159,8 +166,8 @@ constructor( biometricSettingsRepository.authenticationFlags, trustRepository.isCurrentUserTrustManaged, isAnyBiometricsEnabledAndEnrolled, deviceEntryFingerprintAuthInteractor.isLockedOut, faceAuthRepository.isLockedOut, deviceEntryBiometricsAllowedInteractor.isFingerprintLockedOut, deviceEntryBiometricsAllowedInteractor.isFaceLockedOut, isFingerprintAuthCurrentlyAllowedOnBouncer, ::Septuple ) Loading packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryBiometricsAllowedInteractor.kt +17 −4 Original line number Diff line number Diff line Loading @@ -22,6 +22,7 @@ import com.android.systemui.dagger.SysUISingleton import javax.inject.Inject import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.flatMapLatest import kotlinx.coroutines.flow.flowOf Loading @@ -44,17 +45,29 @@ constructor( biometricSettingsInteractor: DeviceEntryBiometricSettingsInteractor, facePropertyRepository: FacePropertyRepository, ) { /** * Whether face is locked out due to too many failed face attempts. This currently includes * whether face is not allowed based on other biometric lockouts; however does not include if * face isn't allowed due to other strong or primary authentication requirements. */ val isFaceLockedOut: StateFlow<Boolean> = deviceEntryFaceAuthInteractor.isLockedOut private val isStrongFaceAuth: Flow<Boolean> = facePropertyRepository.sensorInfo.map { it?.strength == SensorStrength.STRONG } private val isStrongFaceAuthLockedOut: Flow<Boolean> = combine(isStrongFaceAuth, deviceEntryFaceAuthInteractor.isLockedOut) { isStrongFaceAuth, isFaceAuthLockedOut -> combine(isStrongFaceAuth, isFaceLockedOut) { isStrongFaceAuth, isFaceAuthLockedOut -> isStrongFaceAuth && isFaceAuthLockedOut } /** * Whether fingerprint is locked out due to too many failed fingerprint attempts. This does NOT * include whether fingerprint is not allowed based on other biometric lockouts nor if * fingerprint isn't allowed due to other strong or primary authentication requirements. */ val isFingerprintLockedOut: StateFlow<Boolean> = deviceEntryFingerprintAuthInteractor.isLockedOut /** * Whether fingerprint authentication is currently allowed for the user. This is true if the * user has fingerprint auth enabled, enrolled, it is not disabled by any security timeouts by Loading @@ -64,7 +77,7 @@ constructor( */ val isFingerprintAuthCurrentlyAllowed: Flow<Boolean> = combine( deviceEntryFingerprintAuthInteractor.isLockedOut, isFingerprintLockedOut, biometricSettingsInteractor.fingerprintAuthCurrentlyAllowed, isStrongFaceAuthLockedOut, ) { fpLockedOut, fpAllowedBySettings, strongAuthFaceAuthLockedOut -> Loading packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFingerprintAuthInteractor.kt +1 −1 Original line number Diff line number Diff line Loading @@ -54,7 +54,7 @@ constructor( val authenticationStatus: Flow<FingerprintAuthenticationStatus> = repository.authenticationStatus val isLockedOut: Flow<Boolean> = repository.isLockedOut val isLockedOut: StateFlow<Boolean> = repository.isLockedOut val fingerprintFailure: Flow<FailFingerprintAuthenticationStatus> = repository.authenticationStatus.filterIsInstance<FailFingerprintAuthenticationStatus>() Loading Loading
packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/domain/interactor/BouncerMessageInteractorTest.kt +32 −5 Original line number Diff line number Diff line Loading @@ -39,7 +39,6 @@ import com.android.systemui.bouncer.data.repository.fakeKeyguardBouncerRepositor import com.android.systemui.bouncer.shared.model.BouncerMessageModel import com.android.systemui.coroutines.collectLastValue import com.android.systemui.deviceentry.domain.interactor.deviceEntryBiometricsAllowedInteractor import com.android.systemui.deviceentry.domain.interactor.deviceEntryFingerprintAuthInteractor import com.android.systemui.flags.SystemPropertiesHelper import com.android.systemui.keyguard.data.repository.fakeBiometricSettingsRepository import com.android.systemui.keyguard.data.repository.fakeDeviceEntryFaceAuthRepository Loading Loading @@ -114,8 +113,6 @@ class BouncerMessageInteractorTest : SysuiTestCase() { systemPropertiesHelper = systemPropertiesHelper, primaryBouncerInteractor = kosmos.primaryBouncerInteractor, facePropertyRepository = kosmos.fakeFacePropertyRepository, deviceEntryFingerprintAuthInteractor = kosmos.deviceEntryFingerprintAuthInteractor, faceAuthRepository = kosmos.fakeDeviceEntryFaceAuthRepository, securityModel = securityModel, deviceEntryBiometricsAllowedInteractor = kosmos.deviceEntryBiometricsAllowedInteractor, Loading Loading @@ -217,7 +214,7 @@ class BouncerMessageInteractorTest : SysuiTestCase() { fun resetMessageBackToDefault_faceAuthRestarts() = testScope.runTest { init() verify(updateMonitor).registerCallback(keyguardUpdateMonitorCaptor.capture()) captureKeyguardUpdateMonitorCallback() val bouncerMessage by collectLastValue(underTest.bouncerMessage) underTest.setFaceAcquisitionMessage("not empty") Loading @@ -240,7 +237,7 @@ class BouncerMessageInteractorTest : SysuiTestCase() { fun faceRestartDoesNotResetFingerprintMessage() = testScope.runTest { init() verify(updateMonitor).registerCallback(keyguardUpdateMonitorCaptor.capture()) captureKeyguardUpdateMonitorCallback() val bouncerMessage by collectLastValue(underTest.bouncerMessage) underTest.setFingerprintAcquisitionMessage("not empty") Loading Loading @@ -336,6 +333,32 @@ class BouncerMessageInteractorTest : SysuiTestCase() { assertThat(lockoutMessage?.secondaryMessage?.message).isNull() } @Test fun faceLockoutThenFaceFailure_doesNotUpdateMessage() = testScope.runTest { init() captureKeyguardUpdateMonitorCallback() val bouncerMessage by collectLastValue(underTest.bouncerMessage) kosmos.fakeBiometricSettingsRepository.setIsFaceAuthEnrolledAndEnabled(true) kosmos.fakeDeviceEntryFaceAuthRepository.setLockedOut(true) runCurrent() assertThat(primaryResMessage(bouncerMessage)) .isEqualTo("Unlock with PIN or fingerprint") assertThat(secondaryResMessage(bouncerMessage)) .isEqualTo("Can’t unlock with face. Too many attempts.") // WHEN face failure comes in during lockout keyguardUpdateMonitorCaptor.value.onBiometricAuthFailed(BiometricSourceType.FACE) // THEN lockout message does NOT update to face failure message assertThat(primaryResMessage(bouncerMessage)) .isEqualTo("Unlock with PIN or fingerprint") assertThat(secondaryResMessage(bouncerMessage)) .isEqualTo("Can’t unlock with face. Too many attempts.") } @Test fun onFaceLockoutStateChange_whenFaceIsNotEnrolled_isANoop() = testScope.runTest { Loading Loading @@ -629,6 +652,10 @@ class BouncerMessageInteractorTest : SysuiTestCase() { } } private fun captureKeyguardUpdateMonitorCallback() { verify(updateMonitor).registerCallback(keyguardUpdateMonitorCaptor.capture()) } companion object { private const val PRIMARY_USER_ID = 0 private val PRIMARY_USER = Loading
packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/DeviceEntryFingerprintAuthRepositoryTest.kt +14 −0 Original line number Diff line number Diff line Loading @@ -369,4 +369,18 @@ class DeviceEntryFingerprintAuthRepositoryTest : SysuiTestCase() { invokeOnCallback { it.onStrongAuthStateChanged(0) } assertThat(shouldUpdateIndicatorVisibility).isTrue() } @Test fun isLockedOut_initialStateFalse() = testScope.runTest { whenever(keyguardUpdateMonitor.isFingerprintLockedOut).thenReturn(false) assertThat(underTest.isLockedOut.value).isEqualTo(false) } @Test fun isLockedOut_initialStateTrue() = testScope.runTest { whenever(keyguardUpdateMonitor.isFingerprintLockedOut).thenReturn(true) assertThat(underTest.isLockedOut.value).isEqualTo(true) } }
packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerMessageInteractor.kt +13 −6 Original line number Diff line number Diff line Loading @@ -33,9 +33,7 @@ import com.android.systemui.bouncer.shared.model.BouncerMessageStrings import com.android.systemui.bouncer.shared.model.Message import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.deviceentry.data.repository.DeviceEntryFaceAuthRepository import com.android.systemui.deviceentry.domain.interactor.DeviceEntryBiometricsAllowedInteractor import com.android.systemui.deviceentry.domain.interactor.DeviceEntryFingerprintAuthInteractor import com.android.systemui.flags.SystemPropertiesHelper import com.android.systemui.keyguard.data.repository.BiometricSettingsRepository import com.android.systemui.keyguard.data.repository.TrustRepository Loading Loading @@ -75,8 +73,6 @@ constructor( primaryBouncerInteractor: PrimaryBouncerInteractor, @Application private val applicationScope: CoroutineScope, private val facePropertyRepository: FacePropertyRepository, private val deviceEntryFingerprintAuthInteractor: DeviceEntryFingerprintAuthInteractor, faceAuthRepository: DeviceEntryFaceAuthRepository, private val securityModel: KeyguardSecurityModel, deviceEntryBiometricsAllowedInteractor: DeviceEntryBiometricsAllowedInteractor, ) { Loading @@ -97,6 +93,17 @@ constructor( private val kumCallback = object : KeyguardUpdateMonitorCallback() { override fun onBiometricAuthFailed(biometricSourceType: BiometricSourceType?) { // Only show the biometric failure messages if the biometric is NOT locked out. // If the biometric is locked out, rely on the lock out message to show // the lockout message & don't override it with the failure message. if ( (biometricSourceType == BiometricSourceType.FACE && deviceEntryBiometricsAllowedInteractor.isFaceLockedOut.value) || (biometricSourceType == BiometricSourceType.FINGERPRINT && deviceEntryBiometricsAllowedInteractor.isFingerprintLockedOut.value) ) { return } repository.setMessage( when (biometricSourceType) { BiometricSourceType.FINGERPRINT -> Loading Loading @@ -159,8 +166,8 @@ constructor( biometricSettingsRepository.authenticationFlags, trustRepository.isCurrentUserTrustManaged, isAnyBiometricsEnabledAndEnrolled, deviceEntryFingerprintAuthInteractor.isLockedOut, faceAuthRepository.isLockedOut, deviceEntryBiometricsAllowedInteractor.isFingerprintLockedOut, deviceEntryBiometricsAllowedInteractor.isFaceLockedOut, isFingerprintAuthCurrentlyAllowedOnBouncer, ::Septuple ) Loading
packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryBiometricsAllowedInteractor.kt +17 −4 Original line number Diff line number Diff line Loading @@ -22,6 +22,7 @@ import com.android.systemui.dagger.SysUISingleton import javax.inject.Inject import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.flatMapLatest import kotlinx.coroutines.flow.flowOf Loading @@ -44,17 +45,29 @@ constructor( biometricSettingsInteractor: DeviceEntryBiometricSettingsInteractor, facePropertyRepository: FacePropertyRepository, ) { /** * Whether face is locked out due to too many failed face attempts. This currently includes * whether face is not allowed based on other biometric lockouts; however does not include if * face isn't allowed due to other strong or primary authentication requirements. */ val isFaceLockedOut: StateFlow<Boolean> = deviceEntryFaceAuthInteractor.isLockedOut private val isStrongFaceAuth: Flow<Boolean> = facePropertyRepository.sensorInfo.map { it?.strength == SensorStrength.STRONG } private val isStrongFaceAuthLockedOut: Flow<Boolean> = combine(isStrongFaceAuth, deviceEntryFaceAuthInteractor.isLockedOut) { isStrongFaceAuth, isFaceAuthLockedOut -> combine(isStrongFaceAuth, isFaceLockedOut) { isStrongFaceAuth, isFaceAuthLockedOut -> isStrongFaceAuth && isFaceAuthLockedOut } /** * Whether fingerprint is locked out due to too many failed fingerprint attempts. This does NOT * include whether fingerprint is not allowed based on other biometric lockouts nor if * fingerprint isn't allowed due to other strong or primary authentication requirements. */ val isFingerprintLockedOut: StateFlow<Boolean> = deviceEntryFingerprintAuthInteractor.isLockedOut /** * Whether fingerprint authentication is currently allowed for the user. This is true if the * user has fingerprint auth enabled, enrolled, it is not disabled by any security timeouts by Loading @@ -64,7 +77,7 @@ constructor( */ val isFingerprintAuthCurrentlyAllowed: Flow<Boolean> = combine( deviceEntryFingerprintAuthInteractor.isLockedOut, isFingerprintLockedOut, biometricSettingsInteractor.fingerprintAuthCurrentlyAllowed, isStrongFaceAuthLockedOut, ) { fpLockedOut, fpAllowedBySettings, strongAuthFaceAuthLockedOut -> Loading
packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFingerprintAuthInteractor.kt +1 −1 Original line number Diff line number Diff line Loading @@ -54,7 +54,7 @@ constructor( val authenticationStatus: Flow<FingerprintAuthenticationStatus> = repository.authenticationStatus val isLockedOut: Flow<Boolean> = repository.isLockedOut val isLockedOut: StateFlow<Boolean> = repository.isLockedOut val fingerprintFailure: Flow<FailFingerprintAuthenticationStatus> = repository.authenticationStatus.filterIsInstance<FailFingerprintAuthenticationStatus>() Loading