Merge "Spit up FingerprintManagerInteractor 2/N" into main

This commit is contained in:
Joshua Mccloskey
2024-09-26 18:29:36 +00:00
committed by Android (Google) Code Review
12 changed files with 199 additions and 70 deletions

View File

@@ -58,6 +58,7 @@ import com.android.settings.biometrics.fingerprint2.domain.interactor.SensorInte
import com.android.settings.biometrics.fingerprint2.domain.interactor.TouchEventInteractor
import com.android.settings.biometrics.fingerprint2.domain.interactor.UdfpsEnrollInteractor
import com.android.settings.biometrics.fingerprint2.domain.interactor.UdfpsEnrollInteractorImpl
import com.android.settings.biometrics.fingerprint2.domain.interactor.UserInteractorImpl
import com.android.settings.biometrics.fingerprint2.domain.interactor.VibrationInteractor
import com.android.settings.biometrics.fingerprint2.domain.interactor.VibrationInteractorImpl
import com.android.settings.biometrics.fingerprint2.lib.domain.interactor.AuthenitcateInteractor
@@ -67,6 +68,7 @@ import com.android.settings.biometrics.fingerprint2.lib.domain.interactor.Genera
import com.android.settings.biometrics.fingerprint2.lib.domain.interactor.RemoveFingerprintInteractor
import com.android.settings.biometrics.fingerprint2.lib.domain.interactor.RenameFingerprintInteractor
import com.android.settings.biometrics.fingerprint2.lib.domain.interactor.SensorInteractor
import com.android.settings.biometrics.fingerprint2.lib.domain.interactor.UserInteractor
import com.android.settings.biometrics.fingerprint2.lib.model.Settings
import java.util.concurrent.Executors
import kotlinx.coroutines.MainScope
@@ -97,11 +99,11 @@ class BiometricsEnvironment(
com.android.internal.R.integer.config_fingerprintMaxTemplatesPerUser
)
)
private val fingerprintEnrollmentRepository =
FingerprintEnrollmentRepositoryImpl(fingerprintManager, userRepo, fingerprintSettingsRepository,
backgroundDispatcher, applicationScope)
private val fingerprintSensorRepository: FingerprintSensorRepository =
FingerprintSensorRepositoryImpl(fingerprintManager, backgroundDispatcher, applicationScope)
private val fingerprintEnrollmentRepository =
FingerprintEnrollmentRepositoryImpl(fingerprintManager, userRepo, fingerprintSettingsRepository,
backgroundDispatcher, applicationScope, fingerprintSensorRepository)
private val debuggingRepository: DebuggingRepository = DebuggingRepositoryImpl()
private val udfpsDebugRepo = UdfpsEnrollDebugRepositoryImpl()
@@ -118,11 +120,13 @@ class BiometricsEnvironment(
EnrollFingerprintInteractorImpl(context.userId, fingerprintManager, Settings)
fun createFingerprintsEnrolledInteractor(): EnrolledFingerprintsInteractorImpl =
EnrolledFingerprintsInteractorImpl(fingerprintManager, context.userId)
EnrolledFingerprintsInteractorImpl(fingerprintEnrollmentRepository)
fun createAuthenticateInteractor(): AuthenitcateInteractor =
AuthenticateInteractorImpl(fingerprintManager, context.userId)
fun createUserInteractor(): UserInteractor = UserInteractorImpl(userRepo)
fun createRemoveFingerprintInteractor(): RemoveFingerprintInteractor =
RemoveFingerprintsInteractorImpl(fingerprintManager, context.userId)

View File

@@ -23,14 +23,16 @@ 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.asStateFlow
import kotlinx.coroutines.flow.callbackFlow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.filterNotNull
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.withContext
/** Repository that contains information about fingerprint enrollments. */
@@ -38,20 +40,31 @@ interface FingerprintEnrollmentRepository {
/** The current enrollments of the user */
val currentEnrollments: Flow<List<FingerprintData>?>
/** Indicates the maximum fingerprints that are enrollable * */
val maxFingerprintsEnrollable: Flow<Int>
/** Indicates if a user can enroll another fingerprint */
val canEnrollUser: Flow<Boolean>
fun maxFingerprintsEnrollable(): Int
/**
* Indicates if we should use the default settings for maximum enrollments or the sensor props
* from the fingerprint sensor
*/
fun setShouldUseSettingsMaxFingerprints(useSettings: Boolean)
}
class FingerprintEnrollmentRepositoryImpl(
fingerprintManager: FingerprintManager,
private val fingerprintManager: FingerprintManager,
userRepo: UserRepo,
private val settingsRepository: FingerprintSettingsRepository,
settingsRepository: FingerprintSettingsRepository,
backgroundDispatcher: CoroutineDispatcher,
applicationScope: CoroutineScope,
sensorRepo: FingerprintSensorRepository,
) : FingerprintEnrollmentRepository {
private val _shouldUseSettingsMaxFingerprints = MutableStateFlow(false)
val shouldUseSettingsMaxFingerprints = _shouldUseSettingsMaxFingerprints.asStateFlow()
private val enrollmentChangedFlow: Flow<Int?> =
callbackFlow {
val callback =
@@ -72,27 +85,34 @@ class FingerprintEnrollmentRepositoryImpl(
override val currentEnrollments: Flow<List<FingerprintData>> =
userRepo.currentUser
.distinctUntilChanged()
.flatMapLatest { currentUser ->
enrollmentChangedFlow.map { enrollmentChanged ->
if (enrollmentChanged == null || enrollmentChanged == currentUser) {
fingerprintManager
.getEnrolledFingerprints(currentUser)
?.map { (FingerprintData(it.name.toString(), it.biometricId, it.deviceId)) }
?.toList()
} else {
null
}
}
}
.combine(enrollmentChangedFlow) { currentUser, _ -> getFingerprintsForUser(currentUser) }
.filterNotNull()
.flowOn(backgroundDispatcher)
override val canEnrollUser: Flow<Boolean> =
currentEnrollments.map {
it?.size?.let { it < settingsRepository.maxEnrollableFingerprints() } ?: false
override val maxFingerprintsEnrollable: Flow<Int> =
shouldUseSettingsMaxFingerprints.combine(sensorRepo.fingerprintSensor) {
shouldUseSettings,
sensor ->
if (shouldUseSettings) {
settingsRepository.maxEnrollableFingerprints()
} else {
sensor.maxEnrollmentsPerUser
}
}
override fun maxFingerprintsEnrollable(): Int {
return settingsRepository.maxEnrollableFingerprints()
override val canEnrollUser: Flow<Boolean> =
currentEnrollments.combine(maxFingerprintsEnrollable) { enrollments, maxFingerprints ->
enrollments.size < maxFingerprints
}
override fun setShouldUseSettingsMaxFingerprints(useSettings: Boolean) {
_shouldUseSettingsMaxFingerprints.update { useSettings }
}
private fun getFingerprintsForUser(userId: Int): List<FingerprintData>? {
return fingerprintManager
.getEnrolledFingerprints(userId)
?.map { (FingerprintData(it.name.toString(), it.biometricId, it.deviceId)) }
?.toList()
}
}

View File

@@ -17,7 +17,10 @@
package com.android.settings.biometrics.fingerprint2.data.repository
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.update
/**
* A repository responsible for indicating the current user.
@@ -27,8 +30,18 @@ interface UserRepo {
* This flow indicates the current user.
*/
val currentUser: Flow<Int>
/**
* Updates the current user.
*/
fun updateUser(user: Int)
}
class UserRepoImpl(val currUser: Int): UserRepo {
override val currentUser: Flow<Int> = flowOf(currUser)
class UserRepoImpl(currUser: Int): UserRepo {
private val _currentUser = MutableStateFlow(currUser)
override val currentUser = _currentUser.asStateFlow()
override fun updateUser(user: Int) {
_currentUser.update { user }
}
}

View File

@@ -21,11 +21,14 @@ import com.android.settings.biometrics.fingerprint2.lib.domain.interactor.CanEnr
import kotlinx.coroutines.flow.Flow
class CanEnrollFingerprintsInteractorImpl(
val fingerprintEnrollmentRepository: FingerprintEnrollmentRepository
private val fingerprintEnrollmentRepository: FingerprintEnrollmentRepository
) : CanEnrollFingerprintsInteractor {
override val canEnrollFingerprints: Flow<Boolean> = fingerprintEnrollmentRepository.canEnrollUser
/** Indicates the maximum fingerprints enrollable for a given user */
override fun maxFingerprintsEnrollable(): Int {
return fingerprintEnrollmentRepository.maxFingerprintsEnrollable()
override val maxFingerprintsEnrollable: Flow<Int> =
fingerprintEnrollmentRepository.maxFingerprintsEnrollable
override fun setShouldUseSettingsMaxFingerprints(useSettings: Boolean) {
fingerprintEnrollmentRepository.setShouldUseSettingsMaxFingerprints(useSettings)
}
}

View File

@@ -16,22 +16,14 @@
package com.android.settings.biometrics.fingerprint2.domain.interactor
import android.hardware.fingerprint.FingerprintManager
import com.android.settings.biometrics.fingerprint2.data.repository.FingerprintEnrollmentRepository
import com.android.settings.biometrics.fingerprint2.lib.domain.interactor.EnrolledFingerprintsInteractor
import com.android.settings.biometrics.fingerprint2.lib.model.FingerprintData
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.flow
class EnrolledFingerprintsInteractorImpl(
private val fingerprintManager: FingerprintManager,
userId: Int,
private val fingerprintEnrollmentRepository: FingerprintEnrollmentRepository
) : EnrolledFingerprintsInteractor {
override val enrolledFingerprints: Flow<List<FingerprintData>?> = flow {
emit(
fingerprintManager
.getEnrolledFingerprints(userId)
?.map { (FingerprintData(it.name.toString(), it.biometricId, it.deviceId)) }
?.toList()
)
}
override val enrolledFingerprints: Flow<List<FingerprintData>?> =
fingerprintEnrollmentRepository.currentEnrollments
}

View File

@@ -0,0 +1,27 @@
/*
* Copyright (C) 2024 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.settings.biometrics.fingerprint2.domain.interactor
import com.android.settings.biometrics.fingerprint2.data.repository.UserRepo
import com.android.settings.biometrics.fingerprint2.lib.domain.interactor.UserInteractor
import kotlinx.coroutines.flow.Flow
class UserInteractorImpl(private val userRepo: UserRepo) : UserInteractor {
override val currentUser: Flow<Int> = userRepo.currentUser
override fun updateUser(user: Int) = userRepo.updateUser(user)
}

View File

@@ -23,5 +23,17 @@ interface CanEnrollFingerprintsInteractor {
/** Returns true if a user can enroll a fingerprint false otherwise. */
val canEnrollFingerprints: Flow<Boolean>
/** Indicates the maximum fingerprints enrollable for a given user */
fun maxFingerprintsEnrollable(): Int
val maxFingerprintsEnrollable: Flow<Int>
/**
* Indicates if we should use the default settings for maximum enrollments or the sensor props
* from the fingerprint sensor. This can be useful if you are supporting HIDL & AIDL enrollment
* types from one code base. Prior to AIDL there was no way to determine how many
* fingerprints were enrollable, Settings relied on
* com.android.internal.R.integer.config_fingerprintMaxTemplatesPerUser.
*
* Typically Fingerprints with AIDL HAL's should not use this
* (setShouldUseSettingsMaxFingerprints(false))
*/
fun setShouldUseSettingsMaxFingerprints(useSettings: Boolean)
}

View File

@@ -0,0 +1,31 @@
/*
* Copyright (C) 2024 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.settings.biometrics.fingerprint2.lib.domain.interactor
import kotlinx.coroutines.flow.Flow
interface UserInteractor {
/**
* This flow indicates the current user.
*/
val currentUser: Flow<Int>
/**
* Updates the current user.
*/
fun updateUser(user: Int)
}

View File

@@ -43,7 +43,6 @@ import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.sample
import kotlinx.coroutines.flow.transform
import kotlinx.coroutines.flow.transformLatest
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch
@@ -72,10 +71,12 @@ class FingerprintSettingsViewModel(
/** Represents the stream of the information of "Add Fingerprint" preference. */
val addFingerprintPrefInfo: Flow<Pair<Boolean, Int>> =
_enrolledFingerprints.filterOnlyWhenSettingsIsShown().combine(
canEnrollFingerprintsInteractor.canEnrollFingerprints
) { _, canEnrollFingerprints ->
Pair(canEnrollFingerprints, canEnrollFingerprintsInteractor.maxFingerprintsEnrollable())
combine(
_enrolledFingerprints.filterOnlyWhenSettingsIsShown(),
canEnrollFingerprintsInteractor.canEnrollFingerprints,
canEnrollFingerprintsInteractor.maxFingerprintsEnrollable,
) { _, canEnrollFingerprints, maxFingerprints ->
Pair(canEnrollFingerprints, maxFingerprints)
}
/** Represents the stream of visibility of sfps preference. */

View File

@@ -38,8 +38,12 @@ import com.android.systemui.biometrics.shared.model.FingerprintSensor
import com.android.systemui.biometrics.shared.model.FingerprintSensorType
import com.android.systemui.biometrics.shared.model.toFingerprintSensor
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.transform
import kotlinx.coroutines.flow.update
/** Fake to be used by other classes to easily fake the FingerprintManager implementation. */
class FakeFingerprintManagerInteractor :
@@ -52,7 +56,7 @@ class FakeFingerprintManagerInteractor :
RenameFingerprintInteractor,
SensorInteractor {
var enrollableFingerprints: Int = 5
private val enrollableFingerprints = MutableStateFlow(5)
var enrolledFingerprintsInternal: MutableList<FingerprintData> = mutableListOf()
var challengeToGenerate: Pair<Long, ByteArray> = Pair(-1L, byteArrayOf())
var authenticateAttempt = FingerprintAuthAttemptModel.Success(1)
@@ -82,13 +86,13 @@ class FakeFingerprintManagerInteractor :
override val enrolledFingerprints: Flow<List<FingerprintData>> = flow {
emit(enrolledFingerprintsInternal)
}
override val canEnrollFingerprints: Flow<Boolean> = flow {
emit(enrolledFingerprintsInternal.size < enrollableFingerprints)
override val canEnrollFingerprints: Flow<Boolean> = enrollableFingerprints.transform {
emit(enrolledFingerprintsInternal.size < it)
}
override fun maxFingerprintsEnrollable(): Int {
return enrollableFingerprints
}
override val maxFingerprintsEnrollable: Flow<Int> = enrollableFingerprints.asStateFlow()
override fun setShouldUseSettingsMaxFingerprints(useSettings: Boolean) {}
override val sensorPropertiesInternal: Flow<FingerprintSensor?> = flow { emit(sensorProp) }
override val hasSideFps: Flow<Boolean> =
@@ -110,4 +114,7 @@ class FakeFingerprintManagerInteractor :
}
}
fun setMaxEnrollableFingerprints(fingerprints: Int) {
enrollableFingerprints.update { fingerprints }
}
}

View File

@@ -30,6 +30,7 @@ import android.hardware.fingerprint.FingerprintSensorPropertiesInternal
import android.os.CancellationSignal
import android.os.Handler
import com.android.settings.biometrics.GatekeeperPasswordProvider
import com.android.settings.biometrics.fingerprint2.data.repository.FingerprintEnrollmentRepository
import com.android.settings.biometrics.fingerprint2.data.repository.FingerprintEnrollmentRepositoryImpl
import com.android.settings.biometrics.fingerprint2.data.repository.FingerprintSensorRepository
import com.android.settings.biometrics.fingerprint2.data.repository.FingerprintSettingsRepositoryImpl
@@ -61,7 +62,7 @@ import kotlinx.coroutines.cancelAndJoin
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.last
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch
import kotlinx.coroutines.test.StandardTestDispatcher
import kotlinx.coroutines.test.TestScope
@@ -106,9 +107,14 @@ class FingerprintManagerInteractorTest {
private val flow: FingerprintFlow = Default
private val maxFingerprints = 5
private val currUser = MutableStateFlow(0)
private lateinit var fingerprintEnrollRepo: FingerprintEnrollmentRepository
private val userRepo =
object : UserRepo {
override val currentUser: Flow<Int> = currUser
override fun updateUser(user: Int) {
currUser.update { user }
}
}
@Before
@@ -133,17 +139,18 @@ class FingerprintManagerInteractorTest {
}
val settingsRepository = FingerprintSettingsRepositoryImpl(maxFingerprints)
val fingerprintEnrollmentRepository =
fingerprintEnrollRepo =
FingerprintEnrollmentRepositoryImpl(
fingerprintManager,
userRepo,
settingsRepository,
backgroundDispatcher,
backgroundScope,
fingerprintSensorRepository,
)
enrolledFingerprintsInteractorUnderTest =
EnrolledFingerprintsInteractorImpl(fingerprintManager, userId)
EnrolledFingerprintsInteractorImpl(fingerprintEnrollRepo)
generateChallengeInteractorUnderTest =
GenerateChallengeInteractorImpl(fingerprintManager, userId, gateKeeperPasswordProvider)
removeFingerprintsInteractorUnderTest =
@@ -153,7 +160,7 @@ class FingerprintManagerInteractorTest {
authenticateInteractorImplUnderTest = AuthenticateInteractorImpl(fingerprintManager, userId)
canEnrollFingerprintsInteractorUnderTest =
CanEnrollFingerprintsInteractorImpl(fingerprintEnrollmentRepository)
CanEnrollFingerprintsInteractorImpl(fingerprintEnrollRepo)
enrollInteractorUnderTest = EnrollFingerprintInteractorImpl(userId, fingerprintManager, flow)
}
@@ -163,9 +170,16 @@ class FingerprintManagerInteractorTest {
testScope.runTest {
whenever(fingerprintManager.getEnrolledFingerprints(anyInt())).thenReturn(emptyList())
val emptyFingerprintList: List<Fingerprint> = emptyList()
assertThat(enrolledFingerprintsInteractorUnderTest.enrolledFingerprints.last())
.isEqualTo(emptyFingerprintList)
var list: List<FingerprintData>? = null
val job =
testScope.launch {
enrolledFingerprintsInteractorUnderTest.enrolledFingerprints.collect { list = it }
}
runCurrent()
job.cancelAndJoin()
assertThat(list!!.isEmpty())
}
@Test
@@ -174,10 +188,19 @@ class FingerprintManagerInteractorTest {
val expected = Fingerprint("Finger 1,", 2, 3L)
val fingerprintList: List<Fingerprint> = listOf(expected)
whenever(fingerprintManager.getEnrolledFingerprints(anyInt())).thenReturn(fingerprintList)
// This causes the enrolled fingerprints to be updated
var list: List<FingerprintData>? = null
val job =
testScope.launch {
enrolledFingerprintsInteractorUnderTest.enrolledFingerprints.collect { list = it }
}
runCurrent()
job.cancelAndJoin()
val list = enrolledFingerprintsInteractorUnderTest.enrolledFingerprints.last()
assertThat(list!!.size).isEqualTo(fingerprintList.size)
val actual = list[0]
val actual = list!![0]
assertThat(actual.name).isEqualTo(expected.name)
assertThat(actual.fingerId).isEqualTo(expected.biometricId)
assertThat(actual.deviceId).isEqualTo(expected.deviceId)
@@ -220,11 +243,7 @@ class FingerprintManagerInteractorTest {
whenever(fingerprintManager.getEnrolledFingerprints(anyInt())).thenReturn(fingerprintList)
var result: Boolean? = null
val job =
testScope.launch {
canEnrollFingerprintsInteractorUnderTest.canEnrollFingerprints.collect { result = it }
}
val job = testScope.launch { fingerprintEnrollRepo.canEnrollUser.collect { result = it } }
runCurrent()
job.cancelAndJoin()

View File

@@ -112,7 +112,7 @@ class FingerprintEnrollConfirmationViewModelTest {
.toFingerprintSensor()
fakeFingerprintManagerInteractor.enrolledFingerprintsInternal = mutableListOf()
fakeFingerprintManagerInteractor.enrollableFingerprints = 5
fakeFingerprintManagerInteractor.setMaxEnrollableFingerprints(5)
var canEnrollFingerprints: Boolean = false
val job = launch {