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:
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
Reference in New Issue
Block a user