Merge "Provide better API methods that can provide face/fp auth status" into udc-qpr-dev

This commit is contained in:
Chandru S
2023-08-16 18:47:34 +00:00
committed by Android (Google) Code Review
12 changed files with 433 additions and 290 deletions

View File

@@ -36,7 +36,6 @@ import com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_RESTART_FOR_MAINL
import com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_TIMEOUT
import com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_TRUSTAGENT_EXPIRED
import com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_USER_REQUEST
import com.android.keyguard.KeyguardUpdateMonitor
import com.android.systemui.R.string.bouncer_face_not_recognized
import com.android.systemui.R.string.keyguard_enter_password
import com.android.systemui.R.string.keyguard_enter_pattern
@@ -80,13 +79,14 @@ import com.android.systemui.R.string.kg_wrong_pin_try_again
import com.android.systemui.bouncer.shared.model.BouncerMessageModel
import com.android.systemui.bouncer.shared.model.Message
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.keyguard.data.repository.BiometricSettingsRepository
import javax.inject.Inject
@SysUISingleton
class BouncerMessageFactory
@Inject
constructor(
private val updateMonitor: KeyguardUpdateMonitor,
private val biometricSettingsRepository: BiometricSettingsRepository,
private val securityModel: KeyguardSecurityModel,
) {
@@ -99,7 +99,7 @@ constructor(
getBouncerMessage(
reason,
securityModel.getSecurityMode(userId),
updateMonitor.isUnlockingWithFingerprintAllowed
biometricSettingsRepository.isFingerprintAuthCurrentlyAllowed.value
)
return pair?.let {
BouncerMessageModel(

View File

@@ -123,20 +123,11 @@ constructor(
fingerprintAuthRepository: DeviceEntryFingerprintAuthRepository,
) : BouncerMessageRepository {
private val isFaceEnrolledAndEnabled =
and(
biometricSettingsRepository.isFaceAuthenticationEnabled,
biometricSettingsRepository.isFaceEnrolled
)
private val isFingerprintEnrolledAndEnabled =
and(
biometricSettingsRepository.isFingerprintEnabledByDevicePolicy,
biometricSettingsRepository.isFingerprintEnrolled
)
private val isAnyBiometricsEnabledAndEnrolled =
or(isFaceEnrolledAndEnabled, isFingerprintEnrolledAndEnabled)
or(
biometricSettingsRepository.isFaceAuthEnrolledAndEnabled,
biometricSettingsRepository.isFingerprintEnrolledAndEnabled,
)
private val wasRebootedForMainlineUpdate
get() = systemPropertiesHelper.get(SYS_BOOT_REASON_PROP) == REBOOT_MAINLINE_UPDATE
@@ -335,8 +326,5 @@ constructor(
}
}
private fun and(flow: Flow<Boolean>, anotherFlow: Flow<Boolean>) =
flow.combine(anotherFlow) { a, b -> a && b }
private fun or(flow: Flow<Boolean>, anotherFlow: Flow<Boolean>) =
flow.combine(anotherFlow) { a, b -> a || b }

View File

@@ -83,9 +83,7 @@ constructor(
fun canShowAlternateBouncerForFingerprint(): Boolean {
return bouncerRepository.alternateBouncerUIAvailable.value &&
biometricSettingsRepository.isFingerprintEnrolled.value &&
biometricSettingsRepository.isStrongBiometricAllowed.value &&
biometricSettingsRepository.isFingerprintEnabledByDevicePolicy.value &&
biometricSettingsRepository.isFingerprintAuthCurrentlyAllowed.value &&
!keyguardUpdateMonitor.isFingerprintLockedOut &&
!keyguardStateController.isUnlocked &&
!statusBarStateController.isDozing

View File

@@ -28,6 +28,9 @@ import com.android.internal.widget.LockPatternUtils
import com.android.systemui.Dumpable
import com.android.systemui.R
import com.android.systemui.biometrics.AuthController
import com.android.systemui.biometrics.data.repository.FacePropertyRepository
import com.android.systemui.biometrics.data.repository.FingerprintPropertyRepository
import com.android.systemui.biometrics.shared.model.SensorStrength
import com.android.systemui.broadcast.BroadcastDispatcher
import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging
import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
@@ -68,34 +71,32 @@ import kotlinx.coroutines.flow.transformLatest
* upstream changes.
*/
interface BiometricSettingsRepository {
/** Whether any fingerprints are enrolled for the current user. */
val isFingerprintEnrolled: StateFlow<Boolean>
/** Whether face authentication is enrolled for the current user. */
val isFaceEnrolled: Flow<Boolean>
/**
* If the current user can enter the device using fingerprint. This is true if user has enrolled
* fingerprints and fingerprint auth is not disabled through settings/device policy
*/
val isFingerprintEnrolledAndEnabled: StateFlow<Boolean>
/**
* Whether face authentication is enabled/disabled based on system settings like device policy,
* biometrics setting.
* If the current user can enter the device using fingerprint, right now.
*
* This returns true if there are no strong auth flags that restrict the user from using
* fingerprint and [isFingerprintEnrolledAndEnabled] is true
*/
val isFaceAuthenticationEnabled: Flow<Boolean>
val isFingerprintAuthCurrentlyAllowed: StateFlow<Boolean>
/**
* Whether the current user is allowed to use a strong biometric for device entry based on
* Android Security policies. If false, the user may be able to use primary authentication for
* device entry.
* If the current user can use face auth to enter the device. This is true when the user has
* face auth enrolled, and is enabled in settings/device policy.
*/
val isStrongBiometricAllowed: StateFlow<Boolean>
val isFaceAuthEnrolledAndEnabled: Flow<Boolean>
/**
* Whether the current user is allowed to use a convenience biometric for device entry based on
* Android Security policies. If false, the user may be able to use strong biometric or primary
* authentication for device entry.
* If the current user can use face auth to enter the device right now. This is true when
* [isFaceAuthEnrolledAndEnabled] is true and strong auth settings allow face auth to run and
* face auth is supported by the current device posture.
*/
val isNonStrongBiometricAllowed: StateFlow<Boolean>
/** Whether fingerprint feature is enabled for the current user by the DevicePolicy */
val isFingerprintEnabledByDevicePolicy: StateFlow<Boolean>
val isFaceAuthCurrentlyAllowed: Flow<Boolean>
/**
* Whether face authentication is supported for the current device posture. Face auth can be
@@ -130,6 +131,8 @@ constructor(
@Background backgroundDispatcher: CoroutineDispatcher,
biometricManager: BiometricManager?,
devicePostureRepository: DevicePostureRepository,
facePropertyRepository: FacePropertyRepository,
fingerprintPropertyRepository: FingerprintPropertyRepository,
dumpManager: DumpManager,
) : BiometricSettingsRepository, Dumpable {
@@ -165,7 +168,9 @@ constructor(
}
override fun dump(pw: PrintWriter, args: Array<String?>) {
pw.println("isFingerprintEnrolled=${isFingerprintEnrolled.value}")
pw.println("isFingerprintEnrolledAndEnabled=${isFingerprintEnrolledAndEnabled.value}")
pw.println("isFingerprintAuthCurrentlyAllowed=${isFingerprintAuthCurrentlyAllowed.value}")
pw.println("isNonStrongBiometricAllowed=${isNonStrongBiometricAllowed.value}")
pw.println("isStrongBiometricAllowed=${isStrongBiometricAllowed.value}")
pw.println("isFingerprintEnabledByDevicePolicy=${isFingerprintEnabledByDevicePolicy.value}")
}
@@ -180,7 +185,7 @@ constructor(
user = UserHandle.ALL
)
override val isFingerprintEnrolled: StateFlow<Boolean> =
private val isFingerprintEnrolled: Flow<Boolean> =
selectedUserId
.flatMapLatest { currentUserId ->
conflatedCallbackFlow {
@@ -211,7 +216,7 @@ constructor(
authController.isFingerprintEnrolled(userRepository.getSelectedUserInfo().id)
)
override val isFaceEnrolled: Flow<Boolean> =
private val isFaceEnrolled: Flow<Boolean> =
selectedUserId.flatMapLatest { selectedUserId: Int ->
conflatedCallbackFlow {
val callback =
@@ -245,14 +250,6 @@ constructor(
isFaceEnabledByBiometricsManager.map { biometricsEnabledForUser[userInfo.id] ?: false }
}
override val isFaceAuthenticationEnabled: Flow<Boolean>
get() =
combine(isFaceEnabledByBiometricsManagerForCurrentUser, isFaceEnabledByDevicePolicy) {
biometricsManagerSetting,
devicePolicySetting ->
biometricsManagerSetting && devicePolicySetting
}
private val isFaceEnabledByDevicePolicy: Flow<Boolean> =
combine(selectedUserId, devicePolicyChangedForAllUsers) { userId, _ ->
devicePolicyManager.isFaceDisabled(userId)
@@ -263,6 +260,13 @@ constructor(
.flowOn(backgroundDispatcher)
.distinctUntilChanged()
private val isFaceAuthenticationEnabled: Flow<Boolean> =
combine(isFaceEnabledByBiometricsManagerForCurrentUser, isFaceEnabledByDevicePolicy) {
biometricsManagerSetting,
devicePolicySetting ->
biometricsManagerSetting && devicePolicySetting
}
private val isFaceEnabledByBiometricsManager: Flow<Pair<Int, Boolean>> =
conflatedCallbackFlow {
val callback =
@@ -283,7 +287,7 @@ constructor(
// being registered.
.stateIn(scope, SharingStarted.Eagerly, Pair(0, false))
override val isStrongBiometricAllowed: StateFlow<Boolean> =
private val isStrongBiometricAllowed: StateFlow<Boolean> =
strongAuthTracker.isStrongBiometricAllowed.stateIn(
scope,
SharingStarted.Eagerly,
@@ -293,7 +297,7 @@ constructor(
)
)
override val isNonStrongBiometricAllowed: StateFlow<Boolean> =
private val isNonStrongBiometricAllowed: StateFlow<Boolean> =
strongAuthTracker.isNonStrongBiometricAllowed.stateIn(
scope,
SharingStarted.Eagerly,
@@ -303,7 +307,19 @@ constructor(
)
)
override val isFingerprintEnabledByDevicePolicy: StateFlow<Boolean> =
private val isFingerprintBiometricAllowed: Flow<Boolean> =
fingerprintPropertyRepository.strength.flatMapLatest {
if (it == SensorStrength.STRONG) isStrongBiometricAllowed
else isNonStrongBiometricAllowed
}
private val isFaceBiometricsAllowed: Flow<Boolean> =
facePropertyRepository.sensorInfo.flatMapLatest {
if (it?.strength == SensorStrength.STRONG) isStrongBiometricAllowed
else isNonStrongBiometricAllowed
}
private val isFingerprintEnabledByDevicePolicy: StateFlow<Boolean> =
selectedUserId
.flatMapLatest { userId ->
devicePolicyChangedForAllUsers
@@ -319,6 +335,25 @@ constructor(
userRepository.getSelectedUserInfo().id
)
)
override val isFingerprintEnrolledAndEnabled: StateFlow<Boolean> =
isFingerprintEnrolled
.and(isFingerprintEnabledByDevicePolicy)
.stateIn(scope, SharingStarted.Eagerly, false)
override val isFingerprintAuthCurrentlyAllowed: StateFlow<Boolean> =
isFingerprintEnrolledAndEnabled
.and(isFingerprintBiometricAllowed)
.stateIn(scope, SharingStarted.Eagerly, false)
override val isFaceAuthEnrolledAndEnabled: Flow<Boolean>
get() = isFaceAuthenticationEnabled.and(isFaceEnrolled)
override val isFaceAuthCurrentlyAllowed: Flow<Boolean>
get() =
isFaceAuthEnrolledAndEnabled
.and(isFaceBiometricsAllowed)
.and(isFaceAuthSupportedInCurrentPosture)
}
@OptIn(ExperimentalCoroutinesApi::class)

View File

@@ -26,8 +26,6 @@ import com.android.internal.logging.UiEventLogger
import com.android.keyguard.FaceAuthUiEvent
import com.android.systemui.Dumpable
import com.android.systemui.R
import com.android.systemui.biometrics.data.repository.FacePropertyRepository
import com.android.systemui.biometrics.shared.model.SensorStrength
import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor
import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging
import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
@@ -72,7 +70,6 @@ import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.filterNotNull
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.map
@@ -162,7 +159,6 @@ constructor(
@FaceAuthTableLog private val faceAuthLog: TableLogBuffer,
private val keyguardTransitionInteractor: KeyguardTransitionInteractor,
private val featureFlags: FeatureFlags,
facePropertyRepository: FacePropertyRepository,
dumpManager: DumpManager,
) : DeviceEntryFaceAuthRepository, Dumpable {
private var authCancellationSignal: CancellationSignal? = null
@@ -182,13 +178,6 @@ constructor(
override val detectionStatus: Flow<FaceDetectionStatus>
get() = _detectionStatus.filterNotNull()
private val isFaceBiometricsAllowed: Flow<Boolean> =
facePropertyRepository.sensorInfo.flatMapLatest {
if (it?.strength == SensorStrength.STRONG)
biometricSettingsRepository.isStrongBiometricAllowed
else biometricSettingsRepository.isNonStrongBiometricAllowed
}
private val _isLockedOut = MutableStateFlow(false)
override val isLockedOut: StateFlow<Boolean> = _isLockedOut
@@ -313,8 +302,10 @@ constructor(
canFaceAuthOrDetectRun(faceDetectLog),
logAndObserve(isBypassEnabled, "isBypassEnabled", faceDetectLog),
logAndObserve(
isFaceBiometricsAllowed.isFalse().or(trustRepository.isCurrentUserTrusted),
"biometricIsNotAllowedOrCurrentUserIsTrusted",
biometricSettingsRepository.isFaceAuthCurrentlyAllowed
.isFalse()
.or(trustRepository.isCurrentUserTrusted),
"faceAuthIsNotCurrentlyAllowedOrCurrentUserIsTrusted",
faceDetectLog
),
// We don't want to run face detect if fingerprint can be used to unlock the device
@@ -346,13 +337,8 @@ constructor(
private fun canFaceAuthOrDetectRun(tableLogBuffer: TableLogBuffer): Flow<Boolean> {
return listOf(
logAndObserve(
biometricSettingsRepository.isFaceEnrolled,
"isFaceEnrolled",
tableLogBuffer
),
logAndObserve(
biometricSettingsRepository.isFaceAuthenticationEnabled,
"isFaceAuthenticationEnabled",
biometricSettingsRepository.isFaceAuthEnrolledAndEnabled,
"isFaceAuthEnrolledAndEnabled",
tableLogBuffer
),
logAndObserve(faceAuthPaused.isFalse(), "faceAuthIsNotPaused", tableLogBuffer),
@@ -406,7 +392,11 @@ constructor(
"currentUserIsNotTrusted",
faceAuthLog
),
logAndObserve(isFaceBiometricsAllowed, "isFaceBiometricsAllowed", faceAuthLog),
logAndObserve(
biometricSettingsRepository.isFaceAuthCurrentlyAllowed,
"isFaceAuthCurrentlyAllowed",
faceAuthLog
),
logAndObserve(isAuthenticated.isFalse(), "faceNotAuthenticated", faceAuthLog),
)
.reduce(::and)

View File

@@ -24,13 +24,12 @@ import com.android.keyguard.KeyguardSecurityModel.SecurityMode.Password
import com.android.keyguard.KeyguardSecurityModel.SecurityMode.Pattern
import com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_DEFAULT
import com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_INCORRECT_PRIMARY_AUTH_INPUT
import com.android.keyguard.KeyguardUpdateMonitor
import com.android.systemui.SysuiTestCase
import com.android.systemui.bouncer.shared.model.BouncerMessageModel
import com.android.systemui.keyguard.data.repository.FakeBiometricSettingsRepository
import com.android.systemui.util.mockito.whenever
import com.google.common.truth.StringSubject
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.runTest
import org.junit.Before
@@ -39,13 +38,12 @@ import org.junit.runner.RunWith
import org.mockito.Mock
import org.mockito.MockitoAnnotations
@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
@RunWith(AndroidJUnit4::class)
class BouncerMessageFactoryTest : SysuiTestCase() {
private lateinit var underTest: BouncerMessageFactory
@Mock private lateinit var updateMonitor: KeyguardUpdateMonitor
@Mock private lateinit var biometricSettingsRepository: FakeBiometricSettingsRepository
@Mock private lateinit var securityModel: KeyguardSecurityModel
@@ -55,7 +53,8 @@ class BouncerMessageFactoryTest : SysuiTestCase() {
fun setUp() {
MockitoAnnotations.initMocks(this)
testScope = TestScope()
underTest = BouncerMessageFactory(updateMonitor, securityModel)
biometricSettingsRepository = FakeBiometricSettingsRepository()
underTest = BouncerMessageFactory(biometricSettingsRepository, securityModel)
}
@Test
@@ -167,7 +166,7 @@ class BouncerMessageFactoryTest : SysuiTestCase() {
secondaryMessageOverride: String? = null,
): BouncerMessageModel? {
whenever(securityModel.getSecurityMode(0)).thenReturn(mode)
whenever(updateMonitor.isUnlockingWithFingerprintAllowed).thenReturn(fpAuthAllowed)
biometricSettingsRepository.setIsFingerprintAuthCurrentlyAllowed(fpAuthAllowed)
return underTest.createFromPromptReason(
reason,

View File

@@ -101,14 +101,15 @@ class BouncerMessageRepositoryTest : SysuiTestCase() {
fingerprintRepository = FakeDeviceEntryFingerprintAuthRepository()
testScope = TestScope()
whenever(updateMonitor.isUnlockingWithFingerprintAllowed).thenReturn(false)
biometricSettingsRepository.setIsFingerprintAuthCurrentlyAllowed(false)
whenever(securityModel.getSecurityMode(PRIMARY_USER_ID)).thenReturn(PIN)
underTest =
BouncerMessageRepositoryImpl(
trustRepository = trustRepository,
biometricSettingsRepository = biometricSettingsRepository,
updateMonitor = updateMonitor,
bouncerMessageFactory = BouncerMessageFactory(updateMonitor, securityModel),
bouncerMessageFactory =
BouncerMessageFactory(biometricSettingsRepository, securityModel),
userRepository = userRepository,
fingerprintAuthRepository = fingerprintRepository,
systemPropertiesHelper = systemPropertiesHelper
@@ -222,8 +223,7 @@ class BouncerMessageRepositoryTest : SysuiTestCase() {
whenever(systemPropertiesHelper.get("sys.boot.reason.last"))
.thenReturn("reboot,mainline_update")
userRepository.setSelectedUserInfo(PRIMARY_USER)
biometricSettingsRepository.setFaceEnrolled(true)
biometricSettingsRepository.setIsFaceAuthEnabled(true)
biometricSettingsRepository.setIsFaceAuthEnrolledAndEnabled(true)
verifyMessagesForAuthFlag(
STRONG_AUTH_REQUIRED_AFTER_BOOT to
@@ -236,8 +236,8 @@ class BouncerMessageRepositoryTest : SysuiTestCase() {
testScope.runTest {
userRepository.setSelectedUserInfo(PRIMARY_USER)
trustRepository.setCurrentUserTrustManaged(false)
biometricSettingsRepository.setFaceEnrolled(false)
biometricSettingsRepository.setFingerprintEnrolled(false)
biometricSettingsRepository.setIsFaceAuthEnrolledAndEnabled(false)
biometricSettingsRepository.setIsFingerprintAuthEnrolledAndEnabled(false)
verifyMessagesForAuthFlag(
STRONG_AUTH_NOT_REQUIRED to null,
@@ -258,8 +258,8 @@ class BouncerMessageRepositoryTest : SysuiTestCase() {
fun authFlagsChanges_withTrustManaged_providesDifferentMessages() =
testScope.runTest {
userRepository.setSelectedUserInfo(PRIMARY_USER)
biometricSettingsRepository.setFaceEnrolled(false)
biometricSettingsRepository.setFingerprintEnrolled(false)
biometricSettingsRepository.setIsFaceAuthEnrolledAndEnabled(false)
biometricSettingsRepository.setIsFingerprintAuthEnrolledAndEnabled(false)
trustRepository.setCurrentUserTrustManaged(true)
@@ -290,10 +290,9 @@ class BouncerMessageRepositoryTest : SysuiTestCase() {
testScope.runTest {
userRepository.setSelectedUserInfo(PRIMARY_USER)
trustRepository.setCurrentUserTrustManaged(false)
biometricSettingsRepository.setFingerprintEnrolled(false)
biometricSettingsRepository.setIsFingerprintAuthEnrolledAndEnabled(false)
biometricSettingsRepository.setIsFaceAuthEnabled(true)
biometricSettingsRepository.setFaceEnrolled(true)
biometricSettingsRepository.setIsFaceAuthEnrolledAndEnabled(true)
verifyMessagesForAuthFlag(
STRONG_AUTH_NOT_REQUIRED to null,
@@ -320,11 +319,9 @@ class BouncerMessageRepositoryTest : SysuiTestCase() {
testScope.runTest {
userRepository.setSelectedUserInfo(PRIMARY_USER)
trustRepository.setCurrentUserTrustManaged(false)
biometricSettingsRepository.setIsFaceAuthEnabled(false)
biometricSettingsRepository.setFaceEnrolled(false)
biometricSettingsRepository.setIsFaceAuthEnrolledAndEnabled(false)
biometricSettingsRepository.setFingerprintEnrolled(true)
biometricSettingsRepository.setFingerprintEnabledByDevicePolicy(true)
biometricSettingsRepository.setIsFingerprintAuthEnrolledAndEnabled(true)
verifyMessagesForAuthFlag(
STRONG_AUTH_NOT_REQUIRED to null,

View File

@@ -94,9 +94,9 @@ class AlternateBouncerInteractorTest : SysuiTestCase() {
}
@Test
fun canShowAlternateBouncerForFingerprint_noFingerprintsEnrolled() {
fun canShowAlternateBouncerForFingerprint_ifFingerprintIsNotUsuallyAllowed() {
givenCanShowAlternateBouncer()
biometricSettingsRepository.setFingerprintEnrolled(false)
biometricSettingsRepository.setIsFingerprintAuthEnrolledAndEnabled(false)
assertFalse(underTest.canShowAlternateBouncerForFingerprint())
}
@@ -104,15 +104,7 @@ class AlternateBouncerInteractorTest : SysuiTestCase() {
@Test
fun canShowAlternateBouncerForFingerprint_strongBiometricNotAllowed() {
givenCanShowAlternateBouncer()
biometricSettingsRepository.setStrongBiometricAllowed(false)
assertFalse(underTest.canShowAlternateBouncerForFingerprint())
}
@Test
fun canShowAlternateBouncerForFingerprint_devicePolicyDoesNotAllowFingerprint() {
givenCanShowAlternateBouncer()
biometricSettingsRepository.setFingerprintEnabledByDevicePolicy(false)
biometricSettingsRepository.setIsFingerprintAuthCurrentlyAllowed(false)
assertFalse(underTest.canShowAlternateBouncerForFingerprint())
}
@@ -189,14 +181,13 @@ class AlternateBouncerInteractorTest : SysuiTestCase() {
private fun givenCanShowAlternateBouncer() {
bouncerRepository.setAlternateBouncerUIAvailable(true)
biometricSettingsRepository.setFingerprintEnrolled(true)
biometricSettingsRepository.setStrongBiometricAllowed(true)
biometricSettingsRepository.setFingerprintEnabledByDevicePolicy(true)
biometricSettingsRepository.setIsFingerprintAuthEnrolledAndEnabled(true)
biometricSettingsRepository.setIsFingerprintAuthCurrentlyAllowed(true)
whenever(keyguardUpdateMonitor.isFingerprintLockedOut).thenReturn(false)
whenever(keyguardStateController.isUnlocked).thenReturn(false)
}
private fun givenCannotShowAlternateBouncer() {
biometricSettingsRepository.setFingerprintEnrolled(false)
biometricSettingsRepository.setIsFingerprintAuthEnrolledAndEnabled(false)
}
}

View File

@@ -22,9 +22,8 @@ import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.keyguard.KeyguardSecurityModel
import com.android.keyguard.KeyguardSecurityModel.SecurityMode.PIN
import com.android.keyguard.KeyguardUpdateMonitor
import com.android.systemui.R.string.keyguard_enter_pin
import com.android.systemui.R.string.kg_too_many_failed_attempts_countdown
import com.android.systemui.R.string.kg_unlock_with_pin_or_fp
import com.android.systemui.SysuiTestCase
import com.android.systemui.bouncer.data.factory.BouncerMessageFactory
import com.android.systemui.bouncer.data.repository.FakeBouncerMessageRepository
@@ -34,11 +33,11 @@ import com.android.systemui.coroutines.FlowValue
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.flags.FakeFeatureFlags
import com.android.systemui.flags.Flags
import com.android.systemui.keyguard.data.repository.FakeBiometricSettingsRepository
import com.android.systemui.user.data.repository.FakeUserRepository
import com.android.systemui.util.mockito.KotlinArgumentCaptor
import com.android.systemui.util.mockito.whenever
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.runTest
import org.junit.Before
@@ -49,14 +48,13 @@ import org.mockito.Mock
import org.mockito.Mockito.verify
import org.mockito.MockitoAnnotations
@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
@TestableLooper.RunWithLooper(setAsMainLooper = true)
@RunWith(AndroidJUnit4::class)
class BouncerMessageInteractorTest : SysuiTestCase() {
@Mock private lateinit var securityModel: KeyguardSecurityModel
@Mock private lateinit var updateMonitor: KeyguardUpdateMonitor
@Mock private lateinit var biometricSettingsRepository: FakeBiometricSettingsRepository
@Mock private lateinit var countDownTimerUtil: CountDownTimerUtil
private lateinit var countDownTimerCallback: KotlinArgumentCaptor<CountDownTimerCallback>
private lateinit var underTest: BouncerMessageInteractor
@@ -73,10 +71,11 @@ class BouncerMessageInteractorTest : SysuiTestCase() {
userRepository.setUserInfos(listOf(PRIMARY_USER))
testScope = TestScope()
countDownTimerCallback = KotlinArgumentCaptor(CountDownTimerCallback::class.java)
biometricSettingsRepository = FakeBiometricSettingsRepository()
allowTestableLooperAsMainThread()
whenever(securityModel.getSecurityMode(PRIMARY_USER_ID)).thenReturn(PIN)
whenever(updateMonitor.isUnlockingWithFingerprintAllowed).thenReturn(false)
biometricSettingsRepository.setIsFingerprintAuthCurrentlyAllowed(true)
}
suspend fun TestScope.init() {
@@ -86,7 +85,7 @@ class BouncerMessageInteractorTest : SysuiTestCase() {
underTest =
BouncerMessageInteractor(
repository = repository,
factory = BouncerMessageFactory(updateMonitor, securityModel),
factory = BouncerMessageFactory(biometricSettingsRepository, securityModel),
userRepository = userRepository,
countDownTimerUtil = countDownTimerUtil,
featureFlags = featureFlags
@@ -151,7 +150,8 @@ class BouncerMessageInteractorTest : SysuiTestCase() {
underTest.setCustomMessage("not empty")
val customMessage = repository.customMessage
assertThat(customMessage.value!!.message!!.messageResId).isEqualTo(keyguard_enter_pin)
assertThat(customMessage.value!!.message!!.messageResId)
.isEqualTo(kg_unlock_with_pin_or_fp)
assertThat(customMessage.value!!.secondaryMessage!!.message).isEqualTo("not empty")
underTest.setCustomMessage(null)
@@ -168,7 +168,7 @@ class BouncerMessageInteractorTest : SysuiTestCase() {
val faceAcquisitionMessage = repository.faceAcquisitionMessage
assertThat(faceAcquisitionMessage.value!!.message!!.messageResId)
.isEqualTo(keyguard_enter_pin)
.isEqualTo(kg_unlock_with_pin_or_fp)
assertThat(faceAcquisitionMessage.value!!.secondaryMessage!!.message)
.isEqualTo("not empty")
@@ -186,7 +186,7 @@ class BouncerMessageInteractorTest : SysuiTestCase() {
val fingerprintAcquisitionMessage = repository.fingerprintAcquisitionMessage
assertThat(fingerprintAcquisitionMessage.value!!.message!!.messageResId)
.isEqualTo(keyguard_enter_pin)
.isEqualTo(kg_unlock_with_pin_or_fp)
assertThat(fingerprintAcquisitionMessage.value!!.secondaryMessage!!.message)
.isEqualTo("not empty")
@@ -275,7 +275,8 @@ class BouncerMessageInteractorTest : SysuiTestCase() {
repository.setBiometricLockedOutMessage(null)
// sets the default message if everything else is null
assertThat(bouncerMessage()!!.message!!.messageResId).isEqualTo(keyguard_enter_pin)
assertThat(bouncerMessage()!!.message!!.messageResId)
.isEqualTo(kg_unlock_with_pin_or_fp)
}
private fun message(value: String): BouncerMessageModel {

View File

@@ -36,6 +36,11 @@ import com.android.systemui.R
import com.android.systemui.RoboPilotTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.biometrics.AuthController
import com.android.systemui.biometrics.data.repository.FaceSensorInfo
import com.android.systemui.biometrics.data.repository.FakeFacePropertyRepository
import com.android.systemui.biometrics.data.repository.FakeFingerprintPropertyRepository
import com.android.systemui.biometrics.shared.model.FingerprintSensorType
import com.android.systemui.biometrics.shared.model.SensorStrength
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.dump.DumpManager
import com.android.systemui.keyguard.data.repository.BiometricType.FACE
@@ -63,6 +68,7 @@ import org.mockito.ArgumentMatchers.anyInt
import org.mockito.ArgumentMatchers.isNull
import org.mockito.Captor
import org.mockito.Mock
import org.mockito.Mockito.atLeastOnce
import org.mockito.Mockito.clearInvocations
import org.mockito.Mockito.times
import org.mockito.Mockito.verify
@@ -89,6 +95,8 @@ class BiometricSettingsRepositoryTest : SysuiTestCase() {
ArgumentCaptor<IBiometricEnabledOnKeyguardCallback.Stub>
private lateinit var userRepository: FakeUserRepository
private lateinit var devicePostureRepository: FakeDevicePostureRepository
private lateinit var facePropertyRepository: FakeFacePropertyRepository
private lateinit var fingerprintPropertyRepository: FakeFingerprintPropertyRepository
private lateinit var testDispatcher: TestDispatcher
private lateinit var testScope: TestScope
@@ -102,6 +110,8 @@ class BiometricSettingsRepositoryTest : SysuiTestCase() {
testScope = TestScope(testDispatcher)
userRepository = FakeUserRepository()
devicePostureRepository = FakeDevicePostureRepository()
facePropertyRepository = FakeFacePropertyRepository()
fingerprintPropertyRepository = FakeFingerprintPropertyRepository()
}
private suspend fun createBiometricSettingsRepository() {
@@ -120,74 +130,110 @@ class BiometricSettingsRepositoryTest : SysuiTestCase() {
biometricManager = biometricManager,
devicePostureRepository = devicePostureRepository,
dumpManager = dumpManager,
facePropertyRepository = facePropertyRepository,
fingerprintPropertyRepository = fingerprintPropertyRepository,
)
testScope.runCurrent()
fingerprintPropertyRepository.setProperties(
1,
SensorStrength.STRONG,
FingerprintSensorType.UDFPS_OPTICAL,
emptyMap()
)
verify(lockPatternUtils).registerStrongAuthTracker(strongAuthTracker.capture())
verify(authController, atLeastOnce()).addCallback(authControllerCallback.capture())
}
@Test
fun fingerprintEnrollmentChange() =
testScope.runTest {
createBiometricSettingsRepository()
val fingerprintEnrolled = collectLastValue(underTest.isFingerprintEnrolled)
val fingerprintAllowed = collectLastValue(underTest.isFingerprintEnrolledAndEnabled)
runCurrent()
verify(authController).addCallback(authControllerCallback.capture())
whenever(authController.isFingerprintEnrolled(anyInt())).thenReturn(true)
enrollmentChange(UNDER_DISPLAY_FINGERPRINT, PRIMARY_USER_ID, true)
assertThat(fingerprintEnrolled()).isTrue()
assertThat(fingerprintAllowed()).isTrue()
whenever(authController.isFingerprintEnrolled(anyInt())).thenReturn(false)
enrollmentChange(UNDER_DISPLAY_FINGERPRINT, ANOTHER_USER_ID, false)
assertThat(fingerprintEnrolled()).isTrue()
assertThat(fingerprintAllowed()).isTrue()
enrollmentChange(UNDER_DISPLAY_FINGERPRINT, PRIMARY_USER_ID, false)
assertThat(fingerprintEnrolled()).isFalse()
assertThat(fingerprintAllowed()).isFalse()
}
@Test
fun strongBiometricAllowedChange() =
testScope.runTest {
fingerprintIsEnrolled()
doNotDisableKeyguardAuthFeatures()
createBiometricSettingsRepository()
val strongBiometricAllowed = collectLastValue(underTest.isStrongBiometricAllowed)
val strongBiometricAllowed by
collectLastValue(underTest.isFingerprintAuthCurrentlyAllowed)
runCurrent()
onStrongAuthChanged(STRONG_AUTH_NOT_REQUIRED, PRIMARY_USER_ID)
assertThat(strongBiometricAllowed()).isTrue()
assertThat(strongBiometricAllowed).isTrue()
onStrongAuthChanged(STRONG_AUTH_REQUIRED_AFTER_BOOT, PRIMARY_USER_ID)
assertThat(strongBiometricAllowed()).isFalse()
assertThat(strongBiometricAllowed).isFalse()
}
@Test
fun convenienceBiometricAllowedChange() =
testScope.runTest {
overrideResource(com.android.internal.R.bool.config_strongAuthRequiredOnBoot, false)
deviceIsInPostureThatSupportsFaceAuth()
faceAuthIsEnrolled()
faceAuthIsNonStrongBiometric()
createBiometricSettingsRepository()
val convenienceBiometricAllowed =
collectLastValue(underTest.isNonStrongBiometricAllowed)
runCurrent()
val convenienceFaceAuthAllowed by collectLastValue(underTest.isFaceAuthCurrentlyAllowed)
doNotDisableKeyguardAuthFeatures()
faceAuthIsEnabledByBiometricManager()
onStrongAuthChanged(STRONG_AUTH_NOT_REQUIRED, PRIMARY_USER_ID)
onNonStrongAuthChanged(true, PRIMARY_USER_ID)
assertThat(convenienceBiometricAllowed()).isTrue()
runCurrent()
assertThat(convenienceFaceAuthAllowed).isTrue()
onNonStrongAuthChanged(false, ANOTHER_USER_ID)
assertThat(convenienceBiometricAllowed()).isTrue()
assertThat(convenienceFaceAuthAllowed).isTrue()
onNonStrongAuthChanged(false, PRIMARY_USER_ID)
assertThat(convenienceBiometricAllowed()).isFalse()
assertThat(convenienceFaceAuthAllowed).isFalse()
mContext.orCreateTestableResources.removeOverride(
com.android.internal.R.bool.config_strongAuthRequiredOnBoot
)
}
private fun faceAuthIsNonStrongBiometric() {
facePropertyRepository.setSensorInfo(FaceSensorInfo(1, SensorStrength.CONVENIENCE))
}
private fun faceAuthIsStrongBiometric() {
facePropertyRepository.setSensorInfo(FaceSensorInfo(1, SensorStrength.STRONG))
}
private fun deviceIsInPostureThatSupportsFaceAuth() {
overrideResource(
R.integer.config_face_auth_supported_posture,
DevicePostureController.DEVICE_POSTURE_FLIPPED
)
devicePostureRepository.setCurrentPosture(DevicePosture.FLIPPED)
}
@Test
fun whenStrongAuthRequiredAfterBoot_nonStrongBiometricNotAllowed() =
testScope.runTest {
overrideResource(com.android.internal.R.bool.config_strongAuthRequiredOnBoot, true)
createBiometricSettingsRepository()
faceAuthIsNonStrongBiometric()
faceAuthIsEnrolled()
doNotDisableKeyguardAuthFeatures()
val convenienceBiometricAllowed =
collectLastValue(underTest.isNonStrongBiometricAllowed)
val convenienceBiometricAllowed = collectLastValue(underTest.isFaceAuthCurrentlyAllowed)
runCurrent()
onNonStrongAuthChanged(true, PRIMARY_USER_ID)
@@ -201,16 +247,24 @@ class BiometricSettingsRepositoryTest : SysuiTestCase() {
fun whenStrongBiometricAuthIsNotAllowed_nonStrongBiometrics_alsoNotAllowed() =
testScope.runTest {
overrideResource(com.android.internal.R.bool.config_strongAuthRequiredOnBoot, false)
faceAuthIsNonStrongBiometric()
deviceIsInPostureThatSupportsFaceAuth()
faceAuthIsEnrolled()
createBiometricSettingsRepository()
val convenienceBiometricAllowed =
collectLastValue(underTest.isNonStrongBiometricAllowed)
doNotDisableKeyguardAuthFeatures()
faceAuthIsEnabledByBiometricManager()
runCurrent()
val convenienceBiometricAllowed by
collectLastValue(underTest.isFaceAuthCurrentlyAllowed)
onStrongAuthChanged(STRONG_AUTH_NOT_REQUIRED, PRIMARY_USER_ID)
onNonStrongAuthChanged(true, PRIMARY_USER_ID)
assertThat(convenienceBiometricAllowed()).isTrue()
runCurrent()
assertThat(convenienceBiometricAllowed).isTrue()
onStrongAuthChanged(STRONG_AUTH_REQUIRED_AFTER_TIMEOUT, PRIMARY_USER_ID)
assertThat(convenienceBiometricAllowed()).isFalse()
assertThat(convenienceBiometricAllowed).isFalse()
mContext.orCreateTestableResources.removeOverride(
com.android.internal.R.bool.config_strongAuthRequiredOnBoot
)
@@ -229,9 +283,11 @@ class BiometricSettingsRepositoryTest : SysuiTestCase() {
@Test
fun fingerprintDisabledByDpmChange() =
testScope.runTest {
fingerprintIsEnrolled(PRIMARY_USER_ID)
createBiometricSettingsRepository()
val fingerprintEnabledByDevicePolicy =
collectLastValue(underTest.isFingerprintEnabledByDevicePolicy)
collectLastValue(underTest.isFingerprintEnrolledAndEnabled)
runCurrent()
whenever(devicePolicyManager.getKeyguardDisabledFeatures(any(), anyInt()))
@@ -244,43 +300,57 @@ class BiometricSettingsRepositoryTest : SysuiTestCase() {
assertThat(fingerprintEnabledByDevicePolicy()).isTrue()
}
private fun fingerprintIsEnrolled(userId: Int = PRIMARY_USER_ID) {
whenever(authController.isFingerprintEnrolled(userId)).thenReturn(true)
}
@Test
fun faceEnrollmentChangeIsPropagatedForTheCurrentUser() =
testScope.runTest {
createBiometricSettingsRepository()
faceAuthIsEnabledByBiometricManager()
doNotDisableKeyguardAuthFeatures(PRIMARY_USER_ID)
runCurrent()
clearInvocations(authController)
whenever(authController.isFaceAuthEnrolled(PRIMARY_USER_ID)).thenReturn(false)
val faceEnrolled = collectLastValue(underTest.isFaceEnrolled)
val faceAuthAllowed = collectLastValue(underTest.isFaceAuthEnrolledAndEnabled)
assertThat(faceEnrolled()).isFalse()
assertThat(faceAuthAllowed()).isFalse()
verify(authController).addCallback(authControllerCallback.capture())
enrollmentChange(REAR_FINGERPRINT, PRIMARY_USER_ID, true)
assertThat(faceEnrolled()).isFalse()
assertThat(faceAuthAllowed()).isFalse()
enrollmentChange(SIDE_FINGERPRINT, PRIMARY_USER_ID, true)
assertThat(faceEnrolled()).isFalse()
assertThat(faceAuthAllowed()).isFalse()
enrollmentChange(UNDER_DISPLAY_FINGERPRINT, PRIMARY_USER_ID, true)
assertThat(faceEnrolled()).isFalse()
assertThat(faceAuthAllowed()).isFalse()
whenever(authController.isFaceAuthEnrolled(ANOTHER_USER_ID)).thenReturn(true)
enrollmentChange(FACE, ANOTHER_USER_ID, true)
assertThat(faceEnrolled()).isFalse()
assertThat(faceAuthAllowed()).isFalse()
whenever(authController.isFaceAuthEnrolled(PRIMARY_USER_ID)).thenReturn(true)
faceAuthIsEnrolled()
enrollmentChange(FACE, PRIMARY_USER_ID, true)
assertThat(faceEnrolled()).isTrue()
assertThat(faceAuthAllowed()).isTrue()
}
private fun faceAuthIsEnabledByBiometricManager(userId: Int = PRIMARY_USER_ID) {
verify(biometricManager, atLeastOnce())
.registerEnabledOnKeyguardCallback(biometricManagerCallback.capture())
biometricManagerCallback.value.onChanged(true, userId)
}
@Test
fun faceEnrollmentStatusOfNewUserUponUserSwitch() =
testScope.runTest {
@@ -290,21 +360,26 @@ class BiometricSettingsRepositoryTest : SysuiTestCase() {
whenever(authController.isFaceAuthEnrolled(PRIMARY_USER_ID)).thenReturn(false)
whenever(authController.isFaceAuthEnrolled(ANOTHER_USER_ID)).thenReturn(true)
val faceEnrolled = collectLastValue(underTest.isFaceEnrolled)
val faceAuthAllowed = collectLastValue(underTest.isFaceAuthEnrolledAndEnabled)
assertThat(faceEnrolled()).isFalse()
assertThat(faceAuthAllowed()).isFalse()
}
@Test
fun faceEnrollmentChangesArePropagatedAfterUserSwitch() =
testScope.runTest {
createBiometricSettingsRepository()
verify(biometricManager)
.registerEnabledOnKeyguardCallback(biometricManagerCallback.capture())
userRepository.setSelectedUserInfo(ANOTHER_USER)
doNotDisableKeyguardAuthFeatures(ANOTHER_USER_ID)
biometricManagerCallback.value.onChanged(true, ANOTHER_USER_ID)
runCurrent()
clearInvocations(authController)
val faceEnrolled = collectLastValue(underTest.isFaceEnrolled)
val faceAuthAllowed = collectLastValue(underTest.isFaceAuthEnrolledAndEnabled)
runCurrent()
verify(authController).addCallback(authControllerCallback.capture())
@@ -312,12 +387,14 @@ class BiometricSettingsRepositoryTest : SysuiTestCase() {
whenever(authController.isFaceAuthEnrolled(ANOTHER_USER_ID)).thenReturn(true)
enrollmentChange(FACE, ANOTHER_USER_ID, true)
assertThat(faceEnrolled()).isTrue()
assertThat(faceAuthAllowed()).isTrue()
}
@Test
fun devicePolicyControlsFaceAuthenticationEnabledState() =
testScope.runTest {
faceAuthIsEnrolled()
createBiometricSettingsRepository()
verify(biometricManager)
.registerEnabledOnKeyguardCallback(biometricManagerCallback.capture())
@@ -325,62 +402,70 @@ class BiometricSettingsRepositoryTest : SysuiTestCase() {
whenever(devicePolicyManager.getKeyguardDisabledFeatures(isNull(), eq(PRIMARY_USER_ID)))
.thenReturn(KEYGUARD_DISABLE_FINGERPRINT or KEYGUARD_DISABLE_FACE)
val isFaceAuthEnabled = collectLastValue(underTest.isFaceAuthenticationEnabled)
val isFaceAuthAllowed = collectLastValue(underTest.isFaceAuthEnrolledAndEnabled)
runCurrent()
broadcastDPMStateChange()
assertThat(isFaceAuthEnabled()).isFalse()
assertThat(isFaceAuthAllowed()).isFalse()
biometricManagerCallback.value.onChanged(true, PRIMARY_USER_ID)
runCurrent()
assertThat(isFaceAuthEnabled()).isFalse()
assertThat(isFaceAuthAllowed()).isFalse()
whenever(devicePolicyManager.getKeyguardDisabledFeatures(isNull(), eq(PRIMARY_USER_ID)))
.thenReturn(KEYGUARD_DISABLE_FINGERPRINT)
broadcastDPMStateChange()
assertThat(isFaceAuthEnabled()).isTrue()
assertThat(isFaceAuthAllowed()).isTrue()
}
@Test
fun biometricManagerControlsFaceAuthenticationEnabledStatus() =
testScope.runTest {
faceAuthIsEnrolled()
createBiometricSettingsRepository()
verify(biometricManager)
.registerEnabledOnKeyguardCallback(biometricManagerCallback.capture())
whenever(devicePolicyManager.getKeyguardDisabledFeatures(isNull(), eq(PRIMARY_USER_ID)))
.thenReturn(0)
broadcastDPMStateChange()
val isFaceAuthEnabled = collectLastValue(underTest.isFaceAuthenticationEnabled)
val isFaceAuthAllowed = collectLastValue(underTest.isFaceAuthEnrolledAndEnabled)
assertThat(isFaceAuthEnabled()).isFalse()
assertThat(isFaceAuthAllowed()).isFalse()
// Value changes for another user
biometricManagerCallback.value.onChanged(true, ANOTHER_USER_ID)
assertThat(isFaceAuthEnabled()).isFalse()
assertThat(isFaceAuthAllowed()).isFalse()
// Value changes for current user.
biometricManagerCallback.value.onChanged(true, PRIMARY_USER_ID)
assertThat(isFaceAuthEnabled()).isTrue()
assertThat(isFaceAuthAllowed()).isTrue()
}
private fun faceAuthIsEnrolled(userId: Int = PRIMARY_USER_ID) {
whenever(authController.isFaceAuthEnrolled(userId)).thenReturn(true)
}
@Test
fun userChange_biometricEnabledChange_handlesRaceCondition() =
testScope.runTest {
createBiometricSettingsRepository()
whenever(authController.isFaceAuthEnrolled(ANOTHER_USER_ID)).thenReturn(true)
verify(biometricManager)
.registerEnabledOnKeyguardCallback(biometricManagerCallback.capture())
val isFaceAuthEnabled = collectLastValue(underTest.isFaceAuthenticationEnabled)
val isFaceAuthAllowed = collectLastValue(underTest.isFaceAuthEnrolledAndEnabled)
biometricManagerCallback.value.onChanged(true, ANOTHER_USER_ID)
runCurrent()
userRepository.setSelectedUserInfo(ANOTHER_USER)
runCurrent()
assertThat(isFaceAuthEnabled()).isTrue()
assertThat(isFaceAuthAllowed()).isTrue()
}
@Test
@@ -388,9 +473,9 @@ class BiometricSettingsRepositoryTest : SysuiTestCase() {
testScope.runTest {
createBiometricSettingsRepository()
collectLastValue(underTest.isFaceAuthenticationEnabled)()
collectLastValue(underTest.isFaceAuthenticationEnabled)()
collectLastValue(underTest.isFaceAuthenticationEnabled)()
collectLastValue(underTest.isFaceAuthEnrolledAndEnabled)()
collectLastValue(underTest.isFaceAuthEnrolledAndEnabled)()
collectLastValue(underTest.isFaceAuthEnrolledAndEnabled)()
verify(biometricManager, times(1)).registerEnabledOnKeyguardCallback(any())
}
@@ -495,10 +580,138 @@ class BiometricSettingsRepositoryTest : SysuiTestCase() {
assertThat(authFlags()!!.flag).isEqualTo(STRONG_AUTH_REQUIRED_AFTER_TIMEOUT)
}
@Test
fun faceAuthCurrentlyAllowed_dependsOnStrongAuthBiometricSetting_ifFaceIsClass3() =
testScope.runTest {
createBiometricSettingsRepository()
val isFaceAuthCurrentlyAllowed by collectLastValue(underTest.isFaceAuthCurrentlyAllowed)
faceAuthIsEnrolled()
deviceIsInPostureThatSupportsFaceAuth()
doNotDisableKeyguardAuthFeatures()
faceAuthIsStrongBiometric()
faceAuthIsEnabledByBiometricManager()
onStrongAuthChanged(STRONG_AUTH_NOT_REQUIRED, PRIMARY_USER_ID)
onNonStrongAuthChanged(false, PRIMARY_USER_ID)
assertThat(isFaceAuthCurrentlyAllowed).isTrue()
onStrongAuthChanged(STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN, PRIMARY_USER_ID)
onNonStrongAuthChanged(true, PRIMARY_USER_ID)
assertThat(isFaceAuthCurrentlyAllowed).isFalse()
}
@Test
fun faceAuthCurrentlyAllowed_dependsOnNonStrongAuthBiometricSetting_ifFaceIsNotStrong() =
testScope.runTest {
createBiometricSettingsRepository()
val isFaceAuthCurrentlyAllowed by collectLastValue(underTest.isFaceAuthCurrentlyAllowed)
faceAuthIsEnrolled()
deviceIsInPostureThatSupportsFaceAuth()
doNotDisableKeyguardAuthFeatures()
faceAuthIsNonStrongBiometric()
faceAuthIsEnabledByBiometricManager()
onStrongAuthChanged(STRONG_AUTH_NOT_REQUIRED, PRIMARY_USER_ID)
onNonStrongAuthChanged(false, PRIMARY_USER_ID)
assertThat(isFaceAuthCurrentlyAllowed).isFalse()
onStrongAuthChanged(STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN, PRIMARY_USER_ID)
onNonStrongAuthChanged(true, PRIMARY_USER_ID)
assertThat(isFaceAuthCurrentlyAllowed).isFalse()
onStrongAuthChanged(STRONG_AUTH_NOT_REQUIRED, PRIMARY_USER_ID)
onNonStrongAuthChanged(true, PRIMARY_USER_ID)
assertThat(isFaceAuthCurrentlyAllowed).isTrue()
}
@Test
fun fpAuthCurrentlyAllowed_dependsOnNonStrongAuthBiometricSetting_ifFpIsNotStrong() =
testScope.runTest {
createBiometricSettingsRepository()
val isFingerprintCurrentlyAllowed by
collectLastValue(underTest.isFingerprintAuthCurrentlyAllowed)
fingerprintIsEnrolled(PRIMARY_USER_ID)
enrollmentChange(UNDER_DISPLAY_FINGERPRINT, PRIMARY_USER_ID, true)
doNotDisableKeyguardAuthFeatures(PRIMARY_USER_ID)
runCurrent()
fingerprintPropertyRepository.setProperties(
1,
SensorStrength.STRONG,
FingerprintSensorType.UDFPS_OPTICAL,
emptyMap()
)
// Non strong auth is not allowed now, FP is marked strong
onStrongAuthChanged(STRONG_AUTH_NOT_REQUIRED, PRIMARY_USER_ID)
onNonStrongAuthChanged(false, PRIMARY_USER_ID)
assertThat(isFingerprintCurrentlyAllowed).isTrue()
fingerprintPropertyRepository.setProperties(
1,
SensorStrength.CONVENIENCE,
FingerprintSensorType.UDFPS_OPTICAL,
emptyMap()
)
assertThat(isFingerprintCurrentlyAllowed).isFalse()
fingerprintPropertyRepository.setProperties(
1,
SensorStrength.WEAK,
FingerprintSensorType.UDFPS_OPTICAL,
emptyMap()
)
assertThat(isFingerprintCurrentlyAllowed).isFalse()
}
@Test
fun fpAuthCurrentlyAllowed_dependsOnStrongAuthBiometricSetting_ifFpIsStrong() =
testScope.runTest {
createBiometricSettingsRepository()
val isFingerprintCurrentlyAllowed by
collectLastValue(underTest.isFingerprintAuthCurrentlyAllowed)
fingerprintIsEnrolled(PRIMARY_USER_ID)
enrollmentChange(UNDER_DISPLAY_FINGERPRINT, PRIMARY_USER_ID, true)
doNotDisableKeyguardAuthFeatures(PRIMARY_USER_ID)
runCurrent()
fingerprintPropertyRepository.setProperties(
1,
SensorStrength.STRONG,
FingerprintSensorType.UDFPS_OPTICAL,
emptyMap()
)
// Non strong auth is not allowed now, FP is marked strong
onStrongAuthChanged(STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN, PRIMARY_USER_ID)
onNonStrongAuthChanged(true, PRIMARY_USER_ID)
assertThat(isFingerprintCurrentlyAllowed).isFalse()
onStrongAuthChanged(STRONG_AUTH_NOT_REQUIRED, PRIMARY_USER_ID)
onNonStrongAuthChanged(false, PRIMARY_USER_ID)
assertThat(isFingerprintCurrentlyAllowed).isTrue()
}
private fun enrollmentChange(biometricType: BiometricType, userId: Int, enabled: Boolean) {
authControllerCallback.value.onEnrollmentsChanged(biometricType, userId, enabled)
}
private fun doNotDisableKeyguardAuthFeatures(userId: Int = PRIMARY_USER_ID) {
whenever(devicePolicyManager.getKeyguardDisabledFeatures(isNull(), eq(userId)))
.thenReturn(0)
broadcastDPMStateChange()
}
private fun broadcastDPMStateChange() {
fakeBroadcastDispatcher.sendIntentToMatchingReceiversOnly(
context,

View File

@@ -40,9 +40,7 @@ import com.android.keyguard.KeyguardUpdateMonitor
import com.android.systemui.R
import com.android.systemui.RoboPilotTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.biometrics.data.repository.FaceSensorInfo
import com.android.systemui.biometrics.data.repository.FakeFacePropertyRepository
import com.android.systemui.biometrics.shared.model.SensorStrength
import com.android.systemui.bouncer.data.repository.FakeKeyguardBouncerRepository
import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor
import com.android.systemui.coroutines.FlowValue
@@ -255,7 +253,6 @@ class DeviceEntryFaceAuthRepositoryTest : SysuiTestCase() {
faceAuthBuffer,
keyguardTransitionInteractor,
featureFlags,
fakeFacePropertyRepository,
dumpManager,
)
}
@@ -525,15 +522,11 @@ class DeviceEntryFaceAuthRepositoryTest : SysuiTestCase() {
}
@Test
fun authenticateDoesNotRunIfFaceIsNotEnrolled() =
fun authenticateDoesNotRunIfFaceIsNotUsuallyAllowed() =
testScope.runTest {
testGatingCheckForFaceAuth { biometricSettingsRepository.setFaceEnrolled(false) }
}
@Test
fun authenticateDoesNotRunIfFaceIsNotEnabled() =
testScope.runTest {
testGatingCheckForFaceAuth { biometricSettingsRepository.setIsFaceAuthEnabled(false) }
testGatingCheckForFaceAuth {
biometricSettingsRepository.setIsFaceAuthEnrolledAndEnabled(false)
}
}
@Test
@@ -589,21 +582,10 @@ class DeviceEntryFaceAuthRepositoryTest : SysuiTestCase() {
}
@Test
fun authenticateDoesNotRunWhenNonStrongBiometricIsNotAllowed() =
fun authenticateDoesNotRunWhenFaceAuthIsNotCurrentlyAllowedToRun() =
testScope.runTest {
testGatingCheckForFaceAuth {
biometricSettingsRepository.setIsNonStrongBiometricAllowed(false)
}
}
@Test
fun authenticateDoesNotRunWhenStrongBiometricIsNotAllowedAndFaceSensorIsStrong() =
testScope.runTest {
fakeFacePropertyRepository.setSensorInfo(FaceSensorInfo(1, SensorStrength.STRONG))
runCurrent()
testGatingCheckForFaceAuth(isFaceStrong = true) {
biometricSettingsRepository.setIsStrongBiometricAllowed(false)
biometricSettingsRepository.setIsFaceAuthCurrentlyAllowed(false)
}
}
@@ -662,7 +644,7 @@ class DeviceEntryFaceAuthRepositoryTest : SysuiTestCase() {
allPreconditionsToRunFaceAuthAreTrue()
// Flip one precondition to false.
biometricSettingsRepository.setIsNonStrongBiometricAllowed(false)
biometricSettingsRepository.setIsFaceAuthCurrentlyAllowed(false)
assertThat(canFaceAuthRun()).isFalse()
underTest.authenticate(
FACE_AUTH_TRIGGERED_SWIPE_UP_ON_BOUNCER,
@@ -827,15 +809,11 @@ class DeviceEntryFaceAuthRepositoryTest : SysuiTestCase() {
}
@Test
fun detectDoesNotRunWhenFaceIsNotEnrolled() =
fun detectDoesNotRunWhenFaceIsNotUsuallyAllowed() =
testScope.runTest {
testGatingCheckForDetect { biometricSettingsRepository.setFaceEnrolled(false) }
}
@Test
fun detectDoesNotRunWhenFaceIsNotEnabled() =
testScope.runTest {
testGatingCheckForDetect { biometricSettingsRepository.setIsFaceAuthEnabled(false) }
testGatingCheckForDetect {
biometricSettingsRepository.setIsFaceAuthEnrolledAndEnabled(false)
}
}
@Test
@@ -932,23 +910,10 @@ class DeviceEntryFaceAuthRepositoryTest : SysuiTestCase() {
}
@Test
fun detectDoesNotRunWhenNonStrongBiometricIsAllowed() =
fun detectDoesNotRunWhenFaceAuthIsCurrentlyAllowedToRun() =
testScope.runTest {
testGatingCheckForDetect {
biometricSettingsRepository.setIsNonStrongBiometricAllowed(true)
}
}
@Test
fun detectDoesNotRunWhenStrongBiometricIsAllowedAndFaceAuthSensorStrengthIsStrong() =
testScope.runTest {
fakeFacePropertyRepository.setSensorInfo(FaceSensorInfo(1, SensorStrength.STRONG))
runCurrent()
testGatingCheckForDetect(isFaceStrong = true) {
biometricSettingsRepository.setIsStrongBiometricAllowed(true)
// this shouldn't matter as face is set as a strong sensor
biometricSettingsRepository.setIsNonStrongBiometricAllowed(false)
biometricSettingsRepository.setIsFaceAuthCurrentlyAllowed(true)
}
}
@@ -1043,12 +1008,9 @@ class DeviceEntryFaceAuthRepositoryTest : SysuiTestCase() {
faceAuthenticateIsCalled()
}
private suspend fun TestScope.testGatingCheckForFaceAuth(
isFaceStrong: Boolean = false,
gatingCheckModifier: () -> Unit
) {
private suspend fun TestScope.testGatingCheckForFaceAuth(gatingCheckModifier: () -> Unit) {
initCollectors()
allPreconditionsToRunFaceAuthAreTrue(isFaceStrong)
allPreconditionsToRunFaceAuthAreTrue()
gatingCheckModifier()
runCurrent()
@@ -1057,7 +1019,7 @@ class DeviceEntryFaceAuthRepositoryTest : SysuiTestCase() {
assertThat(underTest.canRunFaceAuth.value).isFalse()
// flip the gating check back on.
allPreconditionsToRunFaceAuthAreTrue(isFaceStrong)
allPreconditionsToRunFaceAuthAreTrue()
triggerFaceAuth(false)
@@ -1076,19 +1038,12 @@ class DeviceEntryFaceAuthRepositoryTest : SysuiTestCase() {
faceAuthenticateIsNotCalled()
}
private suspend fun TestScope.testGatingCheckForDetect(
isFaceStrong: Boolean = false,
gatingCheckModifier: () -> Unit
) {
private suspend fun TestScope.testGatingCheckForDetect(gatingCheckModifier: () -> Unit) {
initCollectors()
allPreconditionsToRunFaceAuthAreTrue()
if (isFaceStrong) {
biometricSettingsRepository.setStrongBiometricAllowed(false)
} else {
// This will stop face auth from running but is required to be false for detect.
biometricSettingsRepository.setIsNonStrongBiometricAllowed(false)
}
// This will stop face auth from running but is required to be false for detect.
biometricSettingsRepository.setIsFaceAuthCurrentlyAllowed(false)
runCurrent()
assertThat(canFaceAuthRun()).isFalse()
@@ -1123,13 +1078,9 @@ class DeviceEntryFaceAuthRepositoryTest : SysuiTestCase() {
cancellationSignal.value.setOnCancelListener { wasAuthCancelled = true }
}
private suspend fun TestScope.allPreconditionsToRunFaceAuthAreTrue(
isFaceStrong: Boolean = false
) {
private suspend fun TestScope.allPreconditionsToRunFaceAuthAreTrue() {
verify(faceManager, atLeastOnce())
.addLockoutResetCallback(faceLockoutResetCallback.capture())
biometricSettingsRepository.setFaceEnrolled(true)
biometricSettingsRepository.setIsFaceAuthEnabled(true)
underTest.resumeFaceAuth()
trustRepository.setCurrentUserTrusted(false)
keyguardRepository.setKeyguardGoingAway(false)
@@ -1140,14 +1091,11 @@ class DeviceEntryFaceAuthRepositoryTest : SysuiTestCase() {
WakeSleepReason.OTHER
)
)
if (isFaceStrong) {
biometricSettingsRepository.setStrongBiometricAllowed(true)
} else {
biometricSettingsRepository.setIsNonStrongBiometricAllowed(true)
}
biometricSettingsRepository.setIsFaceAuthEnrolledAndEnabled(true)
biometricSettingsRepository.setIsFaceAuthSupportedInCurrentPosture(true)
biometricSettingsRepository.setIsFaceAuthCurrentlyAllowed(true)
biometricSettingsRepository.setIsUserInLockdown(false)
fakeUserRepository.setSelectedUserInfo(primaryUser)
biometricSettingsRepository.setIsFaceAuthSupportedInCurrentPosture(true)
faceLockoutResetCallback.value.onLockoutReset(0)
bouncerRepository.setAlternateVisible(true)
keyguardRepository.setKeyguardShowing(true)

View File

@@ -22,32 +22,24 @@ import com.android.systemui.keyguard.shared.model.AuthenticationFlags
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.map
class FakeBiometricSettingsRepository : BiometricSettingsRepository {
private val _isFingerprintEnrolledAndEnabled = MutableStateFlow(false)
override val isFingerprintEnrolledAndEnabled: StateFlow<Boolean>
get() = _isFingerprintEnrolledAndEnabled
private val _isFingerprintEnrolled = MutableStateFlow<Boolean>(false)
override val isFingerprintEnrolled: StateFlow<Boolean> = _isFingerprintEnrolled.asStateFlow()
private val _isFingerprintAuthCurrentlyAllowed = MutableStateFlow(false)
override val isFingerprintAuthCurrentlyAllowed: StateFlow<Boolean>
get() = _isFingerprintAuthCurrentlyAllowed
private val _isFaceEnrolled = MutableStateFlow(false)
override val isFaceEnrolled: Flow<Boolean>
get() = _isFaceEnrolled
private val _isFaceAuthEnrolledAndEnabled = MutableStateFlow(false)
override val isFaceAuthEnrolledAndEnabled: Flow<Boolean>
get() = _isFaceAuthEnrolledAndEnabled
private val _isFaceAuthEnabled = MutableStateFlow(false)
override val isFaceAuthenticationEnabled: Flow<Boolean>
get() = _isFaceAuthEnabled
private val _isStrongBiometricAllowed = MutableStateFlow(false)
override val isStrongBiometricAllowed = _isStrongBiometricAllowed.asStateFlow()
private val _isNonStrongBiometricAllowed = MutableStateFlow(false)
override val isNonStrongBiometricAllowed: StateFlow<Boolean>
get() = _isNonStrongBiometricAllowed
private val _isFingerprintEnabledByDevicePolicy = MutableStateFlow(false)
override val isFingerprintEnabledByDevicePolicy =
_isFingerprintEnabledByDevicePolicy.asStateFlow()
private val _isFaceAuthCurrentlyAllowed = MutableStateFlow(false)
override val isFaceAuthCurrentlyAllowed: Flow<Boolean>
get() = _isFaceAuthCurrentlyAllowed
private val _isFaceAuthSupportedInCurrentPosture = MutableStateFlow(false)
override val isFaceAuthSupportedInCurrentPosture: Flow<Boolean>
@@ -59,34 +51,33 @@ class FakeBiometricSettingsRepository : BiometricSettingsRepository {
private val _authFlags = MutableStateFlow(AuthenticationFlags(0, 0))
override val authenticationFlags: Flow<AuthenticationFlags>
get() = _authFlags
fun setFingerprintEnrolled(isFingerprintEnrolled: Boolean) {
_isFingerprintEnrolled.value = isFingerprintEnrolled
}
fun setStrongBiometricAllowed(isStrongBiometricAllowed: Boolean) {
_isStrongBiometricAllowed.value = isStrongBiometricAllowed
}
fun setFingerprintEnabledByDevicePolicy(isFingerprintEnabledByDevicePolicy: Boolean) {
_isFingerprintEnabledByDevicePolicy.value = isFingerprintEnabledByDevicePolicy
}
fun setAuthenticationFlags(value: AuthenticationFlags) {
_authFlags.value = value
}
fun setFaceEnrolled(isFaceEnrolled: Boolean) {
_isFaceEnrolled.value = isFaceEnrolled
fun setIsFingerprintAuthEnrolledAndEnabled(value: Boolean) {
_isFingerprintEnrolledAndEnabled.value = value
_isFingerprintAuthCurrentlyAllowed.value = _isFingerprintAuthCurrentlyAllowed.value && value
}
fun setIsFingerprintAuthCurrentlyAllowed(value: Boolean) {
_isFingerprintAuthCurrentlyAllowed.value = value
}
fun setIsFaceAuthEnrolledAndEnabled(value: Boolean) {
_isFaceAuthEnrolledAndEnabled.value = value
_isFaceAuthCurrentlyAllowed.value = _isFaceAuthCurrentlyAllowed.value && value
}
fun setIsFaceAuthCurrentlyAllowed(value: Boolean) {
_isFaceAuthCurrentlyAllowed.value = value
}
fun setIsFaceAuthSupportedInCurrentPosture(value: Boolean) {
_isFaceAuthSupportedInCurrentPosture.value = value
}
fun setIsFaceAuthEnabled(enabled: Boolean) {
_isFaceAuthEnabled.value = enabled
}
fun setIsUserInLockdown(value: Boolean) {
if (value) {
setAuthenticationFlags(
@@ -105,12 +96,4 @@ class FakeBiometricSettingsRepository : BiometricSettingsRepository {
)
}
}
fun setIsNonStrongBiometricAllowed(value: Boolean) {
_isNonStrongBiometricAllowed.value = value
}
fun setIsStrongBiometricAllowed(value: Boolean) {
_isStrongBiometricAllowed.value = value
}
}