summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/data/repository/BiometricSettingsRepository.kt56
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/shared/model/AuthenticationFlags.kt70
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/BiometricSettingsRepositoryTest.kt61
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeBiometricSettingsRepository.kt31
4 files changed, 184 insertions, 34 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 0055f9a36529..0b6c7c415599 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
@@ -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
-}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/AuthenticationFlags.kt b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/AuthenticationFlags.kt
new file mode 100644
index 000000000000..0bbf67fb49e8
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/AuthenticationFlags.kt
@@ -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
+}
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 1bab8170ccde..b925aeb10262 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
@@ -310,19 +310,38 @@ class BiometricSettingsRepositoryTest : SysuiTestCase() {
createBiometricSettingsRepository()
verify(biometricManager)
.registerEnabledOnKeyguardCallback(biometricManagerCallback.capture())
-
whenever(devicePolicyManager.getKeyguardDisabledFeatures(isNull(), eq(PRIMARY_USER_ID)))
.thenReturn(0)
broadcastDPMStateChange()
+ val isFaceAuthEnabled = collectLastValue(underTest.isFaceAuthenticationEnabled)
+
+ assertThat(isFaceAuthEnabled()).isFalse()
+
+ // 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)
- val isFaceAuthEnabled = collectLastValue(underTest.isFaceAuthenticationEnabled)
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()
- biometricManagerCallback.value.onChanged(false, PRIMARY_USER_ID)
+ userRepository.setSelectedUserInfo(ANOTHER_USER)
+ runCurrent()
- assertThat(isFaceAuthEnabled()).isFalse()
+ 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)
}
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 65735f028c41..4aaf3478a31d 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
@@ -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) {