Make current user strong auth flags available in BiometricSettingsRepository

Other changes:
 - Face auth enabled check is now user specific.

Bug: 275600559
Test: atest BiometricSettingsRepositoryTest
Change-Id: Ia9ec3ce22e7b03ba476a7044a380bbeaf5bb2edc
This commit is contained in:
Chandru S
2023-05-08 07:07:02 -07:00
parent 366498ddd8
commit ed1479cc15
4 changed files with 185 additions and 35 deletions

View File

@@ -25,7 +25,6 @@ import android.hardware.biometrics.IBiometricEnabledOnKeyguardCallback
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
@@ -36,13 +35,14 @@ import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.dump.DumpManager
import com.android.systemui.keyguard.TAG
import com.android.systemui.keyguard.shared.model.AuthenticationFlags
import com.android.systemui.keyguard.shared.model.DevicePosture
import com.android.systemui.user.data.repository.UserRepository
import java.io.PrintWriter
import javax.inject.Inject
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
@@ -108,10 +108,14 @@ interface BiometricSettingsRepository {
* lockdown.
*/
val isCurrentUserInLockdown: Flow<Boolean>
/** Authentication flags set for the current user. */
val authenticationFlags: Flow<AuthenticationFlags>
}
const val TAG = "BiometricsRepositoryImpl"
private const val TAG = "BiometricsRepositoryImpl"
@OptIn(ExperimentalCoroutinesApi::class)
@SysUISingleton
class BiometricSettingsRepositoryImpl
@Inject
@@ -129,6 +133,8 @@ constructor(
dumpManager: DumpManager,
) : BiometricSettingsRepository, Dumpable {
private val biometricsEnabledForUser = mutableMapOf<Int, Boolean>()
override val isFaceAuthSupportedInCurrentPosture: Flow<Boolean>
private val strongAuthTracker = StrongAuthTracker(userRepository, context)
@@ -136,6 +142,9 @@ constructor(
override val isCurrentUserInLockdown: Flow<Boolean> =
strongAuthTracker.currentUserAuthFlags.map { it.isInUserLockdown }
override val authenticationFlags: Flow<AuthenticationFlags> =
strongAuthTracker.currentUserAuthFlags
init {
Log.d(TAG, "Registering StrongAuthTracker")
lockPatternUtils.registerStrongAuthTracker(strongAuthTracker)
@@ -231,9 +240,14 @@ constructor(
}
}
private val isFaceEnabledByBiometricsManagerForCurrentUser: Flow<Boolean> =
userRepository.selectedUserInfo.flatMapLatest { userInfo ->
isFaceEnabledByBiometricsManager.map { biometricsEnabledForUser[userInfo.id] ?: false }
}
override val isFaceAuthenticationEnabled: Flow<Boolean>
get() =
combine(isFaceEnabledByBiometricsManager, isFaceEnabledByDevicePolicy) {
combine(isFaceEnabledByBiometricsManagerForCurrentUser, isFaceEnabledByDevicePolicy) {
biometricsManagerSetting,
devicePolicySetting ->
biometricsManagerSetting && devicePolicySetting
@@ -249,13 +263,13 @@ constructor(
.flowOn(backgroundDispatcher)
.distinctUntilChanged()
private val isFaceEnabledByBiometricsManager =
private val isFaceEnabledByBiometricsManager: Flow<Pair<Int, Boolean>> =
conflatedCallbackFlow {
val callback =
object : IBiometricEnabledOnKeyguardCallback.Stub() {
override fun onChanged(enabled: Boolean, userId: Int) {
trySendWithFailureLogging(
enabled,
Pair(userId, enabled),
TAG,
"biometricsEnabled state changed"
)
@@ -264,9 +278,10 @@ constructor(
biometricManager?.registerEnabledOnKeyguardCallback(callback)
awaitClose {}
}
.onEach { biometricsEnabledForUser[it.first] = it.second }
// This is because the callback is binder-based and we want to avoid multiple callbacks
// being registered.
.stateIn(scope, SharingStarted.Eagerly, false)
.stateIn(scope, SharingStarted.Eagerly, Pair(0, false))
override val isStrongBiometricAllowed: StateFlow<Boolean> =
strongAuthTracker.isStrongBiometricAllowed.stateIn(
@@ -306,14 +321,13 @@ constructor(
)
}
@OptIn(ExperimentalCoroutinesApi::class)
private class StrongAuthTracker(private val userRepository: UserRepository, context: Context?) :
LockPatternUtils.StrongAuthTracker(context) {
// Backing field for onStrongAuthRequiredChanged
private val _strongAuthFlags =
MutableStateFlow(
StrongAuthenticationFlags(currentUserId, getStrongAuthForUser(currentUserId))
)
private val _authFlags =
MutableStateFlow(AuthenticationFlags(currentUserId, getStrongAuthForUser(currentUserId)))
// Backing field for onIsNonStrongBiometricAllowedChanged
private val _nonStrongBiometricAllowed =
@@ -321,17 +335,15 @@ private class StrongAuthTracker(private val userRepository: UserRepository, cont
Pair(currentUserId, isNonStrongBiometricAllowedAfterIdleTimeout(currentUserId))
)
val currentUserAuthFlags: Flow<StrongAuthenticationFlags> =
val currentUserAuthFlags: Flow<AuthenticationFlags> =
userRepository.selectedUserInfo
.map { it.id }
.distinctUntilChanged()
.flatMapLatest { userId ->
_strongAuthFlags
.filter { it.userId == userId }
_authFlags
.map { AuthenticationFlags(userId, getStrongAuthForUser(userId)) }
.onEach { Log.d(TAG, "currentUser authFlags changed, new value: $it") }
.onStart {
emit(StrongAuthenticationFlags(userId, getStrongAuthForUser(userId)))
}
.onStart { emit(AuthenticationFlags(userId, getStrongAuthForUser(userId))) }
}
/** isStrongBiometricAllowed for the current user. */
@@ -356,7 +368,7 @@ private class StrongAuthTracker(private val userRepository: UserRepository, cont
override fun onStrongAuthRequiredChanged(userId: Int) {
val newFlags = getStrongAuthForUser(userId)
_strongAuthFlags.value = StrongAuthenticationFlags(userId, newFlags)
_authFlags.value = AuthenticationFlags(userId, newFlags)
Log.d(TAG, "onStrongAuthRequiredChanged for userId: $userId, flag value: $newFlags")
}
@@ -375,11 +387,3 @@ 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
}

View File

@@ -0,0 +1,70 @@
/*
* Copyright (C) 2023 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.systemui.keyguard.shared.model
import com.android.internal.widget.LockPatternUtils
/** Authentication flags corresponding to a user. */
data class AuthenticationFlags(val userId: Int, val flag: Int) {
val isInUserLockdown =
containsFlag(
flag,
LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN
)
val isPrimaryAuthRequiredAfterReboot =
containsFlag(flag, LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_BOOT)
val isPrimaryAuthRequiredAfterTimeout =
containsFlag(flag, LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_TIMEOUT)
val isPrimaryAuthRequiredAfterDpmLockdown =
containsFlag(
flag,
LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_DPM_LOCK_NOW
)
val someAuthRequiredAfterUserRequest =
containsFlag(flag, LockPatternUtils.StrongAuthTracker.SOME_AUTH_REQUIRED_AFTER_USER_REQUEST)
val someAuthRequiredAfterTrustAgentExpired =
containsFlag(
flag,
LockPatternUtils.StrongAuthTracker.SOME_AUTH_REQUIRED_AFTER_TRUSTAGENT_EXPIRED
)
val primaryAuthRequiredAfterLockout =
containsFlag(flag, LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_LOCKOUT)
val primaryAuthRequiredForUnattendedUpdate =
containsFlag(
flag,
LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_FOR_UNATTENDED_UPDATE
)
/** Either Class 3 biometrics or primary auth can be used to unlock the device. */
val strongerAuthRequiredAfterNonStrongBiometricsTimeout =
containsFlag(
flag,
LockPatternUtils.StrongAuthTracker
.STRONG_AUTH_REQUIRED_AFTER_NON_STRONG_BIOMETRICS_TIMEOUT
)
}
private fun containsFlag(haystack: Int, needle: Int): Boolean {
return haystack and needle != 0
}

View File

@@ -310,19 +310,38 @@ class BiometricSettingsRepositoryTest : SysuiTestCase() {
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()
assertThat(isFaceAuthEnabled()).isFalse()
biometricManagerCallback.value.onChanged(false, PRIMARY_USER_ID)
// Value changes for another user
biometricManagerCallback.value.onChanged(true, ANOTHER_USER_ID)
assertThat(isFaceAuthEnabled()).isFalse()
// Value changes for current user.
biometricManagerCallback.value.onChanged(true, PRIMARY_USER_ID)
assertThat(isFaceAuthEnabled()).isTrue()
}
@Test
fun userChange_biometricEnabledChange_handlesRaceCondition() =
testScope.runTest {
createBiometricSettingsRepository()
verify(biometricManager)
.registerEnabledOnKeyguardCallback(biometricManagerCallback.capture())
val isFaceAuthEnabled = collectLastValue(underTest.isFaceAuthenticationEnabled)
biometricManagerCallback.value.onChanged(true, ANOTHER_USER_ID)
runCurrent()
userRepository.setSelectedUserInfo(ANOTHER_USER)
runCurrent()
assertThat(isFaceAuthEnabled()).isTrue()
}
@Test
@@ -382,7 +401,7 @@ class BiometricSettingsRepositoryTest : SysuiTestCase() {
}
@Test
fun userInLockdownUsesStrongAuthFlagsToDetermineValue() =
fun userInLockdownUsesAuthFlagsToDetermineValue() =
testScope.runTest {
createBiometricSettingsRepository()
@@ -405,6 +424,38 @@ class BiometricSettingsRepositoryTest : SysuiTestCase() {
assertThat(isUserInLockdown()).isTrue()
}
@Test
fun authFlagChangesForCurrentUserArePropagated() =
testScope.runTest {
createBiometricSettingsRepository()
val authFlags = collectLastValue(underTest.authenticationFlags)
// has default value.
val defaultStrongAuthValue = STRONG_AUTH_REQUIRED_AFTER_BOOT
assertThat(authFlags()!!.flag).isEqualTo(defaultStrongAuthValue)
// 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(authFlags()!!.flag).isEqualTo(defaultStrongAuthValue)
// change strong auth flags for current user.
onStrongAuthChanged(inLockdown, PRIMARY_USER_ID)
assertThat(authFlags()!!.flag).isEqualTo(inLockdown)
onStrongAuthChanged(STRONG_AUTH_REQUIRED_AFTER_TIMEOUT, ANOTHER_USER_ID)
assertThat(authFlags()!!.flag).isEqualTo(inLockdown)
userRepository.setSelectedUserInfo(ANOTHER_USER)
assertThat(authFlags()!!.flag).isEqualTo(STRONG_AUTH_REQUIRED_AFTER_TIMEOUT)
}
private fun enrollmentChange(biometricType: BiometricType, userId: Int, enabled: Boolean) {
authControllerCallback.value.onEnrollmentsChanged(biometricType, userId, enabled)
}

View File

@@ -17,10 +17,13 @@
package com.android.systemui.keyguard.data.repository
import com.android.internal.widget.LockPatternUtils
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 {
@@ -50,9 +53,12 @@ class FakeBiometricSettingsRepository : BiometricSettingsRepository {
override val isFaceAuthSupportedInCurrentPosture: Flow<Boolean>
get() = _isFaceAuthSupportedInCurrentPosture
private val _isCurrentUserInLockdown = MutableStateFlow(false)
override val isCurrentUserInLockdown: Flow<Boolean>
get() = _isCurrentUserInLockdown
get() = _authFlags.map { it.isInUserLockdown }
private val _authFlags = MutableStateFlow(AuthenticationFlags(0, 0))
override val authenticationFlags: Flow<AuthenticationFlags>
get() = _authFlags
fun setFingerprintEnrolled(isFingerprintEnrolled: Boolean) {
_isFingerprintEnrolled.value = isFingerprintEnrolled
@@ -66,6 +72,10 @@ class FakeBiometricSettingsRepository : BiometricSettingsRepository {
_isFingerprintEnabledByDevicePolicy.value = isFingerprintEnabledByDevicePolicy
}
fun setAuthenticationFlags(value: AuthenticationFlags) {
_authFlags.value = value
}
fun setFaceEnrolled(isFaceEnrolled: Boolean) {
_isFaceEnrolled.value = isFaceEnrolled
}
@@ -79,7 +89,22 @@ class FakeBiometricSettingsRepository : BiometricSettingsRepository {
}
fun setIsUserInLockdown(value: Boolean) {
_isCurrentUserInLockdown.value = value
if (value) {
setAuthenticationFlags(
AuthenticationFlags(
_authFlags.value.userId,
_authFlags.value.flag or
LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN
)
)
} else {
setAuthenticationFlags(
AuthenticationFlags(
_authFlags.value.userId,
LockPatternUtils.StrongAuthTracker.STRONG_AUTH_NOT_REQUIRED
)
)
}
}
fun setIsNonStrongBiometricAllowed(value: Boolean) {