Provide isUserInLockdown through BiometricSettingsRepository

Since LockPatternUtils.registerStrongAuthTracker is a binder based listener,
this CL changes BiometricSettingsRepository#isStrongBiometricAllowed to rely on the same listener we register for isUserInLockdown

Bug: 262838215
Test: atest BiometricSettingsRepositoryTest
(cherry picked from https://googleplex-android-review.googlesource.com/q/commit:231b255ae166743b1834bf3c105f0b88e39cdcb3)
Merged-In: If9cb20ce12d2aa6ace5a1a5ccce2b887bdfa6e44
Change-Id: If9cb20ce12d2aa6ace5a1a5ccce2b887bdfa6e44
This commit is contained in:
Chandru S
2023-03-20 18:54:00 -07:00
committed by Cherrypicker Worker
parent 8246587377
commit 58d59134a0
3 changed files with 113 additions and 50 deletions

View File

@@ -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
}

View File

@@ -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)
}

View File

@@ -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
}