Loading packages/SystemUI/src/com/android/systemui/keyguard/data/repository/BiometricSettingsRepository.kt +72 −37 Original line number Diff line number Diff line Loading @@ -22,10 +22,10 @@ import android.content.Context import android.content.IntentFilter import android.hardware.biometrics.BiometricManager import android.hardware.biometrics.IBiometricEnabledOnKeyguardCallback import android.os.Looper import android.os.UserHandle import android.util.Log import com.android.internal.widget.LockPatternUtils import com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN import com.android.systemui.Dumpable import com.android.systemui.R import com.android.systemui.biometrics.AuthController Loading @@ -35,8 +35,8 @@ import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCall import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.dagger.qualifiers.Main import com.android.systemui.dump.DumpManager import com.android.systemui.keyguard.TAG import com.android.systemui.keyguard.shared.model.DevicePosture import com.android.systemui.user.data.repository.UserRepository import java.io.PrintWriter Loading @@ -45,10 +45,12 @@ import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.channels.awaitClose import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.filter import kotlinx.coroutines.flow.flatMapLatest import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.flow.flowOn Loading Loading @@ -93,8 +95,16 @@ interface BiometricSettingsRepository { * restricted to specific postures using [R.integer.config_face_auth_supported_posture] */ val isFaceAuthSupportedInCurrentPosture: Flow<Boolean> /** * Whether the user manually locked down the device. This doesn't include device policy manager * lockdown. */ val isCurrentUserInLockdown: Flow<Boolean> } const val TAG = "BiometricsRepositoryImpl" @SysUISingleton class BiometricSettingsRepositoryImpl @Inject Loading @@ -103,19 +113,25 @@ constructor( lockPatternUtils: LockPatternUtils, broadcastDispatcher: BroadcastDispatcher, authController: AuthController, userRepository: UserRepository, private val userRepository: UserRepository, devicePolicyManager: DevicePolicyManager, @Application scope: CoroutineScope, @Background backgroundDispatcher: CoroutineDispatcher, biometricManager: BiometricManager?, @Main looper: Looper, devicePostureRepository: DevicePostureRepository, dumpManager: DumpManager, ) : BiometricSettingsRepository, Dumpable { override val isFaceAuthSupportedInCurrentPosture: Flow<Boolean> private val strongAuthTracker = StrongAuthTracker(userRepository, context) override val isCurrentUserInLockdown: Flow<Boolean> = strongAuthTracker.currentUserAuthFlags.map { it.isInUserLockdown } init { Log.d(TAG, "Registering StrongAuthTracker") lockPatternUtils.registerStrongAuthTracker(strongAuthTracker) dumpManager.registerDumpable(this) val configFaceAuthSupportedPosture = DevicePosture.toPosture( Loading Loading @@ -251,35 +267,11 @@ constructor( .stateIn(scope, SharingStarted.Eagerly, false) override val isStrongBiometricAllowed: StateFlow<Boolean> = selectedUserId .flatMapLatest { currUserId -> conflatedCallbackFlow { val callback = object : LockPatternUtils.StrongAuthTracker(context, looper) { override fun onStrongAuthRequiredChanged(userId: Int) { if (currUserId != userId) { return } trySendWithFailureLogging( isBiometricAllowedForUser(true, currUserId), TAG ) } override fun onIsNonStrongBiometricAllowedChanged(userId: Int) { // no-op } } lockPatternUtils.registerStrongAuthTracker(callback) awaitClose { lockPatternUtils.unregisterStrongAuthTracker(callback) } } } .stateIn( strongAuthTracker.isStrongBiometricAllowed.stateIn( scope, started = SharingStarted.Eagerly, initialValue = lockPatternUtils.isBiometricAllowedForUser( SharingStarted.Eagerly, strongAuthTracker.isBiometricAllowedForUser( true, userRepository.getSelectedUserInfo().id ) ) Loading @@ -300,9 +292,44 @@ constructor( userRepository.getSelectedUserInfo().id ) ) } private class StrongAuthTracker(private val userRepository: UserRepository, context: Context?) : LockPatternUtils.StrongAuthTracker(context) { private val _authFlags = MutableStateFlow( StrongAuthenticationFlags(currentUserId, getStrongAuthForUser(currentUserId)) ) companion object { private const val TAG = "BiometricsRepositoryImpl" val currentUserAuthFlags: Flow<StrongAuthenticationFlags> = userRepository.selectedUserInfo .map { it.id } .distinctUntilChanged() .flatMapLatest { currUserId -> _authFlags .filter { it.userId == currUserId } .onEach { Log.d(TAG, "currentUser authFlags changed, new value: $it") } .onStart { emit( StrongAuthenticationFlags( currentUserId, getStrongAuthForUser(currentUserId) ) ) } } val isStrongBiometricAllowed: Flow<Boolean> = currentUserAuthFlags.map { isBiometricAllowedForUser(true, it.userId) } private val currentUserId get() = userRepository.getSelectedUserInfo().id override fun onStrongAuthRequiredChanged(userId: Int) { val newFlags = getStrongAuthForUser(userId) _authFlags.value = StrongAuthenticationFlags(userId, newFlags) Log.d(TAG, "onStrongAuthRequiredChanged for userId: $userId, flag value: $newFlags") } } Loading @@ -314,3 +341,11 @@ private fun DevicePolicyManager.isFingerprintDisabled(userId: Int): Boolean = private fun DevicePolicyManager.isNotActive(userId: Int, policy: Int): Boolean = (getKeyguardDisabledFeatures(null, userId) and policy) == 0 private data class StrongAuthenticationFlags(val userId: Int, val flag: Int) { val isInUserLockdown = containsFlag(flag, STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN) } private fun containsFlag(haystack: Int, needle: Int): Boolean { return haystack and needle != 0 } packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/BiometricSettingsRepositoryTest.kt +37 −13 Original line number Diff line number Diff line Loading @@ -30,6 +30,8 @@ import androidx.test.filters.SmallTest import com.android.internal.widget.LockPatternUtils import com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_NOT_REQUIRED import com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_BOOT import com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_TIMEOUT import com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN import com.android.systemui.R import com.android.systemui.SysuiTestCase import com.android.systemui.biometrics.AuthController Loading @@ -42,7 +44,6 @@ import com.android.systemui.keyguard.data.repository.BiometricType.UNDER_DISPLAY import com.android.systemui.keyguard.shared.model.DevicePosture import com.android.systemui.statusbar.policy.DevicePostureController import com.android.systemui.user.data.repository.FakeUserRepository import com.android.systemui.util.mockito.argumentCaptor import com.android.systemui.util.mockito.eq import com.android.systemui.util.mockito.whenever import com.google.common.truth.Truth.assertThat Loading Loading @@ -78,6 +79,8 @@ class BiometricSettingsRepositoryTest : SysuiTestCase() { @Mock private lateinit var devicePolicyManager: DevicePolicyManager @Mock private lateinit var dumpManager: DumpManager @Mock private lateinit var biometricManager: BiometricManager @Captor private lateinit var strongAuthTracker: ArgumentCaptor<LockPatternUtils.StrongAuthTracker> @Captor private lateinit var authControllerCallback: ArgumentCaptor<AuthController.Callback> @Captor private lateinit var biometricManagerCallback: Loading Loading @@ -112,12 +115,12 @@ class BiometricSettingsRepositoryTest : SysuiTestCase() { devicePolicyManager = devicePolicyManager, scope = testScope.backgroundScope, backgroundDispatcher = testDispatcher, looper = testableLooper!!.looper, dumpManager = dumpManager, biometricManager = biometricManager, devicePostureRepository = devicePostureRepository, dumpManager = dumpManager, ) testScope.runCurrent() verify(lockPatternUtils).registerStrongAuthTracker(strongAuthTracker.capture()) } @Test Loading Loading @@ -147,21 +150,18 @@ class BiometricSettingsRepositoryTest : SysuiTestCase() { val strongBiometricAllowed = collectLastValue(underTest.isStrongBiometricAllowed) runCurrent() val captor = argumentCaptor<LockPatternUtils.StrongAuthTracker>() verify(lockPatternUtils).registerStrongAuthTracker(captor.capture()) captor.value.stub.onStrongAuthRequiredChanged(STRONG_AUTH_NOT_REQUIRED, PRIMARY_USER_ID) testableLooper?.processAllMessages() // StrongAuthTracker uses the TestableLooper onStrongAuthChanged(STRONG_AUTH_NOT_REQUIRED, PRIMARY_USER_ID) assertThat(strongBiometricAllowed()).isTrue() captor.value.stub.onStrongAuthRequiredChanged( STRONG_AUTH_REQUIRED_AFTER_BOOT, PRIMARY_USER_ID ) testableLooper?.processAllMessages() // StrongAuthTracker uses the TestableLooper onStrongAuthChanged(STRONG_AUTH_REQUIRED_AFTER_BOOT, PRIMARY_USER_ID) assertThat(strongBiometricAllowed()).isFalse() } private fun onStrongAuthChanged(flags: Int, userId: Int) { strongAuthTracker.value.stub.onStrongAuthRequiredChanged(flags, userId) testableLooper?.processAllMessages() // StrongAuthTracker uses the TestableLooper } @Test fun fingerprintDisabledByDpmChange() = testScope.runTest { Loading Loading @@ -351,6 +351,30 @@ class BiometricSettingsRepositoryTest : SysuiTestCase() { assertThat(isFaceAuthSupported()).isTrue() } @Test fun userInLockdownUsesStrongAuthFlagsToDetermineValue() = testScope.runTest { createBiometricSettingsRepository() val isUserInLockdown = collectLastValue(underTest.isCurrentUserInLockdown) // has default value. assertThat(isUserInLockdown()).isFalse() // change strong auth flags for another user. // Combine with one more flag to check if we do the bitwise and val inLockdown = STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN or STRONG_AUTH_REQUIRED_AFTER_TIMEOUT onStrongAuthChanged(inLockdown, ANOTHER_USER_ID) // Still false. assertThat(isUserInLockdown()).isFalse() // change strong auth flags for current user. onStrongAuthChanged(inLockdown, PRIMARY_USER_ID) assertThat(isUserInLockdown()).isTrue() } private fun enrollmentChange(biometricType: BiometricType, userId: Int, enabled: Boolean) { authControllerCallback.value.onEnrollmentsChanged(biometricType, userId, enabled) } Loading packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeBiometricSettingsRepository.kt +4 −0 Original line number Diff line number Diff line Loading @@ -46,6 +46,10 @@ class FakeBiometricSettingsRepository : BiometricSettingsRepository { override val isFaceAuthSupportedInCurrentPosture: Flow<Boolean> get() = flowOf(true) private val _isCurrentUserInLockdown = MutableStateFlow(false) override val isCurrentUserInLockdown: Flow<Boolean> get() = _isCurrentUserInLockdown fun setFingerprintEnrolled(isFingerprintEnrolled: Boolean) { _isFingerprintEnrolled.value = isFingerprintEnrolled } Loading Loading
packages/SystemUI/src/com/android/systemui/keyguard/data/repository/BiometricSettingsRepository.kt +72 −37 Original line number Diff line number Diff line Loading @@ -22,10 +22,10 @@ import android.content.Context import android.content.IntentFilter import android.hardware.biometrics.BiometricManager import android.hardware.biometrics.IBiometricEnabledOnKeyguardCallback import android.os.Looper import android.os.UserHandle import android.util.Log import com.android.internal.widget.LockPatternUtils import com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN import com.android.systemui.Dumpable import com.android.systemui.R import com.android.systemui.biometrics.AuthController Loading @@ -35,8 +35,8 @@ import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCall import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.dagger.qualifiers.Main import com.android.systemui.dump.DumpManager import com.android.systemui.keyguard.TAG import com.android.systemui.keyguard.shared.model.DevicePosture import com.android.systemui.user.data.repository.UserRepository import java.io.PrintWriter Loading @@ -45,10 +45,12 @@ import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.channels.awaitClose import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.filter import kotlinx.coroutines.flow.flatMapLatest import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.flow.flowOn Loading Loading @@ -93,8 +95,16 @@ interface BiometricSettingsRepository { * restricted to specific postures using [R.integer.config_face_auth_supported_posture] */ val isFaceAuthSupportedInCurrentPosture: Flow<Boolean> /** * Whether the user manually locked down the device. This doesn't include device policy manager * lockdown. */ val isCurrentUserInLockdown: Flow<Boolean> } const val TAG = "BiometricsRepositoryImpl" @SysUISingleton class BiometricSettingsRepositoryImpl @Inject Loading @@ -103,19 +113,25 @@ constructor( lockPatternUtils: LockPatternUtils, broadcastDispatcher: BroadcastDispatcher, authController: AuthController, userRepository: UserRepository, private val userRepository: UserRepository, devicePolicyManager: DevicePolicyManager, @Application scope: CoroutineScope, @Background backgroundDispatcher: CoroutineDispatcher, biometricManager: BiometricManager?, @Main looper: Looper, devicePostureRepository: DevicePostureRepository, dumpManager: DumpManager, ) : BiometricSettingsRepository, Dumpable { override val isFaceAuthSupportedInCurrentPosture: Flow<Boolean> private val strongAuthTracker = StrongAuthTracker(userRepository, context) override val isCurrentUserInLockdown: Flow<Boolean> = strongAuthTracker.currentUserAuthFlags.map { it.isInUserLockdown } init { Log.d(TAG, "Registering StrongAuthTracker") lockPatternUtils.registerStrongAuthTracker(strongAuthTracker) dumpManager.registerDumpable(this) val configFaceAuthSupportedPosture = DevicePosture.toPosture( Loading Loading @@ -251,35 +267,11 @@ constructor( .stateIn(scope, SharingStarted.Eagerly, false) override val isStrongBiometricAllowed: StateFlow<Boolean> = selectedUserId .flatMapLatest { currUserId -> conflatedCallbackFlow { val callback = object : LockPatternUtils.StrongAuthTracker(context, looper) { override fun onStrongAuthRequiredChanged(userId: Int) { if (currUserId != userId) { return } trySendWithFailureLogging( isBiometricAllowedForUser(true, currUserId), TAG ) } override fun onIsNonStrongBiometricAllowedChanged(userId: Int) { // no-op } } lockPatternUtils.registerStrongAuthTracker(callback) awaitClose { lockPatternUtils.unregisterStrongAuthTracker(callback) } } } .stateIn( strongAuthTracker.isStrongBiometricAllowed.stateIn( scope, started = SharingStarted.Eagerly, initialValue = lockPatternUtils.isBiometricAllowedForUser( SharingStarted.Eagerly, strongAuthTracker.isBiometricAllowedForUser( true, userRepository.getSelectedUserInfo().id ) ) Loading @@ -300,9 +292,44 @@ constructor( userRepository.getSelectedUserInfo().id ) ) } private class StrongAuthTracker(private val userRepository: UserRepository, context: Context?) : LockPatternUtils.StrongAuthTracker(context) { private val _authFlags = MutableStateFlow( StrongAuthenticationFlags(currentUserId, getStrongAuthForUser(currentUserId)) ) companion object { private const val TAG = "BiometricsRepositoryImpl" val currentUserAuthFlags: Flow<StrongAuthenticationFlags> = userRepository.selectedUserInfo .map { it.id } .distinctUntilChanged() .flatMapLatest { currUserId -> _authFlags .filter { it.userId == currUserId } .onEach { Log.d(TAG, "currentUser authFlags changed, new value: $it") } .onStart { emit( StrongAuthenticationFlags( currentUserId, getStrongAuthForUser(currentUserId) ) ) } } val isStrongBiometricAllowed: Flow<Boolean> = currentUserAuthFlags.map { isBiometricAllowedForUser(true, it.userId) } private val currentUserId get() = userRepository.getSelectedUserInfo().id override fun onStrongAuthRequiredChanged(userId: Int) { val newFlags = getStrongAuthForUser(userId) _authFlags.value = StrongAuthenticationFlags(userId, newFlags) Log.d(TAG, "onStrongAuthRequiredChanged for userId: $userId, flag value: $newFlags") } } Loading @@ -314,3 +341,11 @@ private fun DevicePolicyManager.isFingerprintDisabled(userId: Int): Boolean = private fun DevicePolicyManager.isNotActive(userId: Int, policy: Int): Boolean = (getKeyguardDisabledFeatures(null, userId) and policy) == 0 private data class StrongAuthenticationFlags(val userId: Int, val flag: Int) { val isInUserLockdown = containsFlag(flag, STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN) } private fun containsFlag(haystack: Int, needle: Int): Boolean { return haystack and needle != 0 }
packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/BiometricSettingsRepositoryTest.kt +37 −13 Original line number Diff line number Diff line Loading @@ -30,6 +30,8 @@ import androidx.test.filters.SmallTest import com.android.internal.widget.LockPatternUtils import com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_NOT_REQUIRED import com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_BOOT import com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_TIMEOUT import com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN import com.android.systemui.R import com.android.systemui.SysuiTestCase import com.android.systemui.biometrics.AuthController Loading @@ -42,7 +44,6 @@ import com.android.systemui.keyguard.data.repository.BiometricType.UNDER_DISPLAY import com.android.systemui.keyguard.shared.model.DevicePosture import com.android.systemui.statusbar.policy.DevicePostureController import com.android.systemui.user.data.repository.FakeUserRepository import com.android.systemui.util.mockito.argumentCaptor import com.android.systemui.util.mockito.eq import com.android.systemui.util.mockito.whenever import com.google.common.truth.Truth.assertThat Loading Loading @@ -78,6 +79,8 @@ class BiometricSettingsRepositoryTest : SysuiTestCase() { @Mock private lateinit var devicePolicyManager: DevicePolicyManager @Mock private lateinit var dumpManager: DumpManager @Mock private lateinit var biometricManager: BiometricManager @Captor private lateinit var strongAuthTracker: ArgumentCaptor<LockPatternUtils.StrongAuthTracker> @Captor private lateinit var authControllerCallback: ArgumentCaptor<AuthController.Callback> @Captor private lateinit var biometricManagerCallback: Loading Loading @@ -112,12 +115,12 @@ class BiometricSettingsRepositoryTest : SysuiTestCase() { devicePolicyManager = devicePolicyManager, scope = testScope.backgroundScope, backgroundDispatcher = testDispatcher, looper = testableLooper!!.looper, dumpManager = dumpManager, biometricManager = biometricManager, devicePostureRepository = devicePostureRepository, dumpManager = dumpManager, ) testScope.runCurrent() verify(lockPatternUtils).registerStrongAuthTracker(strongAuthTracker.capture()) } @Test Loading Loading @@ -147,21 +150,18 @@ class BiometricSettingsRepositoryTest : SysuiTestCase() { val strongBiometricAllowed = collectLastValue(underTest.isStrongBiometricAllowed) runCurrent() val captor = argumentCaptor<LockPatternUtils.StrongAuthTracker>() verify(lockPatternUtils).registerStrongAuthTracker(captor.capture()) captor.value.stub.onStrongAuthRequiredChanged(STRONG_AUTH_NOT_REQUIRED, PRIMARY_USER_ID) testableLooper?.processAllMessages() // StrongAuthTracker uses the TestableLooper onStrongAuthChanged(STRONG_AUTH_NOT_REQUIRED, PRIMARY_USER_ID) assertThat(strongBiometricAllowed()).isTrue() captor.value.stub.onStrongAuthRequiredChanged( STRONG_AUTH_REQUIRED_AFTER_BOOT, PRIMARY_USER_ID ) testableLooper?.processAllMessages() // StrongAuthTracker uses the TestableLooper onStrongAuthChanged(STRONG_AUTH_REQUIRED_AFTER_BOOT, PRIMARY_USER_ID) assertThat(strongBiometricAllowed()).isFalse() } private fun onStrongAuthChanged(flags: Int, userId: Int) { strongAuthTracker.value.stub.onStrongAuthRequiredChanged(flags, userId) testableLooper?.processAllMessages() // StrongAuthTracker uses the TestableLooper } @Test fun fingerprintDisabledByDpmChange() = testScope.runTest { Loading Loading @@ -351,6 +351,30 @@ class BiometricSettingsRepositoryTest : SysuiTestCase() { assertThat(isFaceAuthSupported()).isTrue() } @Test fun userInLockdownUsesStrongAuthFlagsToDetermineValue() = testScope.runTest { createBiometricSettingsRepository() val isUserInLockdown = collectLastValue(underTest.isCurrentUserInLockdown) // has default value. assertThat(isUserInLockdown()).isFalse() // change strong auth flags for another user. // Combine with one more flag to check if we do the bitwise and val inLockdown = STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN or STRONG_AUTH_REQUIRED_AFTER_TIMEOUT onStrongAuthChanged(inLockdown, ANOTHER_USER_ID) // Still false. assertThat(isUserInLockdown()).isFalse() // change strong auth flags for current user. onStrongAuthChanged(inLockdown, PRIMARY_USER_ID) assertThat(isUserInLockdown()).isTrue() } private fun enrollmentChange(biometricType: BiometricType, userId: Int, enabled: Boolean) { authControllerCallback.value.onEnrollmentsChanged(biometricType, userId, enabled) } Loading
packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeBiometricSettingsRepository.kt +4 −0 Original line number Diff line number Diff line Loading @@ -46,6 +46,10 @@ class FakeBiometricSettingsRepository : BiometricSettingsRepository { override val isFaceAuthSupportedInCurrentPosture: Flow<Boolean> get() = flowOf(true) private val _isCurrentUserInLockdown = MutableStateFlow(false) override val isCurrentUserInLockdown: Flow<Boolean> get() = _isCurrentUserInLockdown fun setFingerprintEnrolled(isFingerprintEnrolled: Boolean) { _isFingerprintEnrolled.value = isFingerprintEnrolled } Loading