diff options
3 files changed, 113 insertions, 50 deletions
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/BiometricSettingsRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/BiometricSettingsRepository.kt index 84abf57cacf2..d5129a612b04 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/BiometricSettingsRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/BiometricSettingsRepository.kt @@ -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 @@ -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 @@ -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 @@ -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 @@ -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( @@ -251,38 +267,14 @@ 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( - scope, - started = SharingStarted.Eagerly, - initialValue = - lockPatternUtils.isBiometricAllowedForUser( - userRepository.getSelectedUserInfo().id - ) + strongAuthTracker.isStrongBiometricAllowed.stateIn( + scope, + SharingStarted.Eagerly, + strongAuthTracker.isBiometricAllowedForUser( + true, + userRepository.getSelectedUserInfo().id ) + ) override val isFingerprintEnabledByDevicePolicy: StateFlow<Boolean> = selectedUserId @@ -300,9 +292,44 @@ constructor( userRepository.getSelectedUserInfo().id ) ) +} - companion object { - private const val TAG = "BiometricsRepositoryImpl" +private class StrongAuthTracker(private val userRepository: UserRepository, context: Context?) : + LockPatternUtils.StrongAuthTracker(context) { + + private val _authFlags = + MutableStateFlow( + StrongAuthenticationFlags(currentUserId, getStrongAuthForUser(currentUserId)) + ) + + 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") } } @@ -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 +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/BiometricSettingsRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/BiometricSettingsRepositoryTest.kt index 5dc04f7efa63..fb7d379c0627 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/BiometricSettingsRepositoryTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/BiometricSettingsRepositoryTest.kt @@ -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 @@ -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 @@ -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: @@ -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 @@ -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 { @@ -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) } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeBiometricSettingsRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeBiometricSettingsRepository.kt index d4b1701892c7..d8b3270d3aff 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeBiometricSettingsRepository.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeBiometricSettingsRepository.kt @@ -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 } |