Merge "Add face auth enrollment state to BiometricSettingsRepository" into tm-qpr-dev
This commit is contained in:
committed by
Android (Google) Code Review
commit
06b953fa63
@@ -20,6 +20,8 @@ import android.app.admin.DevicePolicyManager
|
|||||||
import android.app.admin.DevicePolicyManager.ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED
|
import android.app.admin.DevicePolicyManager.ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.IntentFilter
|
import android.content.IntentFilter
|
||||||
|
import android.hardware.biometrics.BiometricManager
|
||||||
|
import android.hardware.biometrics.IBiometricEnabledOnKeyguardCallback
|
||||||
import android.os.Looper
|
import android.os.Looper
|
||||||
import android.os.UserHandle
|
import android.os.UserHandle
|
||||||
import com.android.internal.widget.LockPatternUtils
|
import com.android.internal.widget.LockPatternUtils
|
||||||
@@ -42,10 +44,12 @@ import kotlinx.coroutines.channels.awaitClose
|
|||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
import kotlinx.coroutines.flow.SharingStarted
|
import kotlinx.coroutines.flow.SharingStarted
|
||||||
import kotlinx.coroutines.flow.StateFlow
|
import kotlinx.coroutines.flow.StateFlow
|
||||||
|
import kotlinx.coroutines.flow.combine
|
||||||
import kotlinx.coroutines.flow.distinctUntilChanged
|
import kotlinx.coroutines.flow.distinctUntilChanged
|
||||||
import kotlinx.coroutines.flow.flatMapLatest
|
import kotlinx.coroutines.flow.flatMapLatest
|
||||||
import kotlinx.coroutines.flow.flowOn
|
import kotlinx.coroutines.flow.flowOn
|
||||||
import kotlinx.coroutines.flow.map
|
import kotlinx.coroutines.flow.map
|
||||||
|
import kotlinx.coroutines.flow.onStart
|
||||||
import kotlinx.coroutines.flow.stateIn
|
import kotlinx.coroutines.flow.stateIn
|
||||||
import kotlinx.coroutines.flow.transformLatest
|
import kotlinx.coroutines.flow.transformLatest
|
||||||
|
|
||||||
@@ -60,6 +64,15 @@ interface BiometricSettingsRepository {
|
|||||||
/** Whether any fingerprints are enrolled for the current user. */
|
/** Whether any fingerprints are enrolled for the current user. */
|
||||||
val isFingerprintEnrolled: StateFlow<Boolean>
|
val isFingerprintEnrolled: StateFlow<Boolean>
|
||||||
|
|
||||||
|
/** Whether face authentication is enrolled for the current user. */
|
||||||
|
val isFaceEnrolled: Flow<Boolean>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether face authentication is enabled/disabled based on system settings like device policy,
|
||||||
|
* biometrics setting.
|
||||||
|
*/
|
||||||
|
val isFaceAuthenticationEnabled: Flow<Boolean>
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Whether the current user is allowed to use a strong biometric for device entry based on
|
* 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
|
* Android Security policies. If false, the user may be able to use primary authentication for
|
||||||
@@ -83,6 +96,7 @@ constructor(
|
|||||||
devicePolicyManager: DevicePolicyManager,
|
devicePolicyManager: DevicePolicyManager,
|
||||||
@Application scope: CoroutineScope,
|
@Application scope: CoroutineScope,
|
||||||
@Background backgroundDispatcher: CoroutineDispatcher,
|
@Background backgroundDispatcher: CoroutineDispatcher,
|
||||||
|
biometricManager: BiometricManager?,
|
||||||
@Main looper: Looper,
|
@Main looper: Looper,
|
||||||
dumpManager: DumpManager,
|
dumpManager: DumpManager,
|
||||||
) : BiometricSettingsRepository, Dumpable {
|
) : BiometricSettingsRepository, Dumpable {
|
||||||
@@ -101,9 +115,15 @@ constructor(
|
|||||||
private val selectedUserId: Flow<Int> =
|
private val selectedUserId: Flow<Int> =
|
||||||
userRepository.selectedUserInfo.map { it.id }.distinctUntilChanged()
|
userRepository.selectedUserInfo.map { it.id }.distinctUntilChanged()
|
||||||
|
|
||||||
|
private val devicePolicyChangedForAllUsers =
|
||||||
|
broadcastDispatcher.broadcastFlow(
|
||||||
|
filter = IntentFilter(ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED),
|
||||||
|
user = UserHandle.ALL
|
||||||
|
)
|
||||||
|
|
||||||
override val isFingerprintEnrolled: StateFlow<Boolean> =
|
override val isFingerprintEnrolled: StateFlow<Boolean> =
|
||||||
selectedUserId
|
selectedUserId
|
||||||
.flatMapLatest {
|
.flatMapLatest { currentUserId ->
|
||||||
conflatedCallbackFlow {
|
conflatedCallbackFlow {
|
||||||
val callback =
|
val callback =
|
||||||
object : AuthController.Callback {
|
object : AuthController.Callback {
|
||||||
@@ -112,7 +132,7 @@ constructor(
|
|||||||
userId: Int,
|
userId: Int,
|
||||||
hasEnrollments: Boolean
|
hasEnrollments: Boolean
|
||||||
) {
|
) {
|
||||||
if (sensorBiometricType.isFingerprint) {
|
if (sensorBiometricType.isFingerprint && userId == currentUserId) {
|
||||||
trySendWithFailureLogging(
|
trySendWithFailureLogging(
|
||||||
hasEnrollments,
|
hasEnrollments,
|
||||||
TAG,
|
TAG,
|
||||||
@@ -132,6 +152,77 @@ constructor(
|
|||||||
authController.isFingerprintEnrolled(userRepository.getSelectedUserInfo().id)
|
authController.isFingerprintEnrolled(userRepository.getSelectedUserInfo().id)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
override val isFaceEnrolled: Flow<Boolean> =
|
||||||
|
selectedUserId.flatMapLatest { selectedUserId: Int ->
|
||||||
|
conflatedCallbackFlow {
|
||||||
|
val callback =
|
||||||
|
object : AuthController.Callback {
|
||||||
|
override fun onEnrollmentsChanged(
|
||||||
|
sensorBiometricType: BiometricType,
|
||||||
|
userId: Int,
|
||||||
|
hasEnrollments: Boolean
|
||||||
|
) {
|
||||||
|
// TODO(b/242022358), use authController.isFaceAuthEnrolled after
|
||||||
|
// ag/20176811 is available.
|
||||||
|
if (
|
||||||
|
sensorBiometricType == BiometricType.FACE &&
|
||||||
|
userId == selectedUserId
|
||||||
|
) {
|
||||||
|
trySendWithFailureLogging(
|
||||||
|
hasEnrollments,
|
||||||
|
TAG,
|
||||||
|
"Face enrollment changed"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
authController.addCallback(callback)
|
||||||
|
trySendWithFailureLogging(
|
||||||
|
authController.isFaceAuthEnrolled(selectedUserId),
|
||||||
|
TAG,
|
||||||
|
"Initial value of face auth enrollment"
|
||||||
|
)
|
||||||
|
awaitClose { authController.removeCallback(callback) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override val isFaceAuthenticationEnabled: Flow<Boolean>
|
||||||
|
get() =
|
||||||
|
combine(isFaceEnabledByBiometricsManager, isFaceEnabledByDevicePolicy) {
|
||||||
|
biometricsManagerSetting,
|
||||||
|
devicePolicySetting ->
|
||||||
|
biometricsManagerSetting && devicePolicySetting
|
||||||
|
}
|
||||||
|
|
||||||
|
private val isFaceEnabledByDevicePolicy: Flow<Boolean> =
|
||||||
|
combine(selectedUserId, devicePolicyChangedForAllUsers) { userId, _ ->
|
||||||
|
devicePolicyManager.isFaceDisabled(userId)
|
||||||
|
}
|
||||||
|
.onStart {
|
||||||
|
emit(devicePolicyManager.isFaceDisabled(userRepository.getSelectedUserInfo().id))
|
||||||
|
}
|
||||||
|
.flowOn(backgroundDispatcher)
|
||||||
|
.distinctUntilChanged()
|
||||||
|
|
||||||
|
private val isFaceEnabledByBiometricsManager =
|
||||||
|
conflatedCallbackFlow {
|
||||||
|
val callback =
|
||||||
|
object : IBiometricEnabledOnKeyguardCallback.Stub() {
|
||||||
|
override fun onChanged(enabled: Boolean, userId: Int) {
|
||||||
|
trySendWithFailureLogging(
|
||||||
|
enabled,
|
||||||
|
TAG,
|
||||||
|
"biometricsEnabled state changed"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
biometricManager?.registerEnabledOnKeyguardCallback(callback)
|
||||||
|
awaitClose {}
|
||||||
|
}
|
||||||
|
// This is because the callback is binder-based and we want to avoid multiple callbacks
|
||||||
|
// being registered.
|
||||||
|
.stateIn(scope, SharingStarted.Eagerly, false)
|
||||||
|
|
||||||
override val isStrongBiometricAllowed: StateFlow<Boolean> =
|
override val isStrongBiometricAllowed: StateFlow<Boolean> =
|
||||||
selectedUserId
|
selectedUserId
|
||||||
.flatMapLatest { currUserId ->
|
.flatMapLatest { currUserId ->
|
||||||
@@ -169,17 +260,8 @@ constructor(
|
|||||||
override val isFingerprintEnabledByDevicePolicy: StateFlow<Boolean> =
|
override val isFingerprintEnabledByDevicePolicy: StateFlow<Boolean> =
|
||||||
selectedUserId
|
selectedUserId
|
||||||
.flatMapLatest { userId ->
|
.flatMapLatest { userId ->
|
||||||
broadcastDispatcher
|
devicePolicyChangedForAllUsers
|
||||||
.broadcastFlow(
|
.transformLatest { emit(devicePolicyManager.isFingerprintDisabled(userId)) }
|
||||||
filter = IntentFilter(ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED),
|
|
||||||
user = UserHandle.ALL
|
|
||||||
)
|
|
||||||
.transformLatest {
|
|
||||||
emit(
|
|
||||||
(devicePolicyManager.getKeyguardDisabledFeatures(null, userId) and
|
|
||||||
DevicePolicyManager.KEYGUARD_DISABLE_FINGERPRINT) == 0
|
|
||||||
)
|
|
||||||
}
|
|
||||||
.flowOn(backgroundDispatcher)
|
.flowOn(backgroundDispatcher)
|
||||||
.distinctUntilChanged()
|
.distinctUntilChanged()
|
||||||
}
|
}
|
||||||
@@ -187,13 +269,21 @@ constructor(
|
|||||||
scope,
|
scope,
|
||||||
started = SharingStarted.Eagerly,
|
started = SharingStarted.Eagerly,
|
||||||
initialValue =
|
initialValue =
|
||||||
devicePolicyManager.getKeyguardDisabledFeatures(
|
devicePolicyManager.isFingerprintDisabled(
|
||||||
null,
|
|
||||||
userRepository.getSelectedUserInfo().id
|
userRepository.getSelectedUserInfo().id
|
||||||
) and DevicePolicyManager.KEYGUARD_DISABLE_FINGERPRINT == 0
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
private const val TAG = "BiometricsRepositoryImpl"
|
private const val TAG = "BiometricsRepositoryImpl"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun DevicePolicyManager.isFaceDisabled(userId: Int): Boolean =
|
||||||
|
isNotActive(userId, DevicePolicyManager.KEYGUARD_DISABLE_FACE)
|
||||||
|
|
||||||
|
private fun DevicePolicyManager.isFingerprintDisabled(userId: Int): Boolean =
|
||||||
|
isNotActive(userId, DevicePolicyManager.KEYGUARD_DISABLE_FINGERPRINT)
|
||||||
|
|
||||||
|
private fun DevicePolicyManager.isNotActive(userId: Int, policy: Int): Boolean =
|
||||||
|
(getKeyguardDisabledFeatures(null, userId) and policy) == 0
|
||||||
|
|||||||
@@ -18,8 +18,12 @@
|
|||||||
package com.android.systemui.keyguard.data.repository
|
package com.android.systemui.keyguard.data.repository
|
||||||
|
|
||||||
import android.app.admin.DevicePolicyManager
|
import android.app.admin.DevicePolicyManager
|
||||||
|
import android.app.admin.DevicePolicyManager.KEYGUARD_DISABLE_FACE
|
||||||
|
import android.app.admin.DevicePolicyManager.KEYGUARD_DISABLE_FINGERPRINT
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.content.pm.UserInfo
|
import android.content.pm.UserInfo
|
||||||
|
import android.hardware.biometrics.BiometricManager
|
||||||
|
import android.hardware.biometrics.IBiometricEnabledOnKeyguardCallback
|
||||||
import android.testing.AndroidTestingRunner
|
import android.testing.AndroidTestingRunner
|
||||||
import android.testing.TestableLooper
|
import android.testing.TestableLooper
|
||||||
import androidx.test.filters.SmallTest
|
import androidx.test.filters.SmallTest
|
||||||
@@ -30,8 +34,13 @@ import com.android.systemui.SysuiTestCase
|
|||||||
import com.android.systemui.biometrics.AuthController
|
import com.android.systemui.biometrics.AuthController
|
||||||
import com.android.systemui.coroutines.collectLastValue
|
import com.android.systemui.coroutines.collectLastValue
|
||||||
import com.android.systemui.dump.DumpManager
|
import com.android.systemui.dump.DumpManager
|
||||||
|
import com.android.systemui.keyguard.data.repository.BiometricType.FACE
|
||||||
|
import com.android.systemui.keyguard.data.repository.BiometricType.REAR_FINGERPRINT
|
||||||
|
import com.android.systemui.keyguard.data.repository.BiometricType.SIDE_FINGERPRINT
|
||||||
|
import com.android.systemui.keyguard.data.repository.BiometricType.UNDER_DISPLAY_FINGERPRINT
|
||||||
import com.android.systemui.user.data.repository.FakeUserRepository
|
import com.android.systemui.user.data.repository.FakeUserRepository
|
||||||
import com.android.systemui.util.mockito.argumentCaptor
|
import com.android.systemui.util.mockito.argumentCaptor
|
||||||
|
import com.android.systemui.util.mockito.eq
|
||||||
import com.android.systemui.util.mockito.whenever
|
import com.android.systemui.util.mockito.whenever
|
||||||
import com.google.common.truth.Truth.assertThat
|
import com.google.common.truth.Truth.assertThat
|
||||||
import kotlinx.coroutines.test.StandardTestDispatcher
|
import kotlinx.coroutines.test.StandardTestDispatcher
|
||||||
@@ -42,9 +51,14 @@ import kotlinx.coroutines.test.runTest
|
|||||||
import org.junit.Before
|
import org.junit.Before
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
import org.junit.runner.RunWith
|
import org.junit.runner.RunWith
|
||||||
|
import org.mockito.ArgumentCaptor
|
||||||
import org.mockito.ArgumentMatchers.any
|
import org.mockito.ArgumentMatchers.any
|
||||||
import org.mockito.ArgumentMatchers.anyInt
|
import org.mockito.ArgumentMatchers.anyInt
|
||||||
|
import org.mockito.ArgumentMatchers.isNull
|
||||||
|
import org.mockito.Captor
|
||||||
import org.mockito.Mock
|
import org.mockito.Mock
|
||||||
|
import org.mockito.Mockito.clearInvocations
|
||||||
|
import org.mockito.Mockito.times
|
||||||
import org.mockito.Mockito.verify
|
import org.mockito.Mockito.verify
|
||||||
import org.mockito.MockitoAnnotations
|
import org.mockito.MockitoAnnotations
|
||||||
|
|
||||||
@@ -58,6 +72,11 @@ class BiometricSettingsRepositoryTest : SysuiTestCase() {
|
|||||||
@Mock private lateinit var lockPatternUtils: LockPatternUtils
|
@Mock private lateinit var lockPatternUtils: LockPatternUtils
|
||||||
@Mock private lateinit var devicePolicyManager: DevicePolicyManager
|
@Mock private lateinit var devicePolicyManager: DevicePolicyManager
|
||||||
@Mock private lateinit var dumpManager: DumpManager
|
@Mock private lateinit var dumpManager: DumpManager
|
||||||
|
@Mock private lateinit var biometricManager: BiometricManager
|
||||||
|
@Captor private lateinit var authControllerCallback: ArgumentCaptor<AuthController.Callback>
|
||||||
|
@Captor
|
||||||
|
private lateinit var biometricManagerCallback:
|
||||||
|
ArgumentCaptor<IBiometricEnabledOnKeyguardCallback.Stub>
|
||||||
private lateinit var userRepository: FakeUserRepository
|
private lateinit var userRepository: FakeUserRepository
|
||||||
|
|
||||||
private lateinit var testDispatcher: TestDispatcher
|
private lateinit var testDispatcher: TestDispatcher
|
||||||
@@ -74,7 +93,7 @@ class BiometricSettingsRepositoryTest : SysuiTestCase() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun createBiometricSettingsRepository() {
|
private suspend fun createBiometricSettingsRepository() {
|
||||||
userRepository.setUserInfos(listOf(PRIMARY_USER))
|
userRepository.setUserInfos(listOf(PRIMARY_USER, ANOTHER_USER))
|
||||||
userRepository.setSelectedUserInfo(PRIMARY_USER)
|
userRepository.setSelectedUserInfo(PRIMARY_USER)
|
||||||
underTest =
|
underTest =
|
||||||
BiometricSettingsRepositoryImpl(
|
BiometricSettingsRepositoryImpl(
|
||||||
@@ -88,33 +107,29 @@ class BiometricSettingsRepositoryTest : SysuiTestCase() {
|
|||||||
backgroundDispatcher = testDispatcher,
|
backgroundDispatcher = testDispatcher,
|
||||||
looper = testableLooper!!.looper,
|
looper = testableLooper!!.looper,
|
||||||
dumpManager = dumpManager,
|
dumpManager = dumpManager,
|
||||||
|
biometricManager = biometricManager,
|
||||||
)
|
)
|
||||||
|
testScope.runCurrent()
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun fingerprintEnrollmentChange() =
|
fun fingerprintEnrollmentChange() =
|
||||||
testScope.runTest {
|
testScope.runTest {
|
||||||
createBiometricSettingsRepository()
|
createBiometricSettingsRepository()
|
||||||
val fingerprintEnabledByDevicePolicy = collectLastValue(underTest.isFingerprintEnrolled)
|
val fingerprintEnrolled = collectLastValue(underTest.isFingerprintEnrolled)
|
||||||
runCurrent()
|
runCurrent()
|
||||||
|
|
||||||
val captor = argumentCaptor<AuthController.Callback>()
|
verify(authController).addCallback(authControllerCallback.capture())
|
||||||
verify(authController).addCallback(captor.capture())
|
|
||||||
whenever(authController.isFingerprintEnrolled(anyInt())).thenReturn(true)
|
whenever(authController.isFingerprintEnrolled(anyInt())).thenReturn(true)
|
||||||
captor.value.onEnrollmentsChanged(
|
enrollmentChange(UNDER_DISPLAY_FINGERPRINT, PRIMARY_USER_ID, true)
|
||||||
BiometricType.UNDER_DISPLAY_FINGERPRINT,
|
assertThat(fingerprintEnrolled()).isTrue()
|
||||||
PRIMARY_USER_ID,
|
|
||||||
true
|
|
||||||
)
|
|
||||||
assertThat(fingerprintEnabledByDevicePolicy()).isTrue()
|
|
||||||
|
|
||||||
whenever(authController.isFingerprintEnrolled(anyInt())).thenReturn(false)
|
whenever(authController.isFingerprintEnrolled(anyInt())).thenReturn(false)
|
||||||
captor.value.onEnrollmentsChanged(
|
enrollmentChange(UNDER_DISPLAY_FINGERPRINT, ANOTHER_USER_ID, false)
|
||||||
BiometricType.UNDER_DISPLAY_FINGERPRINT,
|
assertThat(fingerprintEnrolled()).isTrue()
|
||||||
PRIMARY_USER_ID,
|
|
||||||
false
|
enrollmentChange(UNDER_DISPLAY_FINGERPRINT, PRIMARY_USER_ID, false)
|
||||||
)
|
assertThat(fingerprintEnrolled()).isFalse()
|
||||||
assertThat(fingerprintEnabledByDevicePolicy()).isFalse()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -127,15 +142,14 @@ class BiometricSettingsRepositoryTest : SysuiTestCase() {
|
|||||||
val captor = argumentCaptor<LockPatternUtils.StrongAuthTracker>()
|
val captor = argumentCaptor<LockPatternUtils.StrongAuthTracker>()
|
||||||
verify(lockPatternUtils).registerStrongAuthTracker(captor.capture())
|
verify(lockPatternUtils).registerStrongAuthTracker(captor.capture())
|
||||||
|
|
||||||
captor.value
|
captor.value.stub.onStrongAuthRequiredChanged(STRONG_AUTH_NOT_REQUIRED, PRIMARY_USER_ID)
|
||||||
.getStub()
|
|
||||||
.onStrongAuthRequiredChanged(STRONG_AUTH_NOT_REQUIRED, PRIMARY_USER_ID)
|
|
||||||
testableLooper?.processAllMessages() // StrongAuthTracker uses the TestableLooper
|
testableLooper?.processAllMessages() // StrongAuthTracker uses the TestableLooper
|
||||||
assertThat(strongBiometricAllowed()).isTrue()
|
assertThat(strongBiometricAllowed()).isTrue()
|
||||||
|
|
||||||
captor.value
|
captor.value.stub.onStrongAuthRequiredChanged(
|
||||||
.getStub()
|
STRONG_AUTH_REQUIRED_AFTER_BOOT,
|
||||||
.onStrongAuthRequiredChanged(STRONG_AUTH_REQUIRED_AFTER_BOOT, PRIMARY_USER_ID)
|
PRIMARY_USER_ID
|
||||||
|
)
|
||||||
testableLooper?.processAllMessages() // StrongAuthTracker uses the TestableLooper
|
testableLooper?.processAllMessages() // StrongAuthTracker uses the TestableLooper
|
||||||
assertThat(strongBiometricAllowed()).isFalse()
|
assertThat(strongBiometricAllowed()).isFalse()
|
||||||
}
|
}
|
||||||
@@ -149,7 +163,7 @@ class BiometricSettingsRepositoryTest : SysuiTestCase() {
|
|||||||
runCurrent()
|
runCurrent()
|
||||||
|
|
||||||
whenever(devicePolicyManager.getKeyguardDisabledFeatures(any(), anyInt()))
|
whenever(devicePolicyManager.getKeyguardDisabledFeatures(any(), anyInt()))
|
||||||
.thenReturn(DevicePolicyManager.KEYGUARD_DISABLE_FINGERPRINT)
|
.thenReturn(KEYGUARD_DISABLE_FINGERPRINT)
|
||||||
broadcastDPMStateChange()
|
broadcastDPMStateChange()
|
||||||
assertThat(fingerprintEnabledByDevicePolicy()).isFalse()
|
assertThat(fingerprintEnabledByDevicePolicy()).isFalse()
|
||||||
|
|
||||||
@@ -158,6 +172,137 @@ class BiometricSettingsRepositoryTest : SysuiTestCase() {
|
|||||||
assertThat(fingerprintEnabledByDevicePolicy()).isTrue()
|
assertThat(fingerprintEnabledByDevicePolicy()).isTrue()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun faceEnrollmentChangeIsPropagatedForTheCurrentUser() =
|
||||||
|
testScope.runTest {
|
||||||
|
createBiometricSettingsRepository()
|
||||||
|
runCurrent()
|
||||||
|
clearInvocations(authController)
|
||||||
|
|
||||||
|
whenever(authController.isFaceAuthEnrolled(PRIMARY_USER_ID)).thenReturn(false)
|
||||||
|
val faceEnrolled = collectLastValue(underTest.isFaceEnrolled)
|
||||||
|
|
||||||
|
assertThat(faceEnrolled()).isFalse()
|
||||||
|
verify(authController).addCallback(authControllerCallback.capture())
|
||||||
|
enrollmentChange(REAR_FINGERPRINT, PRIMARY_USER_ID, true)
|
||||||
|
|
||||||
|
assertThat(faceEnrolled()).isFalse()
|
||||||
|
|
||||||
|
enrollmentChange(SIDE_FINGERPRINT, PRIMARY_USER_ID, true)
|
||||||
|
|
||||||
|
assertThat(faceEnrolled()).isFalse()
|
||||||
|
|
||||||
|
enrollmentChange(UNDER_DISPLAY_FINGERPRINT, PRIMARY_USER_ID, true)
|
||||||
|
|
||||||
|
assertThat(faceEnrolled()).isFalse()
|
||||||
|
|
||||||
|
enrollmentChange(FACE, ANOTHER_USER_ID, true)
|
||||||
|
|
||||||
|
assertThat(faceEnrolled()).isFalse()
|
||||||
|
|
||||||
|
enrollmentChange(FACE, PRIMARY_USER_ID, true)
|
||||||
|
|
||||||
|
assertThat(faceEnrolled()).isTrue()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun faceEnrollmentStatusOfNewUserUponUserSwitch() =
|
||||||
|
testScope.runTest {
|
||||||
|
createBiometricSettingsRepository()
|
||||||
|
runCurrent()
|
||||||
|
clearInvocations(authController)
|
||||||
|
|
||||||
|
whenever(authController.isFaceAuthEnrolled(PRIMARY_USER_ID)).thenReturn(false)
|
||||||
|
whenever(authController.isFaceAuthEnrolled(ANOTHER_USER_ID)).thenReturn(true)
|
||||||
|
val faceEnrolled = collectLastValue(underTest.isFaceEnrolled)
|
||||||
|
|
||||||
|
assertThat(faceEnrolled()).isFalse()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun faceEnrollmentChangesArePropagatedAfterUserSwitch() =
|
||||||
|
testScope.runTest {
|
||||||
|
createBiometricSettingsRepository()
|
||||||
|
|
||||||
|
userRepository.setSelectedUserInfo(ANOTHER_USER)
|
||||||
|
runCurrent()
|
||||||
|
clearInvocations(authController)
|
||||||
|
|
||||||
|
val faceEnrolled = collectLastValue(underTest.isFaceEnrolled)
|
||||||
|
runCurrent()
|
||||||
|
|
||||||
|
verify(authController).addCallback(authControllerCallback.capture())
|
||||||
|
|
||||||
|
enrollmentChange(FACE, ANOTHER_USER_ID, true)
|
||||||
|
|
||||||
|
assertThat(faceEnrolled()).isTrue()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun devicePolicyControlsFaceAuthenticationEnabledState() =
|
||||||
|
testScope.runTest {
|
||||||
|
createBiometricSettingsRepository()
|
||||||
|
verify(biometricManager)
|
||||||
|
.registerEnabledOnKeyguardCallback(biometricManagerCallback.capture())
|
||||||
|
|
||||||
|
whenever(devicePolicyManager.getKeyguardDisabledFeatures(isNull(), eq(PRIMARY_USER_ID)))
|
||||||
|
.thenReturn(KEYGUARD_DISABLE_FINGERPRINT or KEYGUARD_DISABLE_FACE)
|
||||||
|
|
||||||
|
val isFaceAuthEnabled = collectLastValue(underTest.isFaceAuthenticationEnabled)
|
||||||
|
runCurrent()
|
||||||
|
|
||||||
|
broadcastDPMStateChange()
|
||||||
|
|
||||||
|
assertThat(isFaceAuthEnabled()).isFalse()
|
||||||
|
|
||||||
|
biometricManagerCallback.value.onChanged(true, PRIMARY_USER_ID)
|
||||||
|
runCurrent()
|
||||||
|
assertThat(isFaceAuthEnabled()).isFalse()
|
||||||
|
|
||||||
|
whenever(devicePolicyManager.getKeyguardDisabledFeatures(isNull(), eq(PRIMARY_USER_ID)))
|
||||||
|
.thenReturn(KEYGUARD_DISABLE_FINGERPRINT)
|
||||||
|
broadcastDPMStateChange()
|
||||||
|
|
||||||
|
assertThat(isFaceAuthEnabled()).isTrue()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun biometricManagerControlsFaceAuthenticationEnabledStatus() =
|
||||||
|
testScope.runTest {
|
||||||
|
createBiometricSettingsRepository()
|
||||||
|
verify(biometricManager)
|
||||||
|
.registerEnabledOnKeyguardCallback(biometricManagerCallback.capture())
|
||||||
|
|
||||||
|
whenever(devicePolicyManager.getKeyguardDisabledFeatures(isNull(), eq(PRIMARY_USER_ID)))
|
||||||
|
.thenReturn(0)
|
||||||
|
broadcastDPMStateChange()
|
||||||
|
|
||||||
|
biometricManagerCallback.value.onChanged(true, PRIMARY_USER_ID)
|
||||||
|
val isFaceAuthEnabled = collectLastValue(underTest.isFaceAuthenticationEnabled)
|
||||||
|
|
||||||
|
assertThat(isFaceAuthEnabled()).isTrue()
|
||||||
|
|
||||||
|
biometricManagerCallback.value.onChanged(false, PRIMARY_USER_ID)
|
||||||
|
|
||||||
|
assertThat(isFaceAuthEnabled()).isFalse()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun biometricManagerCallbackIsRegisteredOnlyOnce() =
|
||||||
|
testScope.runTest {
|
||||||
|
createBiometricSettingsRepository()
|
||||||
|
|
||||||
|
collectLastValue(underTest.isFaceAuthenticationEnabled)()
|
||||||
|
collectLastValue(underTest.isFaceAuthenticationEnabled)()
|
||||||
|
collectLastValue(underTest.isFaceAuthenticationEnabled)()
|
||||||
|
|
||||||
|
verify(biometricManager, times(1)).registerEnabledOnKeyguardCallback(any())
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun enrollmentChange(biometricType: BiometricType, userId: Int, enabled: Boolean) {
|
||||||
|
authControllerCallback.value.onEnrollmentsChanged(biometricType, userId, enabled)
|
||||||
|
}
|
||||||
|
|
||||||
private fun broadcastDPMStateChange() {
|
private fun broadcastDPMStateChange() {
|
||||||
fakeBroadcastDispatcher.registeredReceivers.forEach { receiver ->
|
fakeBroadcastDispatcher.registeredReceivers.forEach { receiver ->
|
||||||
receiver.onReceive(
|
receiver.onReceive(
|
||||||
@@ -175,5 +320,13 @@ class BiometricSettingsRepositoryTest : SysuiTestCase() {
|
|||||||
/* name= */ "primary user",
|
/* name= */ "primary user",
|
||||||
/* flags= */ UserInfo.FLAG_PRIMARY
|
/* flags= */ UserInfo.FLAG_PRIMARY
|
||||||
)
|
)
|
||||||
|
|
||||||
|
private const val ANOTHER_USER_ID = 1
|
||||||
|
private val ANOTHER_USER =
|
||||||
|
UserInfo(
|
||||||
|
/* id= */ ANOTHER_USER_ID,
|
||||||
|
/* name= */ "another user",
|
||||||
|
/* flags= */ UserInfo.FLAG_PRIMARY
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,6 +17,7 @@
|
|||||||
|
|
||||||
package com.android.systemui.keyguard.data.repository
|
package com.android.systemui.keyguard.data.repository
|
||||||
|
|
||||||
|
import kotlinx.coroutines.flow.Flow
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
import kotlinx.coroutines.flow.StateFlow
|
import kotlinx.coroutines.flow.StateFlow
|
||||||
import kotlinx.coroutines.flow.asStateFlow
|
import kotlinx.coroutines.flow.asStateFlow
|
||||||
@@ -26,6 +27,14 @@ class FakeBiometricSettingsRepository : BiometricSettingsRepository {
|
|||||||
private val _isFingerprintEnrolled = MutableStateFlow<Boolean>(false)
|
private val _isFingerprintEnrolled = MutableStateFlow<Boolean>(false)
|
||||||
override val isFingerprintEnrolled: StateFlow<Boolean> = _isFingerprintEnrolled.asStateFlow()
|
override val isFingerprintEnrolled: StateFlow<Boolean> = _isFingerprintEnrolled.asStateFlow()
|
||||||
|
|
||||||
|
private val _isFaceEnrolled = MutableStateFlow(false)
|
||||||
|
override val isFaceEnrolled: Flow<Boolean>
|
||||||
|
get() = _isFaceEnrolled
|
||||||
|
|
||||||
|
private val _isFaceAuthEnabled = MutableStateFlow(false)
|
||||||
|
override val isFaceAuthenticationEnabled: Flow<Boolean>
|
||||||
|
get() = _isFaceAuthEnabled
|
||||||
|
|
||||||
private val _isStrongBiometricAllowed = MutableStateFlow(false)
|
private val _isStrongBiometricAllowed = MutableStateFlow(false)
|
||||||
override val isStrongBiometricAllowed = _isStrongBiometricAllowed.asStateFlow()
|
override val isStrongBiometricAllowed = _isStrongBiometricAllowed.asStateFlow()
|
||||||
|
|
||||||
@@ -44,4 +53,12 @@ class FakeBiometricSettingsRepository : BiometricSettingsRepository {
|
|||||||
fun setFingerprintEnabledByDevicePolicy(isFingerprintEnabledByDevicePolicy: Boolean) {
|
fun setFingerprintEnabledByDevicePolicy(isFingerprintEnabledByDevicePolicy: Boolean) {
|
||||||
_isFingerprintEnabledByDevicePolicy.value = isFingerprintEnabledByDevicePolicy
|
_isFingerprintEnabledByDevicePolicy.value = isFingerprintEnabledByDevicePolicy
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun setFaceEnrolled(isFaceEnrolled: Boolean) {
|
||||||
|
_isFaceEnrolled.value = isFaceEnrolled
|
||||||
|
}
|
||||||
|
|
||||||
|
fun setIsFaceAuthEnabled(enabled: Boolean) {
|
||||||
|
_isFaceAuthEnabled.value = enabled
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user