Split up FingerprintManagerInteractor

Test: atest, screenshot tests passed
Flag: com.android.settings.flags.fingerprint_v2_enrollment
Change-Id: I70833d5d9888f730233a9757589ce7faa45eccc9
This commit is contained in:
Joshua McCloskey
2024-08-07 23:52:11 +00:00
parent 59f11d9377
commit 882e1c3621
51 changed files with 1231 additions and 523 deletions

View File

@@ -18,7 +18,9 @@ package com.android.settings;
import android.app.Application;
import android.content.Context;
import android.content.pm.PackageManager;
import android.database.ContentObserver;
import android.hardware.fingerprint.FingerprintManager;
import android.net.Uri;
import android.provider.Settings;
import android.util.FeatureFlagUtils;
@@ -74,9 +76,6 @@ public class SettingsApplication extends Application {
// Set Spa environment.
setSpaEnvironment();
if (Flags.fingerprintV2Enrollment()) {
mBiometricsEnvironment = new BiometricsEnvironment(this);
}
if (ActivityEmbeddingUtils.isSettingsSplitEnabled(this)
&& FeatureFlagUtils.isEnabled(this,
@@ -120,7 +119,20 @@ public class SettingsApplication extends Application {
@Nullable
public BiometricsEnvironment getBiometricEnvironment() {
return mBiometricsEnvironment;
if (Flags.fingerprintV2Enrollment()) {
if (getPackageManager().hasSystemFeature(PackageManager.FEATURE_FINGERPRINT)) {
final FingerprintManager fpm = getSystemService(FingerprintManager.class);
if (mBiometricsEnvironment == null) {
mBiometricsEnvironment = new BiometricsEnvironment(this, fpm);
}
return mBiometricsEnvironment;
} else {
return null;
}
}
return null;
}
@Override

View File

@@ -16,12 +16,9 @@
package com.android.settings.biometrics.fingerprint2
import android.content.pm.PackageManager
import android.hardware.fingerprint.FingerprintManager
import android.os.ServiceManager.ServiceNotFoundException
import android.view.MotionEvent
import android.view.accessibility.AccessibilityManager
import androidx.fragment.app.FragmentActivity
import androidx.lifecycle.ViewModelStore
import androidx.lifecycle.ViewModelStoreOwner
import com.android.internal.widget.LockPatternUtils
@@ -29,33 +26,47 @@ import com.android.settings.SettingsApplication
import com.android.settings.biometrics.GatekeeperPasswordProvider
import com.android.settings.biometrics.fingerprint2.data.repository.DebuggingRepository
import com.android.settings.biometrics.fingerprint2.data.repository.DebuggingRepositoryImpl
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.FingerprintSensorRepositoryImpl
import com.android.settings.biometrics.fingerprint2.data.repository.FingerprintSettingsRepositoryImpl
import com.android.settings.biometrics.fingerprint2.data.repository.UserRepoImpl
import com.android.settings.biometrics.fingerprint2.debug.data.repository.UdfpsEnrollDebugRepositoryImpl
import com.android.settings.biometrics.fingerprint2.debug.domain.interactor.DebugTouchEventInteractorImpl
import com.android.settings.biometrics.fingerprint2.domain.interactor.AccessibilityInteractor
import com.android.settings.biometrics.fingerprint2.domain.interactor.AccessibilityInteractorImpl
import com.android.settings.biometrics.fingerprint2.domain.interactor.AuthenticateInteractorImpl
import com.android.settings.biometrics.fingerprint2.domain.interactor.CanEnrollFingerprintsInteractorImpl
import com.android.settings.biometrics.fingerprint2.domain.interactor.DebuggingInteractor
import com.android.settings.biometrics.fingerprint2.domain.interactor.DebuggingInteractorImpl
import com.android.settings.biometrics.fingerprint2.domain.interactor.DisplayDensityInteractor
import com.android.settings.biometrics.fingerprint2.domain.interactor.DisplayDensityInteractorImpl
import com.android.settings.biometrics.fingerprint2.domain.interactor.EnrollFingerprintInteractorImpl
import com.android.settings.biometrics.fingerprint2.domain.interactor.EnrollStageInteractor
import com.android.settings.biometrics.fingerprint2.domain.interactor.EnrollStageInteractorImpl
import com.android.settings.biometrics.fingerprint2.domain.interactor.FingerprintEnrollInteractor
import com.android.settings.biometrics.fingerprint2.domain.interactor.FingerprintEnrollInteractorImpl
import com.android.settings.biometrics.fingerprint2.domain.interactor.FingerprintManagerInteractorImpl
import com.android.settings.biometrics.fingerprint2.domain.interactor.EnrolledFingerprintsInteractorImpl
import com.android.settings.biometrics.fingerprint2.domain.interactor.FingerprintSensorInteractor
import com.android.settings.biometrics.fingerprint2.domain.interactor.FingerprintSensorInteractorImpl
import com.android.settings.biometrics.fingerprint2.domain.interactor.FoldStateInteractor
import com.android.settings.biometrics.fingerprint2.domain.interactor.FoldStateInteractorImpl
import com.android.settings.biometrics.fingerprint2.domain.interactor.GenerateChallengeInteractorImpl
import com.android.settings.biometrics.fingerprint2.domain.interactor.OrientationInteractor
import com.android.settings.biometrics.fingerprint2.domain.interactor.OrientationInteractorImpl
import com.android.settings.biometrics.fingerprint2.domain.interactor.RemoveFingerprintsInteractorImpl
import com.android.settings.biometrics.fingerprint2.domain.interactor.RenameFingerprintsInteractorImpl
import com.android.settings.biometrics.fingerprint2.domain.interactor.SensorInteractorImpl
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.VibrationInteractor
import com.android.settings.biometrics.fingerprint2.domain.interactor.VibrationInteractorImpl
import com.android.settings.biometrics.fingerprint2.lib.domain.interactor.FingerprintManagerInteractor
import com.android.settings.biometrics.fingerprint2.lib.domain.interactor.AuthenitcateInteractor
import com.android.settings.biometrics.fingerprint2.lib.domain.interactor.CanEnrollFingerprintsInteractor
import com.android.settings.biometrics.fingerprint2.lib.domain.interactor.EnrollFingerprintInteractor
import com.android.settings.biometrics.fingerprint2.lib.domain.interactor.GenerateChallengeInteractor
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.model.Settings
import java.util.concurrent.Executors
import kotlinx.coroutines.MainScope
@@ -70,43 +81,53 @@ import kotlinx.coroutines.flow.flowOf
* This code is instantiated within the [SettingsApplication], all repos should be private &
* immutable and all interactors should public and immutable
*/
class BiometricsEnvironment(context: SettingsApplication) : ViewModelStoreOwner {
class BiometricsEnvironment(
val context: SettingsApplication,
private val fingerprintManager: FingerprintManager,
) : ViewModelStoreOwner {
private val executorService = Executors.newSingleThreadExecutor()
private val backgroundDispatcher = executorService.asCoroutineDispatcher()
private val applicationScope = MainScope()
private val gateKeeperPasswordProvider = GatekeeperPasswordProvider(LockPatternUtils(context))
private val fingerprintManager = try {
if (context.packageManager.hasSystemFeature(PackageManager.FEATURE_FINGERPRINT)) {
context.getSystemService(FragmentActivity.FINGERPRINT_SERVICE) as FingerprintManager?
} else {
null
}
} catch (exception: ServiceNotFoundException){
null
}
private val userRepo = UserRepoImpl(context.userId)
private val fingerprintSettingsRepository =
FingerprintSettingsRepositoryImpl(
context.resources.getInteger(
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 debuggingRepository: DebuggingRepository = DebuggingRepositoryImpl()
private val udfpsDebugRepo = UdfpsEnrollDebugRepositoryImpl()
/** For now, interactors are public to those with access to the [BiometricsEnvironment] class */
val fingerprintEnrollInteractor: FingerprintEnrollInteractor by lazy {
FingerprintEnrollInteractorImpl(context, fingerprintManager, Settings)
}
fun createSensorPropertiesInteractor(): SensorInteractor =
SensorInteractorImpl(fingerprintSensorRepository)
/** [FingerprintManagerInteractor] to be used to construct view models */
val fingerprintManagerInteractor: FingerprintManagerInteractor by lazy {
FingerprintManagerInteractorImpl(
context,
backgroundDispatcher,
fingerprintManager,
fingerprintSensorRepository,
gateKeeperPasswordProvider,
fingerprintEnrollInteractor,
)
}
fun createCanEnrollFingerprintsInteractor(): CanEnrollFingerprintsInteractor =
CanEnrollFingerprintsInteractorImpl(fingerprintEnrollmentRepository)
fun createGenerateChallengeInteractor(): GenerateChallengeInteractor =
GenerateChallengeInteractorImpl(fingerprintManager, context.userId, gateKeeperPasswordProvider)
fun createFingerprintEnrollInteractor(): EnrollFingerprintInteractor =
EnrollFingerprintInteractorImpl(context.userId, fingerprintManager, Settings)
fun createFingerprintsEnrolledInteractor(): EnrolledFingerprintsInteractorImpl =
EnrolledFingerprintsInteractorImpl(fingerprintManager, context.userId)
fun createAuthenticateInteractor(): AuthenitcateInteractor =
AuthenticateInteractorImpl(fingerprintManager, context.userId)
fun createRemoveFingerprintInteractor(): RemoveFingerprintInteractor =
RemoveFingerprintsInteractorImpl(fingerprintManager, context.userId)
fun createRenameFingerprintInteractor(): RenameFingerprintInteractor =
RenameFingerprintsInteractorImpl(fingerprintManager, context.userId, backgroundDispatcher)
val accessibilityInteractor: AccessibilityInteractor by lazy {
AccessibilityInteractorImpl(

View File

@@ -0,0 +1,98 @@
/*
* 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.data.repository
import android.hardware.biometrics.BiometricStateListener
import android.hardware.fingerprint.FingerprintManager
import com.android.settings.biometrics.fingerprint2.lib.model.FingerprintData
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.callbackFlow
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.withContext
/** Repository that contains information about fingerprint enrollments. */
interface FingerprintEnrollmentRepository {
/** The current enrollments of the user */
val currentEnrollments: Flow<List<FingerprintData>?>
/** Indicates if a user can enroll another fingerprint */
val canEnrollUser: Flow<Boolean>
fun maxFingerprintsEnrollable(): Int
}
class FingerprintEnrollmentRepositoryImpl(
fingerprintManager: FingerprintManager,
userRepo: UserRepo,
private val settingsRepository: FingerprintSettingsRepository,
backgroundDispatcher: CoroutineDispatcher,
applicationScope: CoroutineScope,
) : FingerprintEnrollmentRepository {
private val enrollmentChangedFlow: Flow<Int?> =
callbackFlow {
val callback =
object : BiometricStateListener() {
override fun onEnrollmentsChanged(userId: Int, sensorId: Int, hasEnrollments: Boolean) {
trySend(userId)
}
}
withContext(backgroundDispatcher) {
fingerprintManager.registerBiometricStateListener(callback)
}
awaitClose {
// no way to unregister
}
}
.stateIn(applicationScope, started = SharingStarted.Eagerly, initialValue = null)
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
}
}
}
.filterNotNull()
.flowOn(backgroundDispatcher)
override val canEnrollUser: Flow<Boolean> =
currentEnrollments.map {
it?.size?.let { it < settingsRepository.maxEnrollableFingerprints() } ?: false
}
override fun maxFingerprintsEnrollable(): Int {
return settingsRepository.maxEnrollableFingerprints()
}
}

View File

@@ -31,6 +31,8 @@ import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.callbackFlow
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.flow.transform
import kotlinx.coroutines.withContext
@@ -43,10 +45,13 @@ import kotlinx.coroutines.withContext
interface FingerprintSensorRepository {
/** Get the [FingerprintSensor] */
val fingerprintSensor: Flow<FingerprintSensor>
/** Indicates if this device supports the side fingerprint sensor */
val hasSideFps: Flow<Boolean>
}
class FingerprintSensorRepositoryImpl(
fingerprintManager: FingerprintManager?,
private val fingerprintManager: FingerprintManager,
backgroundDispatcher: CoroutineDispatcher,
activityScope: CoroutineScope,
) : FingerprintSensorRepository {
@@ -66,7 +71,7 @@ class FingerprintSensorRepositoryImpl(
}
}
withContext(backgroundDispatcher) {
fingerprintManager?.addAuthenticatorsRegisteredCallback(callback)
fingerprintManager.addAuthenticatorsRegisteredCallback(callback)
}
awaitClose {}
}
@@ -75,6 +80,9 @@ class FingerprintSensorRepositoryImpl(
override val fingerprintSensor: Flow<FingerprintSensor> =
fingerprintPropsInternal.transform { emit(it.toFingerprintSensor()) }
override val hasSideFps: Flow<Boolean> =
fingerprintSensor.flatMapLatest { flow { emit(fingerprintManager.isPowerbuttonFps()) } }
companion object {
private val DEFAULT_PROPS =

View File

@@ -0,0 +1,32 @@
/*
* 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.data.repository
/**
* Repository for storing metadata about fingerprint enrollments.
*/
interface FingerprintSettingsRepository {
/**
* Indicates the maximum number of fingerprints enrollable
*/
fun maxEnrollableFingerprints(): Int
}
class FingerprintSettingsRepositoryImpl(private val maxFingerprintsEnrollable: Int) :
FingerprintSettingsRepository {
override fun maxEnrollableFingerprints() = maxFingerprintsEnrollable
}

View File

@@ -16,7 +16,6 @@
package com.android.settings.biometrics.fingerprint2.data.repository
import android.graphics.Point
import android.view.MotionEvent
import kotlinx.coroutines.flow.Flow

View File

@@ -0,0 +1,34 @@
/*
* 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.data.repository
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.flowOf
/**
* A repository responsible for indicating the current user.
*/
interface UserRepo {
/**
* This flow indicates the current user.
*/
val currentUser: Flow<Int>
}
class UserRepoImpl(val currUser: Int): UserRepo {
override val currentUser: Flow<Int> = flowOf(currUser)
}

View File

@@ -97,6 +97,8 @@ class UdfpsEnrollDebugRepositoryImpl :
}
override val fingerprintSensor: Flow<FingerprintSensor> = flowOf(sensorProps)
override val hasSideFps: Flow<Boolean>
get() = flowOf(false)
private fun pointToLeftOfSensor(sensorLocation: Rect): MotionEvent =
MotionEvent.obtain(

View File

@@ -26,4 +26,4 @@ class DebugTouchEventInteractorImpl(
) : TouchEventInteractor {
override val touchEvent: Flow<MotionEvent> =
udfpsSimulatedTouchEventsRepository.touchExplorationDebug
}
}

View File

@@ -0,0 +1,71 @@
/*
* 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 android.hardware.fingerprint.FingerprintManager
import android.os.CancellationSignal
import android.util.Log
import com.android.settings.biometrics.fingerprint2.lib.domain.interactor.AuthenitcateInteractor
import com.android.settings.biometrics.fingerprint2.lib.model.FingerprintAuthAttemptModel
import kotlin.coroutines.resume
import kotlinx.coroutines.CancellableContinuation
import kotlinx.coroutines.suspendCancellableCoroutine
class AuthenticateInteractorImpl(
private val fingerprintManager: FingerprintManager,
private val userId: Int,
) : AuthenitcateInteractor {
override suspend fun authenticate(): FingerprintAuthAttemptModel =
suspendCancellableCoroutine { c: CancellableContinuation<FingerprintAuthAttemptModel> ->
val authenticationCallback =
object : FingerprintManager.AuthenticationCallback() {
override fun onAuthenticationError(errorCode: Int, errString: CharSequence) {
super.onAuthenticationError(errorCode, errString)
if (c.isCompleted) {
Log.d(TAG, "framework sent down onAuthError after finish")
return
}
c.resume(FingerprintAuthAttemptModel.Error(errorCode, errString.toString()))
}
override fun onAuthenticationSucceeded(result: FingerprintManager.AuthenticationResult) {
super.onAuthenticationSucceeded(result)
if (c.isCompleted) {
Log.d(TAG, "framework sent down onAuthError after finish")
return
}
c.resume(FingerprintAuthAttemptModel.Success(result.fingerprint?.biometricId ?: -1))
}
}
val cancellationSignal = CancellationSignal()
c.invokeOnCancellation { cancellationSignal.cancel() }
fingerprintManager.authenticate(
null,
cancellationSignal,
authenticationCallback,
null,
userId,
)
}
companion object {
private const val TAG = "AuthenticateInteractor"
}
}

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.domain.interactor
import com.android.settings.biometrics.fingerprint2.data.repository.FingerprintEnrollmentRepository
import com.android.settings.biometrics.fingerprint2.lib.domain.interactor.CanEnrollFingerprintsInteractor
import kotlinx.coroutines.flow.Flow
class CanEnrollFingerprintsInteractorImpl(
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()
}
}

View File

@@ -0,0 +1,146 @@
/*
* 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 android.hardware.fingerprint.FingerprintEnrollOptions
import android.hardware.fingerprint.FingerprintManager
import android.os.CancellationSignal
import android.util.Log
import com.android.settings.biometrics.fingerprint2.conversion.Util.toEnrollError
import com.android.settings.biometrics.fingerprint2.conversion.Util.toOriginalReason
import com.android.settings.biometrics.fingerprint2.lib.domain.interactor.EnrollFingerprintInteractor
import com.android.settings.biometrics.fingerprint2.lib.model.EnrollReason
import com.android.settings.biometrics.fingerprint2.lib.model.FingerEnrollState
import com.android.settings.biometrics.fingerprint2.lib.model.FingerprintFlow
import com.android.settings.biometrics.fingerprint2.lib.model.SetupWizard
import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.channels.onFailure
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.callbackFlow
import kotlinx.coroutines.flow.update
class EnrollFingerprintInteractorImpl(
private val userId: Int,
private val fingerprintManager: FingerprintManager,
private val fingerprintFlow: FingerprintFlow,
) : EnrollFingerprintInteractor {
private val enrollRequestOutstanding = MutableStateFlow(false)
override suspend fun enroll(
hardwareAuthToken: ByteArray?,
enrollReason: EnrollReason,
fingerprintEnrollOptions: FingerprintEnrollOptions,
): Flow<FingerEnrollState> = callbackFlow {
// TODO (b/308456120) Improve this logic
if (enrollRequestOutstanding.value) {
Log.d(TAG, "Outstanding enroll request, waiting 150ms")
delay(150)
if (enrollRequestOutstanding.value) {
Log.e(TAG, "Request still present, continuing")
}
}
enrollRequestOutstanding.update { true }
var streamEnded = false
var totalSteps: Int? = null
val enrollmentCallback =
object : FingerprintManager.EnrollmentCallback() {
override fun onEnrollmentProgress(remaining: Int) {
// This is sort of an implementation detail, but unfortunately the API isn't
// very expressive. If anything we should look at changing the FingerprintManager API.
if (totalSteps == null) {
totalSteps = remaining + 1
}
trySend(FingerEnrollState.EnrollProgress(remaining, totalSteps!!)).onFailure { error ->
Log.d(TAG, "onEnrollmentProgress($remaining) failed to send, due to $error")
}
if (remaining == 0) {
streamEnded = true
enrollRequestOutstanding.update { false }
}
}
override fun onEnrollmentHelp(helpMsgId: Int, helpString: CharSequence?) {
trySend(FingerEnrollState.EnrollHelp(helpMsgId, helpString.toString())).onFailure { error
->
Log.d(TAG, "onEnrollmentHelp failed to send, due to $error")
}
}
override fun onEnrollmentError(errMsgId: Int, errString: CharSequence?) {
trySend(errMsgId.toEnrollError(fingerprintFlow == SetupWizard)).onFailure { error ->
Log.d(TAG, "onEnrollmentError failed to send, due to $error")
}
Log.d(TAG, "onEnrollmentError($errMsgId)")
streamEnded = true
enrollRequestOutstanding.update { false }
}
override fun onUdfpsPointerDown(sensorId: Int) {
trySend(FingerEnrollState.PointerDown(sensorId)).onFailure { error ->
Log.d(TAG, "onUdfpsPointerDown failed to send, due to $error")
}
}
override fun onUdfpsPointerUp(sensorId: Int) {
trySend(FingerEnrollState.PointerUp(sensorId)).onFailure { error ->
Log.d(TAG, "onUdfpsPointerUp failed to send, due to $error")
}
}
override fun onUdfpsOverlayShown() {
trySend(FingerEnrollState.OverlayShown).onFailure { error ->
Log.d(TAG, "OverlayShown failed to send, due to $error")
}
}
override fun onAcquired(isAcquiredGood: Boolean) {
trySend(FingerEnrollState.Acquired(isAcquiredGood)).onFailure { error ->
Log.d(TAG, "Acquired failed to send, due to $error")
}
}
}
val cancellationSignal = CancellationSignal()
fingerprintManager.enroll(
hardwareAuthToken,
cancellationSignal,
userId,
enrollmentCallback,
enrollReason.toOriginalReason(),
fingerprintEnrollOptions,
)
awaitClose {
// If the stream has not been ended, and the user has stopped collecting the flow
// before it was over, send cancel.
if (!streamEnded) {
Log.e(TAG, "Cancel is sent from settings for enroll()")
cancellationSignal.cancel()
}
}
}
companion object {
private const val TAG = "FingerprintEnrollStateRepository"
}
}

View File

@@ -0,0 +1,37 @@
/*
* 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 android.hardware.fingerprint.FingerprintManager
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,
) : EnrolledFingerprintsInteractor {
override val enrolledFingerprints: Flow<List<FingerprintData>?> = flow {
emit(
fingerprintManager
.getEnrolledFingerprints(userId)
?.map { (FingerprintData(it.name.toString(), it.biometricId, it.deviceId)) }
?.toList()
)
}
}

View File

@@ -16,7 +16,6 @@
package com.android.settings.biometrics.fingerprint2.domain.interactor
import android.content.Context
import android.hardware.fingerprint.FingerprintEnrollOptions
import android.hardware.fingerprint.FingerprintManager
import android.os.CancellationSignal
@@ -49,7 +48,7 @@ interface FingerprintEnrollInteractor {
}
class FingerprintEnrollInteractorImpl(
private val applicationContext: Context,
private val userId: Int,
private val fingerprintManager: FingerprintManager?,
private val fingerprintFlow: FingerprintFlow,
) : FingerprintEnrollInteractor {
@@ -138,7 +137,7 @@ class FingerprintEnrollInteractorImpl(
fingerprintManager?.enroll(
hardwareAuthToken,
cancellationSignal,
applicationContext.userId,
userId,
enrollmentCallback,
enrollReason.toOriginalReason(),
fingerprintEnrollOptions,

View File

@@ -1,173 +0,0 @@
/*
* 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.settings.biometrics.fingerprint2.domain.interactor
import android.content.Context
import android.content.Intent
import android.hardware.fingerprint.FingerprintEnrollOptions
import android.hardware.fingerprint.FingerprintManager
import android.hardware.fingerprint.FingerprintManager.GenerateChallengeCallback
import android.hardware.fingerprint.FingerprintManager.RemovalCallback
import android.os.CancellationSignal
import android.util.Log
import com.android.settings.biometrics.GatekeeperPasswordProvider
import com.android.settings.biometrics.fingerprint2.data.repository.FingerprintSensorRepository
import com.android.settings.biometrics.fingerprint2.lib.domain.interactor.FingerprintManagerInteractor
import com.android.settings.biometrics.fingerprint2.lib.model.EnrollReason
import com.android.settings.biometrics.fingerprint2.lib.model.FingerEnrollState
import com.android.settings.biometrics.fingerprint2.lib.model.FingerprintAuthAttemptModel
import com.android.settings.biometrics.fingerprint2.lib.model.FingerprintData
import com.android.settings.password.ChooseLockSettingsHelper
import kotlin.coroutines.resume
import kotlin.coroutines.suspendCoroutine
import kotlinx.coroutines.CancellableContinuation
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.suspendCancellableCoroutine
import kotlinx.coroutines.withContext
private const val TAG = "FingerprintManagerInteractor"
class FingerprintManagerInteractorImpl(
applicationContext: Context,
private val backgroundDispatcher: CoroutineDispatcher,
private val fingerprintManager: FingerprintManager?,
fingerprintSensorRepository: FingerprintSensorRepository,
private val gatekeeperPasswordProvider: GatekeeperPasswordProvider,
private val fingerprintEnrollStateRepository: FingerprintEnrollInteractor,
) : FingerprintManagerInteractor {
private val maxFingerprints =
applicationContext.resources.getInteger(
com.android.internal.R.integer.config_fingerprintMaxTemplatesPerUser
)
private val applicationContext = applicationContext.applicationContext
override suspend fun generateChallenge(gateKeeperPasswordHandle: Long): Pair<Long, ByteArray> =
suspendCoroutine {
val callback = GenerateChallengeCallback { _, userId, challenge ->
val intent = Intent()
intent.putExtra(ChooseLockSettingsHelper.EXTRA_KEY_GK_PW_HANDLE, gateKeeperPasswordHandle)
val challengeToken =
gatekeeperPasswordProvider.requestGatekeeperHat(intent, challenge, userId)
gatekeeperPasswordProvider.removeGatekeeperPasswordHandle(intent, false)
val p = Pair(challenge, challengeToken)
it.resume(p)
}
fingerprintManager?.generateChallenge(applicationContext.userId, callback)
}
override val enrolledFingerprints: Flow<List<FingerprintData>?> = flow {
emit(
fingerprintManager?.getEnrolledFingerprints(applicationContext.userId)
?.map { (FingerprintData(it.name.toString(), it.biometricId, it.deviceId)) }?.toList()
)
}
override val canEnrollFingerprints: Flow<Boolean> = flow {
emit(
fingerprintManager?.getEnrolledFingerprints(applicationContext.userId)?.size ?: maxFingerprints < maxFingerprints
)
}
override val sensorPropertiesInternal = fingerprintSensorRepository.fingerprintSensor
override val maxEnrollableFingerprints = flow { emit(maxFingerprints) }
override suspend fun enroll(
hardwareAuthToken: ByteArray?,
enrollReason: EnrollReason,
fingerprintEnrollOptions: FingerprintEnrollOptions,
): Flow<FingerEnrollState> =
fingerprintEnrollStateRepository.enroll(
hardwareAuthToken,
enrollReason,
fingerprintEnrollOptions,
)
override suspend fun removeFingerprint(fp: FingerprintData): Boolean = suspendCoroutine {
val callback =
object : RemovalCallback() {
override fun onRemovalError(
fp: android.hardware.fingerprint.Fingerprint,
errMsgId: Int,
errString: CharSequence,
) {
it.resume(false)
}
override fun onRemovalSucceeded(
fp: android.hardware.fingerprint.Fingerprint?,
remaining: Int,
) {
it.resume(true)
}
}
fingerprintManager?.remove(
android.hardware.fingerprint.Fingerprint(fp.name, fp.fingerId, fp.deviceId),
applicationContext.userId,
callback,
)
}
override suspend fun renameFingerprint(fp: FingerprintData, newName: String) {
withContext(backgroundDispatcher) {
fingerprintManager?.rename(fp.fingerId, applicationContext.userId, newName)
}
}
override suspend fun hasSideFps(): Boolean? = suspendCancellableCoroutine {
it.resume(fingerprintManager?.isPowerbuttonFps)
}
override suspend fun authenticate(): FingerprintAuthAttemptModel =
suspendCancellableCoroutine { c: CancellableContinuation<FingerprintAuthAttemptModel> ->
val authenticationCallback =
object : FingerprintManager.AuthenticationCallback() {
override fun onAuthenticationError(errorCode: Int, errString: CharSequence) {
super.onAuthenticationError(errorCode, errString)
if (c.isCompleted) {
Log.d(TAG, "framework sent down onAuthError after finish")
return
}
c.resume(FingerprintAuthAttemptModel.Error(errorCode, errString.toString()))
}
override fun onAuthenticationSucceeded(result: FingerprintManager.AuthenticationResult) {
super.onAuthenticationSucceeded(result)
if (c.isCompleted) {
Log.d(TAG, "framework sent down onAuthError after finish")
return
}
c.resume(FingerprintAuthAttemptModel.Success(result.fingerprint?.biometricId ?: -1))
}
}
val cancellationSignal = CancellationSignal()
c.invokeOnCancellation { cancellationSignal.cancel() }
fingerprintManager?.authenticate(
null,
cancellationSignal,
authenticationCallback,
null,
applicationContext.userId,
)
}
}

View File

@@ -20,9 +20,7 @@ import com.android.settings.biometrics.fingerprint2.data.repository.FingerprintS
import com.android.systemui.biometrics.shared.model.FingerprintSensor
import kotlinx.coroutines.flow.Flow
/**
* Interactor that propagates the type of [FingerprintSensor] this device supports.
*/
/** Interactor that propagates the type of [FingerprintSensor] this device supports. */
interface FingerprintSensorInteractor {
/** Get the [FingerprintSensor] */
val fingerprintSensor: Flow<FingerprintSensor>

View File

@@ -0,0 +1,48 @@
/*
* 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 android.content.Intent
import android.hardware.fingerprint.FingerprintManager
import com.android.settings.biometrics.GatekeeperPasswordProvider
import com.android.settings.biometrics.fingerprint2.lib.domain.interactor.GenerateChallengeInteractor
import com.android.settings.password.ChooseLockSettingsHelper
import kotlin.coroutines.resume
import kotlin.coroutines.suspendCoroutine
class GenerateChallengeInteractorImpl(
private val fingerprintManager: FingerprintManager,
private val userId: Int,
private val gatekeeperPasswordProvider: GatekeeperPasswordProvider,
) : GenerateChallengeInteractor {
override suspend fun generateChallenge(gateKeeperPasswordHandle: Long): Pair<Long, ByteArray> =
suspendCoroutine {
val callback =
FingerprintManager.GenerateChallengeCallback { _, userId, challenge ->
val intent = Intent()
intent.putExtra(ChooseLockSettingsHelper.EXTRA_KEY_GK_PW_HANDLE, gateKeeperPasswordHandle)
val challengeToken =
gatekeeperPasswordProvider.requestGatekeeperHat(intent, challenge, userId)
gatekeeperPasswordProvider.removeGatekeeperPasswordHandle(intent, false)
val p = Pair(challenge, challengeToken)
it.resume(p)
}
fingerprintManager.generateChallenge(userId, callback)
}
}

View File

@@ -0,0 +1,55 @@
/*
* 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 android.hardware.fingerprint.FingerprintManager
import android.hardware.fingerprint.FingerprintManager.RemovalCallback
import com.android.settings.biometrics.fingerprint2.lib.domain.interactor.RemoveFingerprintInteractor
import com.android.settings.biometrics.fingerprint2.lib.model.FingerprintData
import kotlin.coroutines.resume
import kotlin.coroutines.suspendCoroutine
class RemoveFingerprintsInteractorImpl(
private val fingerprintManager: FingerprintManager,
private val userId: Int,
) : RemoveFingerprintInteractor {
override suspend fun removeFingerprint(fp: FingerprintData): Boolean = suspendCoroutine {
val callback =
object : RemovalCallback() {
override fun onRemovalError(
fp: android.hardware.fingerprint.Fingerprint,
errMsgId: Int,
errString: CharSequence,
) {
it.resume(false)
}
override fun onRemovalSucceeded(
fp: android.hardware.fingerprint.Fingerprint?,
remaining: Int,
) {
it.resume(true)
}
}
fingerprintManager.remove(
android.hardware.fingerprint.Fingerprint(fp.name, fp.fingerId, fp.deviceId),
userId,
callback,
)
}
}

View File

@@ -0,0 +1,34 @@
/*
* 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 android.hardware.fingerprint.FingerprintManager
import com.android.settings.biometrics.fingerprint2.lib.domain.interactor.RenameFingerprintInteractor
import com.android.settings.biometrics.fingerprint2.lib.model.FingerprintData
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.withContext
class RenameFingerprintsInteractorImpl(
private val fingerprintManager: FingerprintManager,
private val userId: Int,
private val backgroundDispatcher: CoroutineDispatcher,
) : RenameFingerprintInteractor {
override suspend fun renameFingerprint(fp: FingerprintData, newName: String) {
withContext(backgroundDispatcher) { fingerprintManager.rename(fp.fingerId, userId, newName) }
}
}

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.FingerprintSensorRepository
import com.android.settings.biometrics.fingerprint2.lib.domain.interactor.SensorInteractor
import kotlinx.coroutines.flow.Flow
class SensorInteractorImpl(private val repo: FingerprintSensorRepository) :
SensorInteractor {
override val sensorPropertiesInternal = repo.fingerprintSensor
override val hasSideFps: Flow<Boolean> = repo.hasSideFps
}

View File

@@ -24,4 +24,3 @@ interface TouchEventInteractor {
/** A flow simulating user touches. */
val touchEvent: Flow<MotionEvent>
}

View File

@@ -13,6 +13,6 @@
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
<manifest
package="com.android.settings.biometrics.fingerprint2.lib">
</manifest>

View File

@@ -0,0 +1,25 @@
/*
* 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 com.android.settings.biometrics.fingerprint2.lib.model.FingerprintAuthAttemptModel
/** Interactor responsible for coordinating authentication. */
interface AuthenitcateInteractor {
/** Runs the authenticate flow */
suspend fun authenticate(): FingerprintAuthAttemptModel
}

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.lib.domain.interactor
import kotlinx.coroutines.flow.Flow
/** Returns whether or not a user can enroll a fingerprint */
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
}

View File

@@ -0,0 +1,35 @@
/*
* 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 android.hardware.fingerprint.FingerprintEnrollOptions
import com.android.settings.biometrics.fingerprint2.lib.model.EnrollReason
import com.android.settings.biometrics.fingerprint2.lib.model.FingerEnrollState
import kotlinx.coroutines.flow.Flow
/** Interactor that enrolls a fingerprint */
interface EnrollFingerprintInteractor {
/**
* Runs [FingerprintManager.enroll] with the [hardwareAuthToken] and [EnrollReason] for this
* enrollment. If successful data in the [fingerprintEnrollState] should be populated.
*/
suspend fun enroll(
hardwareAuthToken: ByteArray?,
enrollReason: EnrollReason,
fingerprintEnrollOptions: FingerprintEnrollOptions,
): Flow<FingerEnrollState>
}

View File

@@ -0,0 +1,26 @@
/*
* 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 com.android.settings.biometrics.fingerprint2.lib.model.FingerprintData
import kotlinx.coroutines.flow.Flow
/** Interface to obtain the enrolled fingerprints */
interface EnrolledFingerprintsInteractor {
/** Returns the list of current fingerprints. */
val enrolledFingerprints: Flow<List<FingerprintData>?>
}

View File

@@ -1,79 +0,0 @@
/*
* 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 android.hardware.fingerprint.FingerprintEnrollOptions
import com.android.settings.biometrics.fingerprint2.lib.model.EnrollReason
import com.android.settings.biometrics.fingerprint2.lib.model.FingerEnrollState
import com.android.settings.biometrics.fingerprint2.lib.model.FingerprintAuthAttemptModel
import com.android.settings.biometrics.fingerprint2.lib.model.FingerprintData
import com.android.systemui.biometrics.shared.model.FingerprintSensor
import kotlinx.coroutines.flow.Flow
/**
* Interface to obtain the necessary data for FingerprintEnrollment/Settings
*
* Note that this interface should not have dependencies on heavyweight libraries such as the
* framework, hidl/aidl, etc. This makes it much easier to test and create fakes for.
*/
interface FingerprintManagerInteractor {
/** Returns the list of current fingerprints. */
val enrolledFingerprints: Flow<List<FingerprintData>?>
/** Returns the max enrollable fingerprints, note during SUW this might be 1 */
val maxEnrollableFingerprints: Flow<Int>
/** Returns true if a user can enroll a fingerprint false otherwise. */
val canEnrollFingerprints: Flow<Boolean>
/** Retrieves the sensor properties of a device */
val sensorPropertiesInternal: Flow<FingerprintSensor?>
/** Runs the authenticate flow */
suspend fun authenticate(): FingerprintAuthAttemptModel
/**
* Generates a challenge with the provided [gateKeeperPasswordHandle] and on success returns a
* challenge and challenge token. This info can be used for secure operations such as enrollment
*
* @param gateKeeperPasswordHandle GateKeeper password handle generated by a Confirm
* @return A [Pair] of the challenge and challenge token
*/
suspend fun generateChallenge(gateKeeperPasswordHandle: Long): Pair<Long, ByteArray>
/**
* Runs [FingerprintManager.enroll] with the [hardwareAuthToken] and [EnrollReason] for this
* enrollment. If successful data in the [fingerprintEnrollState] should be populated.
*/
suspend fun enroll(
hardwareAuthToken: ByteArray?,
enrollReason: EnrollReason,
fingerprintEnrollOptions: FingerprintEnrollOptions,
): Flow<FingerEnrollState>
/**
* Removes the given fingerprint, returning true if it was successfully removed and false
* otherwise
*/
suspend fun removeFingerprint(fp: FingerprintData): Boolean
/** Renames the given fingerprint if one exists */
suspend fun renameFingerprint(fp: FingerprintData, newName: String)
/** Indicates if the device has side fingerprint */
suspend fun hasSideFps(): Boolean?
}

View File

@@ -0,0 +1,29 @@
/*
* 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
/** This interactor is responsible for generating a challenge. */
interface GenerateChallengeInteractor {
/**
* Generates a challenge with the provided [gateKeeperPasswordHandle] and on success returns a
* challenge and challenge token. This info can be used for secure operations such as enrollment
*
* @param gateKeeperPasswordHandle GateKeeper password handle generated by a Confirm
* @return A [Pair] of the challenge and challenge token
*/
suspend fun generateChallenge(gateKeeperPasswordHandle: Long): Pair<Long, ByteArray>
}

View File

@@ -0,0 +1,28 @@
/*
* 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 com.android.settings.biometrics.fingerprint2.lib.model.FingerprintData
/** Interactor in charge of removing a fingerprint */
interface RemoveFingerprintInteractor {
/**
* Removes the given fingerprint, returning true if it was successfully removed and false
* otherwise
*/
suspend fun removeFingerprint(fp: FingerprintData): Boolean
}

View File

@@ -0,0 +1,25 @@
/*
* 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 com.android.settings.biometrics.fingerprint2.lib.model.FingerprintData
/** Interactor that can rename a fingerprint. */
interface RenameFingerprintInteractor {
/** Renames the given fingerprint if one exists */
suspend fun renameFingerprint(fp: FingerprintData, newName: String)
}

View File

@@ -0,0 +1,28 @@
/*
* 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 com.android.systemui.biometrics.shared.model.FingerprintSensor
import kotlinx.coroutines.flow.Flow
/** Interactor that has various information about a fingerprint sensor */
interface SensorInteractor {
/** Retrieves the sensor properties of the device */
val sensorPropertiesInternal: Flow<FingerprintSensor?>
/** Indicates if the device supports side fps */
val hasSideFps: Flow<Boolean>
}

View File

@@ -96,8 +96,8 @@ class FingerprintEnrollmentV2Activity : FragmentActivity() {
}
/**
* View models below this line are not used by this class but must be initialized
* in the activity view model store to be used by other view models.
* View models below this line are not used by this class but must be initialized in the activity
* view model store to be used by other view models.
*/
private val fingerprintEnrollViewModel: FingerprintEnrollViewModel by viewModels {
FingerprintEnrollViewModel.Factory

View File

@@ -25,7 +25,7 @@ import androidx.lifecycle.viewmodel.initializer
import androidx.lifecycle.viewmodel.viewModelFactory
import com.android.settings.SettingsApplication
import com.android.settings.biometrics.fingerprint2.domain.interactor.OrientationInteractor
import com.android.settings.biometrics.fingerprint2.lib.domain.interactor.FingerprintManagerInteractor
import com.android.settings.biometrics.fingerprint2.lib.domain.interactor.SensorInteractor
import com.android.settings.biometrics.fingerprint2.lib.model.FingerEnrollState
import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.FingerprintAction
import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.FingerprintEnrollEnrollingViewModel
@@ -46,10 +46,10 @@ import kotlinx.coroutines.flow.update
/** View Model used by the rear fingerprint enrollment fragment. */
class RFPSViewModel(
private val fingerprintEnrollViewModel: FingerprintEnrollEnrollingViewModel,
private val navigationViewModel: FingerprintNavigationViewModel,
orientationInteractor: OrientationInteractor,
private val fingerprintManager: FingerprintManagerInteractor,
private val fingerprintEnrollViewModel: FingerprintEnrollEnrollingViewModel,
private val navigationViewModel: FingerprintNavigationViewModel,
orientationInteractor: OrientationInteractor,
private val sensorInteractor: SensorInteractor,
) : ViewModel() {
private val _textViewIsVisible = MutableStateFlow(false)
@@ -62,7 +62,7 @@ class RFPSViewModel(
val shouldAnimateIcon = _shouldAnimateIcon
private var enrollFlow: Flow<FingerEnrollState?> =
fingerprintManager.sensorPropertiesInternal.filterNotNull().combine(
sensorInteractor.sensorPropertiesInternal.filterNotNull().combine(
fingerprintEnrollViewModel.enrollFlow
) { props, enroll ->
if (props.sensorType == FingerprintSensorType.REAR) {
@@ -181,7 +181,7 @@ class RFPSViewModel(
provider[FingerprintEnrollEnrollingViewModel::class],
provider[FingerprintNavigationViewModel::class],
biometricEnvironment.orientationInteractor,
biometricEnvironment.fingerprintManagerInteractor,
biometricEnvironment.createSensorPropertiesInteractor(),
)
}
}

View File

@@ -38,7 +38,7 @@ import com.android.settings.biometrics.fingerprint2.domain.interactor.Orientatio
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.VibrationInteractor
import com.android.settings.biometrics.fingerprint2.lib.domain.interactor.FingerprintManagerInteractor
import com.android.settings.biometrics.fingerprint2.lib.domain.interactor.SensorInteractor
import com.android.settings.biometrics.fingerprint2.lib.model.FingerEnrollState
import com.android.settings.biometrics.fingerprint2.ui.enrollment.modules.enrolling.udfps.ui.model.DescriptionText
import com.android.settings.biometrics.fingerprint2.ui.enrollment.modules.enrolling.udfps.ui.model.HeaderText
@@ -76,17 +76,17 @@ class UdfpsViewModel(
enrollStageInteractor: EnrollStageInteractor,
orientationInteractor: OrientationInteractor,
udfpsEnrollInteractor: UdfpsEnrollInteractor,
fingerprintManager: FingerprintManagerInteractor,
accessibilityInteractor: AccessibilityInteractor,
sensorRepository: FingerprintSensorInteractor,
touchEventInteractor: TouchEventInteractor,
sensorInteractor: SensorInteractor,
) : ViewModel() {
private val isSetupWizard = flowOf(false)
private var shouldResetErollment = false
private var _enrollState: Flow<FingerEnrollState?> =
fingerprintManager.sensorPropertiesInternal.filterNotNull().combine(
sensorInteractor.sensorPropertiesInternal.filterNotNull().combine(
fingerprintEnrollEnrollingViewModel.enrollFlow
) { props, enroll ->
if (props.sensorType.isUdfps()) {
@@ -198,8 +198,7 @@ class UdfpsViewModel(
.distinctUntilChanged()
private val _touchEvent: MutableStateFlow<MotionEvent?> = MutableStateFlow(null)
val touchEvent =
_touchEvent.asStateFlow().filterNotNull()
val touchEvent = _touchEvent.asStateFlow().filterNotNull()
/** Determines the current [EnrollStageModel] enrollment is in */
private val enrollStage: Flow<EnrollStageModel> =
@@ -267,11 +266,7 @@ class UdfpsViewModel(
backgroundViewModel.background.filter { it }.collect { didGoToBackground() }
}
viewModelScope.launch {
touchEventInteractor.touchEvent.collect {
_touchEvent.update { it }
}
}
viewModelScope.launch { touchEventInteractor.touchEvent.collect { _touchEvent.update { it } } }
}
/** Indicates if we should show the lottie. */
@@ -430,10 +425,10 @@ class UdfpsViewModel(
biometricEnvironment.enrollStageInteractor,
biometricEnvironment.orientationInteractor,
biometricEnvironment.udfpsEnrollInteractor,
biometricEnvironment.fingerprintManagerInteractor,
biometricEnvironment.accessibilityInteractor,
biometricEnvironment.sensorInteractor,
biometricEnvironment.touchEventInteractor,
biometricEnvironment.createSensorPropertiesInteractor(),
)
}
}

View File

@@ -16,27 +16,27 @@
package com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel
import android.util.Log
import androidx.lifecycle.VIEW_MODEL_STORE_OWNER_KEY
import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.viewmodel.initializer
import androidx.lifecycle.viewmodel.viewModelFactory
import com.android.settings.SettingsApplication
import com.android.settings.biometrics.fingerprint2.lib.domain.interactor.FingerprintManagerInteractor
import com.android.settings.biometrics.fingerprint2.lib.domain.interactor.CanEnrollFingerprintsInteractor
import kotlinx.coroutines.flow.Flow
/** Models the UI state for [FingerprintEnrollConfirmationV2Fragment] */
class FingerprintEnrollConfirmationViewModel(
private val navigationViewModel: FingerprintNavigationViewModel,
fingerprintInteractor: FingerprintManagerInteractor,
private val canEnrollFingerprintsInteractor: CanEnrollFingerprintsInteractor,
) : ViewModel() {
/**
* Indicates if the add another button is possible. This should only be true when the user is able
* to enroll more fingerprints.
*/
val isAddAnotherButtonVisible: Flow<Boolean> = fingerprintInteractor.canEnrollFingerprints
val isAddAnotherButtonVisible: Flow<Boolean> =
canEnrollFingerprintsInteractor.canEnrollFingerprints
/**
* Indicates that the user has clicked the next button and is done with fingerprint enrollment.
@@ -64,7 +64,7 @@ class FingerprintEnrollConfirmationViewModel(
val provider = ViewModelProvider(this[VIEW_MODEL_STORE_OWNER_KEY]!!)
FingerprintEnrollConfirmationViewModel(
provider[FingerprintNavigationViewModel::class],
biometricEnvironment!!.fingerprintManagerInteractor,
biometricEnvironment!!.createCanEnrollFingerprintsInteractor(),
)
}
}

View File

@@ -27,7 +27,7 @@ import com.android.settings.SettingsApplication
import com.android.settings.biometrics.fingerprint2.domain.interactor.AccessibilityInteractor
import com.android.settings.biometrics.fingerprint2.domain.interactor.FoldStateInteractor
import com.android.settings.biometrics.fingerprint2.domain.interactor.OrientationInteractor
import com.android.settings.biometrics.fingerprint2.lib.domain.interactor.FingerprintManagerInteractor
import com.android.settings.biometrics.fingerprint2.lib.domain.interactor.SensorInteractor
import com.android.settings.biometrics.fingerprint2.lib.model.FingerEnrollState
import com.android.settings.biometrics.fingerprint2.lib.model.SetupWizard
import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.FingerprintNavigationStep.Education
@@ -44,20 +44,20 @@ import kotlinx.coroutines.launch
/** Models the UI state for fingerprint enroll education */
class FingerprintEnrollFindSensorViewModel(
private val navigationViewModel: FingerprintNavigationViewModel,
private val fingerprintEnrollViewModel: FingerprintEnrollViewModel,
private val gatekeeperViewModel: FingerprintGatekeeperViewModel,
backgroundViewModel: BackgroundViewModel,
fingerprintFlowViewModel: FingerprintFlowViewModel,
accessibilityInteractor: AccessibilityInteractor,
foldStateInteractor: FoldStateInteractor,
orientationInteractor: OrientationInteractor,
fingerprintManagerInteractor: FingerprintManagerInteractor,
private val navigationViewModel: FingerprintNavigationViewModel,
private val fingerprintEnrollViewModel: FingerprintEnrollViewModel,
private val gatekeeperViewModel: FingerprintGatekeeperViewModel,
backgroundViewModel: BackgroundViewModel,
fingerprintFlowViewModel: FingerprintFlowViewModel,
accessibilityInteractor: AccessibilityInteractor,
foldStateInteractor: FoldStateInteractor,
orientationInteractor: OrientationInteractor,
sensorInteractor: SensorInteractor,
) : ViewModel() {
/** Represents the stream of sensor type. */
val sensorType: Flow<FingerprintSensorType> =
fingerprintManagerInteractor.sensorPropertiesInternal.filterNotNull().map { it.sensorType }
sensorInteractor.sensorPropertiesInternal.filterNotNull().map { it.sensorType }
private val _isUdfps: Flow<Boolean> =
sensorType.map {
it == FingerprintSensorType.UDFPS_OPTICAL || it == FingerprintSensorType.UDFPS_ULTRASONIC
@@ -216,7 +216,7 @@ class FingerprintEnrollFindSensorViewModel(
biometricEnvironment.accessibilityInteractor,
biometricEnvironment.foldStateInteractor,
biometricEnvironment.orientationInteractor,
biometricEnvironment.fingerprintManagerInteractor,
biometricEnvironment.createSensorPropertiesInteractor(),
)
}
}

View File

@@ -22,7 +22,7 @@ import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.viewmodel.initializer
import androidx.lifecycle.viewmodel.viewModelFactory
import com.android.settings.SettingsApplication
import com.android.settings.biometrics.fingerprint2.lib.domain.interactor.FingerprintManagerInteractor
import com.android.settings.biometrics.fingerprint2.lib.domain.interactor.SensorInteractor
import com.android.settings.biometrics.fingerprint2.lib.model.FingerprintFlow
import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.FingerprintNavigationStep.Introduction
import com.android.systemui.biometrics.shared.model.FingerprintSensor
@@ -30,13 +30,13 @@ import kotlinx.coroutines.flow.Flow
/** A view model for fingerprint enroll introduction. */
class FingerprintEnrollIntroViewModel(
val navigationViewModel: FingerprintNavigationViewModel,
fingerprintFlowViewModel: FingerprintFlowViewModel,
fingerprintManagerInteractor: FingerprintManagerInteractor,
val navigationViewModel: FingerprintNavigationViewModel,
fingerprintFlowViewModel: FingerprintFlowViewModel,
sensorInteractor: SensorInteractor,
) : ViewModel() {
/** Represents a stream of [FingerprintSensor] */
val sensor: Flow<FingerprintSensor?> = fingerprintManagerInteractor.sensorPropertiesInternal
val sensor: Flow<FingerprintSensor?> = sensorInteractor.sensorPropertiesInternal
/** Represents a stream of [FingerprintFlow] */
val fingerprintFlow: Flow<FingerprintFlow?> = fingerprintFlowViewModel.fingerprintFlow
@@ -67,7 +67,7 @@ class FingerprintEnrollIntroViewModel(
FingerprintEnrollIntroViewModel(
provider[FingerprintNavigationViewModel::class],
provider[FingerprintFlowViewModel::class],
biometricEnvironment!!.fingerprintManagerInteractor,
biometricEnvironment!!.createSensorPropertiesInteractor(),
)
}
}

View File

@@ -24,7 +24,8 @@ import androidx.lifecycle.viewModelScope
import androidx.lifecycle.viewmodel.initializer
import androidx.lifecycle.viewmodel.viewModelFactory
import com.android.settings.SettingsApplication
import com.android.settings.biometrics.fingerprint2.lib.domain.interactor.FingerprintManagerInteractor
import com.android.settings.biometrics.fingerprint2.lib.domain.interactor.EnrollFingerprintInteractor
import com.android.settings.biometrics.fingerprint2.lib.domain.interactor.SensorInteractor
import com.android.settings.biometrics.fingerprint2.lib.model.EnrollReason
import com.android.settings.biometrics.fingerprint2.lib.model.FingerEnrollState
import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.FingerprintNavigationStep.Education
@@ -42,9 +43,10 @@ import kotlinx.coroutines.flow.update
/** Represents all of the fingerprint information needed for a fingerprint enrollment process. */
class FingerprintEnrollViewModel(
private val fingerprintManagerInteractor: FingerprintManagerInteractor,
gatekeeperViewModel: FingerprintGatekeeperViewModel,
val navigationViewModel: FingerprintNavigationViewModel,
gatekeeperViewModel: FingerprintGatekeeperViewModel,
val navigationViewModel: FingerprintNavigationViewModel,
private val sensorInteractor: SensorInteractor,
private val fingerprintEnrollInteractor: EnrollFingerprintInteractor,
) : ViewModel() {
/**
@@ -67,7 +69,7 @@ class FingerprintEnrollViewModel(
/** Represents the stream of [FingerprintSensorType] */
val sensorType: Flow<FingerprintSensorType?> =
fingerprintManagerInteractor.sensorPropertiesInternal.filterNotNull().map { it.sensorType }
sensorInteractor.sensorPropertiesInternal.filterNotNull().map { it.sensorType }
/**
* A flow that contains a [FingerprintEnrollViewModel] which contains the relevant information for
@@ -90,7 +92,7 @@ class FingerprintEnrollViewModel(
enrollReason != null &&
enrollOptions != null
) {
fingerprintManagerInteractor
fingerprintEnrollInteractor
.enroll(hardwareAuthToken.token, enrollReason, enrollOptions)
.collect { emit(it) }
}
@@ -137,9 +139,10 @@ class FingerprintEnrollViewModel(
val biometricEnvironment = settingsApplication.biometricEnvironment
val provider = ViewModelProvider(this[VIEW_MODEL_STORE_OWNER_KEY]!!)
FingerprintEnrollViewModel(
biometricEnvironment!!.fingerprintManagerInteractor,
provider[FingerprintGatekeeperViewModel::class],
provider[FingerprintNavigationViewModel::class],
biometricEnvironment!!.createSensorPropertiesInteractor(),
biometricEnvironment!!.createFingerprintEnrollInteractor(),
)
}
}

View File

@@ -24,7 +24,7 @@ import androidx.lifecycle.viewModelScope
import androidx.lifecycle.viewmodel.initializer
import androidx.lifecycle.viewmodel.viewModelFactory
import com.android.settings.SettingsApplication
import com.android.settings.biometrics.fingerprint2.lib.domain.interactor.FingerprintManagerInteractor
import com.android.settings.biometrics.fingerprint2.lib.domain.interactor.GenerateChallengeInteractor
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow
@@ -50,7 +50,7 @@ sealed interface GatekeeperInfo {
* in as a parameter to this class.
*/
class FingerprintGatekeeperViewModel(
private val fingerprintManagerInteractor: FingerprintManagerInteractor
private val generateChallengeInteractor: GenerateChallengeInteractor
) : ViewModel() {
private var _gatekeeperInfo: MutableStateFlow<GatekeeperInfo?> = MutableStateFlow(null)
@@ -78,7 +78,7 @@ class FingerprintGatekeeperViewModel(
_gatekeeperInfo.update { GatekeeperInfo.Invalid }
} else {
viewModelScope.launch {
val res = fingerprintManagerInteractor.generateChallenge(theGatekeeperPasswordHandle!!)
val res = generateChallengeInteractor.generateChallenge(theGatekeeperPasswordHandle!!)
_gatekeeperInfo.update { GatekeeperInfo.GatekeeperPasswordInfo(res.second, res.first) }
if (shouldStartTimer) {
startTimeout()
@@ -119,7 +119,7 @@ class FingerprintGatekeeperViewModel(
val settingsApplication =
this[ViewModelProvider.AndroidViewModelFactory.APPLICATION_KEY] as SettingsApplication
val biometricEnvironment = settingsApplication.biometricEnvironment
FingerprintGatekeeperViewModel(biometricEnvironment!!.fingerprintManagerInteractor)
FingerprintGatekeeperViewModel(biometricEnvironment!!.createGenerateChallengeInteractor())
}
}
}

View File

@@ -23,7 +23,7 @@ import androidx.lifecycle.viewModelScope
import androidx.lifecycle.viewmodel.initializer
import androidx.lifecycle.viewmodel.viewModelFactory
import com.android.settings.SettingsApplication
import com.android.settings.biometrics.fingerprint2.lib.domain.interactor.FingerprintManagerInteractor
import com.android.settings.biometrics.fingerprint2.lib.domain.interactor.SensorInteractor
import com.android.settings.biometrics.fingerprint2.lib.model.FingerprintFlow
import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.FingerprintNavigationStep.Finish
import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.FingerprintNavigationStep.TransitionStep
@@ -46,7 +46,7 @@ import kotlinx.coroutines.flow.update
* fragments/viewmodels that want to consume these events. It should provide no additional
* functionality beyond what is available in [FingerprintNavigationStep].
*/
class FingerprintNavigationViewModel(fingerprintManagerInteractor: FingerprintManagerInteractor) :
class FingerprintNavigationViewModel(sensorInteractor: SensorInteractor) :
ViewModel() {
private val _flowInternal: MutableStateFlow<FingerprintFlow?> = MutableStateFlow(null)
@@ -55,7 +55,7 @@ class FingerprintNavigationViewModel(fingerprintManagerInteractor: FingerprintMa
combine(
_flowInternal,
_hasConfirmedDeviceCredential,
fingerprintManagerInteractor.sensorPropertiesInternal,
sensorInteractor.sensorPropertiesInternal,
) { flow, hasConfirmed, sensorType ->
if (flow == null || sensorType == null) {
return@combine null
@@ -144,7 +144,7 @@ class FingerprintNavigationViewModel(fingerprintManagerInteractor: FingerprintMa
val settingsApplication =
this[ViewModelProvider.AndroidViewModelFactory.APPLICATION_KEY] as SettingsApplication
val biometricEnvironment = settingsApplication.biometricEnvironment
FingerprintNavigationViewModel(biometricEnvironment!!.fingerprintManagerInteractor)
FingerprintNavigationViewModel(biometricEnvironment!!.createSensorPropertiesInteractor())
}
}
}

View File

@@ -35,19 +35,16 @@ import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.lifecycleScope
import androidx.preference.Preference
import androidx.preference.PreferenceCategory
import com.android.internal.widget.LockPatternUtils
import com.android.settings.R
import com.android.settings.SettingsApplication
import com.android.settings.Utils.SETTINGS_PACKAGE_NAME
import com.android.settings.biometrics.BiometricEnrollBase
import com.android.settings.biometrics.BiometricEnrollBase.CONFIRM_REQUEST
import com.android.settings.biometrics.BiometricEnrollBase.EXTRA_FROM_SETTINGS_SUMMARY
import com.android.settings.biometrics.BiometricEnrollBase.RESULT_FINISHED
import com.android.settings.biometrics.GatekeeperPasswordProvider
import com.android.settings.biometrics.fingerprint.FingerprintEnrollEnrolling
import com.android.settings.biometrics.fingerprint.FingerprintEnrollIntroductionInternal
import com.android.settings.biometrics.fingerprint2.data.repository.FingerprintSensorRepositoryImpl
import com.android.settings.biometrics.fingerprint2.domain.interactor.FingerprintEnrollInteractorImpl
import com.android.settings.biometrics.fingerprint2.domain.interactor.FingerprintManagerInteractorImpl
import com.android.settings.biometrics.fingerprint2.domain.interactor.PressToAuthInteractorImpl
import com.android.settings.biometrics.fingerprint2.lib.model.FingerprintAuthAttemptModel
import com.android.settings.biometrics.fingerprint2.lib.model.FingerprintData
@@ -223,35 +220,24 @@ class FingerprintSettingsV2Fragment :
val fingerprintSensorProvider =
FingerprintSensorRepositoryImpl(fingerprintManager, backgroundDispatcher, lifecycleScope)
val pressToAuthInteractor = PressToAuthInteractorImpl(context, backgroundDispatcher)
val fingerprintEnrollStateRepository =
FingerprintEnrollInteractorImpl(
requireContext().applicationContext,
fingerprintManager,
Settings,
)
val interactor =
FingerprintManagerInteractorImpl(
context.applicationContext,
backgroundDispatcher,
fingerprintManager,
fingerprintSensorProvider,
GatekeeperPasswordProvider(LockPatternUtils(context.applicationContext)),
fingerprintEnrollStateRepository,
)
val token = intent.getByteArrayExtra(ChooseLockSettingsHelper.EXTRA_KEY_CHALLENGE_TOKEN)
val challenge = intent.getLongExtra(BiometricEnrollBase.EXTRA_KEY_CHALLENGE, -1L)
val application = requireActivity().application as SettingsApplication
val environment =
application.biometricEnvironment
?: throw IllegalStateException("The biometric environment must be present")
navigationViewModel =
ViewModelProvider(
this,
FingerprintSettingsNavigationViewModel.FingerprintSettingsNavigationModelFactory(
userId,
interactor,
backgroundDispatcher,
token,
challenge,
environment.createFingerprintsEnrolledInteractor(),
environment.createGenerateChallengeInteractor(),
),
)[FingerprintSettingsNavigationViewModel::class.java]
@@ -260,9 +246,14 @@ class FingerprintSettingsV2Fragment :
this,
FingerprintSettingsViewModel.FingerprintSettingsViewModelFactory(
userId,
interactor,
backgroundDispatcher,
navigationViewModel,
environment.createCanEnrollFingerprintsInteractor(),
environment.createSensorPropertiesInteractor(),
environment.createAuthenticateInteractor(),
environment.createRenameFingerprintInteractor(),
environment.createRemoveFingerprintInteractor(),
environment.createFingerprintsEnrolledInteractor(),
),
)[FingerprintSettingsViewModel::class.java]

View File

@@ -21,7 +21,8 @@ import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.viewModelScope
import com.android.settings.biometrics.BiometricEnrollBase
import com.android.settings.biometrics.fingerprint2.lib.domain.interactor.FingerprintManagerInteractor
import com.android.settings.biometrics.fingerprint2.lib.domain.interactor.EnrolledFingerprintsInteractor
import com.android.settings.biometrics.fingerprint2.lib.domain.interactor.GenerateChallengeInteractor
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
@@ -33,10 +34,11 @@ import kotlinx.coroutines.launch
/** A Viewmodel that represents the navigation of the FingerprintSettings activity. */
class FingerprintSettingsNavigationViewModel(
private val userId: Int,
private val fingerprintManagerInteractor: FingerprintManagerInteractor,
private val backgroundDispatcher: CoroutineDispatcher,
tokenInit: ByteArray?,
challengeInit: Long?,
private val enrolledFingerprintsInteractor: EnrolledFingerprintsInteractor,
private val generateChallengeInteractor: GenerateChallengeInteractor,
) : ViewModel() {
private var token = tokenInit
@@ -52,7 +54,7 @@ class FingerprintSettingsNavigationViewModel(
_nextStep.update { LaunchConfirmDeviceCredential(userId) }
} else {
viewModelScope.launch {
if (fingerprintManagerInteractor.enrolledFingerprints.last()?.isEmpty() == true) {
if (enrolledFingerprintsInteractor.enrolledFingerprints.last()?.isEmpty() == true) {
_nextStep.update { EnrollFirstFingerprint(userId, null, challenge, token) }
} else {
showSettingsHelper()
@@ -148,13 +150,13 @@ class FingerprintSettingsNavigationViewModel(
}
private suspend fun launchEnrollNextStep(gateKeeperPasswordHandle: Long?) {
fingerprintManagerInteractor.enrolledFingerprints.collect {
enrolledFingerprintsInteractor.enrolledFingerprints.collect {
if (it?.isEmpty() == true) {
_nextStep.update { EnrollFirstFingerprint(userId, gateKeeperPasswordHandle, null, null) }
} else {
viewModelScope.launch(backgroundDispatcher) {
val challengePair =
fingerprintManagerInteractor.generateChallenge(gateKeeperPasswordHandle!!)
generateChallengeInteractor.generateChallenge(gateKeeperPasswordHandle!!)
challenge = challengePair.first
token = challengePair.second
@@ -174,10 +176,11 @@ class FingerprintSettingsNavigationViewModel(
class FingerprintSettingsNavigationModelFactory(
private val userId: Int,
private val interactor: FingerprintManagerInteractor,
private val backgroundDispatcher: CoroutineDispatcher,
private val token: ByteArray?,
private val challenge: Long?,
private val enrolledFingerprintsInteractor: EnrolledFingerprintsInteractor,
private val generateChallengeInteractor: GenerateChallengeInteractor,
) : ViewModelProvider.Factory {
@Suppress("UNCHECKED_CAST")
@@ -185,10 +188,11 @@ class FingerprintSettingsNavigationViewModel(
return FingerprintSettingsNavigationViewModel(
userId,
interactor,
backgroundDispatcher,
token,
challenge,
enrolledFingerprintsInteractor,
generateChallengeInteractor,
)
as T
}

View File

@@ -21,7 +21,12 @@ import android.util.Log
import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.viewModelScope
import com.android.settings.biometrics.fingerprint2.lib.domain.interactor.FingerprintManagerInteractor
import com.android.settings.biometrics.fingerprint2.lib.domain.interactor.AuthenitcateInteractor
import com.android.settings.biometrics.fingerprint2.lib.domain.interactor.CanEnrollFingerprintsInteractor
import com.android.settings.biometrics.fingerprint2.lib.domain.interactor.EnrolledFingerprintsInteractor
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.model.FingerprintAuthAttemptModel
import com.android.settings.biometrics.fingerprint2.lib.model.FingerprintData
import com.android.systemui.biometrics.shared.model.FingerprintSensorType
@@ -49,9 +54,14 @@ private const val DEBUG = false
/** Models the UI state for fingerprint settings. */
class FingerprintSettingsViewModel(
private val userId: Int,
private val fingerprintManagerInteractor: FingerprintManagerInteractor,
private val backgroundDispatcher: CoroutineDispatcher,
private val navigationViewModel: FingerprintSettingsNavigationViewModel,
private val canEnrollFingerprintsInteractor: CanEnrollFingerprintsInteractor,
private val sensorInteractor: SensorInteractor,
private val authenticateInteractor: AuthenitcateInteractor,
private val renameFingerprintInteractor: RenameFingerprintInteractor,
private val removeFingerprintInteractor: RemoveFingerprintInteractor,
private val enrolledFingerprintsInteractor: EnrolledFingerprintsInteractor,
) : ViewModel() {
private val _enrolledFingerprints: MutableStateFlow<List<FingerprintData>?> =
MutableStateFlow(null)
@@ -62,19 +72,18 @@ class FingerprintSettingsViewModel(
/** Represents the stream of the information of "Add Fingerprint" preference. */
val addFingerprintPrefInfo: Flow<Pair<Boolean, Int>> =
_enrolledFingerprints.filterOnlyWhenSettingsIsShown().transform {
emit(
Pair(
fingerprintManagerInteractor.canEnrollFingerprints.first(),
fingerprintManagerInteractor.maxEnrollableFingerprints.first(),
)
)
_enrolledFingerprints.filterOnlyWhenSettingsIsShown().combine(
canEnrollFingerprintsInteractor.canEnrollFingerprints
) { _, canEnrollFingerprints ->
Pair(canEnrollFingerprints, canEnrollFingerprintsInteractor.maxFingerprintsEnrollable())
}
/** Represents the stream of visibility of sfps preference. */
val isSfpsPrefVisible: Flow<Boolean> =
_enrolledFingerprints.filterOnlyWhenSettingsIsShown().transform {
emit(fingerprintManagerInteractor.hasSideFps() == true && !it.isNullOrEmpty())
_enrolledFingerprints.filterOnlyWhenSettingsIsShown().combine(sensorInteractor.hasSideFps) {
fingerprints,
hasSideFps ->
hasSideFps && !fingerprints.isNullOrEmpty()
}
private val _isShowingDialog: MutableStateFlow<PreferenceViewModel?> = MutableStateFlow(null)
@@ -90,10 +99,10 @@ class FingerprintSettingsViewModel(
private val _consumerShouldAuthenticate: MutableStateFlow<Boolean> = MutableStateFlow(false)
private val _fingerprintSensorType: Flow<FingerprintSensorType> =
fingerprintManagerInteractor.sensorPropertiesInternal.filterNotNull().map { it.sensorType }
sensorInteractor.sensorPropertiesInternal.filterNotNull().map { it.sensorType }
private val _sensorNullOrEmpty: Flow<Boolean> =
fingerprintManagerInteractor.sensorPropertiesInternal.map { it == null }
sensorInteractor.sensorPropertiesInternal.map { it == null }
private val _isLockedOut: MutableStateFlow<FingerprintAuthAttemptModel.Error?> =
MutableStateFlow(null)
@@ -172,7 +181,7 @@ class FingerprintSettingsViewModel(
while (it && navigationViewModel.nextStep.value is ShowSettings) {
Log.d(TAG, "canAuthenticate authing")
attemptingAuth()
when (val authAttempt = fingerprintManagerInteractor.authenticate()) {
when (val authAttempt = authenticateInteractor.authenticate()) {
is FingerprintAuthAttemptModel.Success -> {
onAuthSuccess(authAttempt)
emit(authAttempt)
@@ -243,7 +252,7 @@ class FingerprintSettingsViewModel(
/** A request to delete a fingerprint */
fun deleteFingerprint(fp: FingerprintData) {
viewModelScope.launch(backgroundDispatcher) {
if (fingerprintManagerInteractor.removeFingerprint(fp)) {
if (removeFingerprintInteractor.removeFingerprint(fp)) {
updateEnrolledFingerprints()
}
}
@@ -252,7 +261,7 @@ class FingerprintSettingsViewModel(
/** A request to rename a fingerprint */
fun renameFingerprint(fp: FingerprintData, newName: String) {
viewModelScope.launch {
fingerprintManagerInteractor.renameFingerprint(fp, newName)
renameFingerprintInteractor.renameFingerprint(fp, newName)
updateEnrolledFingerprints()
}
}
@@ -271,7 +280,7 @@ class FingerprintSettingsViewModel(
}
private suspend fun updateEnrolledFingerprints() {
_enrolledFingerprints.update { fingerprintManagerInteractor.enrolledFingerprints.first() }
_enrolledFingerprints.update { enrolledFingerprintsInteractor.enrolledFingerprints.first() }
}
/** Used to indicate whether the consumer of the view model is ready for authentication. */
@@ -288,9 +297,14 @@ class FingerprintSettingsViewModel(
class FingerprintSettingsViewModelFactory(
private val userId: Int,
private val interactor: FingerprintManagerInteractor,
private val backgroundDispatcher: CoroutineDispatcher,
private val navigationViewModel: FingerprintSettingsNavigationViewModel,
private val canEnrollFingerprintsInteractor: CanEnrollFingerprintsInteractor,
private val sensorInteractor: SensorInteractor,
private val authenticateInteractor: AuthenitcateInteractor,
private val renameFingerprintInteractor: RenameFingerprintInteractor,
private val removeFingerprintInteractor: RemoveFingerprintInteractor,
private val enrolledFingerprintsInteractor: EnrolledFingerprintsInteractor,
) : ViewModelProvider.Factory {
@Suppress("UNCHECKED_CAST")
@@ -298,9 +312,14 @@ class FingerprintSettingsViewModel(
return FingerprintSettingsViewModel(
userId,
interactor,
backgroundDispatcher,
navigationViewModel,
canEnrollFingerprintsInteractor,
sensorInteractor,
authenticateInteractor,
renameFingerprintInteractor,
removeFingerprintInteractor,
enrolledFingerprintsInteractor,
)
as T
}

View File

@@ -111,9 +111,10 @@ class Injector(step: FingerprintNavigationStep.UiStep) {
var fingerprintEnrollViewModel =
FingerprintEnrollViewModel(
fingerprintManagerInteractor,
gatekeeperViewModel,
navigationViewModel,
fingerprintManagerInteractor,
fingerprintManagerInteractor,
)
var fingerprintEnrollEnrollingViewModel =

View File

@@ -22,7 +22,14 @@ import android.hardware.biometrics.SensorProperties
import android.hardware.fingerprint.FingerprintEnrollOptions
import android.hardware.fingerprint.FingerprintSensorProperties
import android.hardware.fingerprint.FingerprintSensorPropertiesInternal
import com.android.settings.biometrics.fingerprint2.lib.domain.interactor.FingerprintManagerInteractor
import com.android.settings.biometrics.fingerprint2.lib.domain.interactor.AuthenitcateInteractor
import com.android.settings.biometrics.fingerprint2.lib.domain.interactor.CanEnrollFingerprintsInteractor
import com.android.settings.biometrics.fingerprint2.lib.domain.interactor.EnrollFingerprintInteractor
import com.android.settings.biometrics.fingerprint2.lib.domain.interactor.EnrolledFingerprintsInteractor
import com.android.settings.biometrics.fingerprint2.lib.domain.interactor.GenerateChallengeInteractor
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.model.EnrollReason
import com.android.settings.biometrics.fingerprint2.lib.model.FingerEnrollState
import com.android.settings.biometrics.fingerprint2.lib.model.FingerprintAuthAttemptModel
@@ -35,7 +42,15 @@ import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.flow.flowOf
/** Fake to be used by other classes to easily fake the FingerprintManager implementation. */
class FakeFingerprintManagerInteractor : FingerprintManagerInteractor {
class FakeFingerprintManagerInteractor :
AuthenitcateInteractor,
CanEnrollFingerprintsInteractor,
EnrolledFingerprintsInteractor,
EnrollFingerprintInteractor,
GenerateChallengeInteractor,
RemoveFingerprintInteractor,
RenameFingerprintInteractor,
SensorInteractor {
var enrollableFingerprints: Int = 5
var enrolledFingerprintsInternal: MutableList<FingerprintData> = mutableListOf()
@@ -67,19 +82,22 @@ class FakeFingerprintManagerInteractor : FingerprintManagerInteractor {
override val enrolledFingerprints: Flow<List<FingerprintData>> = flow {
emit(enrolledFingerprintsInternal)
}
override val canEnrollFingerprints: Flow<Boolean> = flow {
emit(enrolledFingerprintsInternal.size < enrollableFingerprints)
}
override val sensorPropertiesInternal: Flow<FingerprintSensor?> = flow { emit(sensorProp) }
override fun maxFingerprintsEnrollable(): Int {
return enrollableFingerprints
}
override val maxEnrollableFingerprints: Flow<Int> = flow { emit(enrollableFingerprints) }
override val sensorPropertiesInternal: Flow<FingerprintSensor?> = flow { emit(sensorProp) }
override val hasSideFps: Flow<Boolean> =
flowOf(sensorProp.sensorType == FingerprintSensorType.POWER_BUTTON)
override suspend fun enroll(
hardwareAuthToken: ByteArray?,
enrollReason: EnrollReason,
fingerprintEnrollOptions: FingerprintEnrollOptions
fingerprintEnrollOptions: FingerprintEnrollOptions,
): Flow<FingerEnrollState> = flowOf(*enrollStateViewModel.toTypedArray())
override suspend fun removeFingerprint(fp: FingerprintData): Boolean {
@@ -92,7 +110,4 @@ class FakeFingerprintManagerInteractor : FingerprintManagerInteractor {
}
}
override suspend fun hasSideFps(): Boolean {
return sensorProp.sensorType == FingerprintSensorType.POWER_BUTTON
}
}

View File

@@ -16,7 +16,6 @@
package com.android.settings.fingerprint2.domain.interactor
import android.content.Context
import android.content.Intent
import android.hardware.biometrics.ComponentInfoInternal
import android.hardware.biometrics.SensorLocationInternal
@@ -30,23 +29,37 @@ import android.hardware.fingerprint.FingerprintSensorProperties
import android.hardware.fingerprint.FingerprintSensorPropertiesInternal
import android.os.CancellationSignal
import android.os.Handler
import androidx.test.core.app.ApplicationProvider
import com.android.settings.biometrics.GatekeeperPasswordProvider
import com.android.settings.biometrics.fingerprint2.data.repository.FingerprintEnrollmentRepositoryImpl
import com.android.settings.biometrics.fingerprint2.data.repository.FingerprintSensorRepository
import com.android.settings.biometrics.fingerprint2.domain.interactor.FingerprintEnrollInteractorImpl
import com.android.settings.biometrics.fingerprint2.domain.interactor.FingerprintManagerInteractorImpl
import com.android.settings.biometrics.fingerprint2.lib.domain.interactor.FingerprintManagerInteractor
import com.android.settings.biometrics.fingerprint2.data.repository.FingerprintSettingsRepositoryImpl
import com.android.settings.biometrics.fingerprint2.data.repository.UserRepo
import com.android.settings.biometrics.fingerprint2.domain.interactor.AuthenticateInteractorImpl
import com.android.settings.biometrics.fingerprint2.domain.interactor.CanEnrollFingerprintsInteractorImpl
import com.android.settings.biometrics.fingerprint2.domain.interactor.EnrollFingerprintInteractorImpl
import com.android.settings.biometrics.fingerprint2.domain.interactor.EnrolledFingerprintsInteractorImpl
import com.android.settings.biometrics.fingerprint2.domain.interactor.GenerateChallengeInteractorImpl
import com.android.settings.biometrics.fingerprint2.domain.interactor.RemoveFingerprintsInteractorImpl
import com.android.settings.biometrics.fingerprint2.domain.interactor.RenameFingerprintsInteractorImpl
import com.android.settings.biometrics.fingerprint2.lib.domain.interactor.CanEnrollFingerprintsInteractor
import com.android.settings.biometrics.fingerprint2.lib.domain.interactor.EnrollFingerprintInteractor
import com.android.settings.biometrics.fingerprint2.lib.domain.interactor.EnrolledFingerprintsInteractor
import com.android.settings.biometrics.fingerprint2.lib.domain.interactor.GenerateChallengeInteractor
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.model.Default
import com.android.settings.biometrics.fingerprint2.lib.model.EnrollReason
import com.android.settings.biometrics.fingerprint2.lib.model.FingerEnrollState
import com.android.settings.biometrics.fingerprint2.lib.model.FingerprintAuthAttemptModel
import com.android.settings.biometrics.fingerprint2.lib.model.FingerprintData
import com.android.settings.biometrics.fingerprint2.lib.model.FingerprintFlow
import com.android.settings.password.ChooseLockSettingsHelper
import com.android.systemui.biometrics.shared.model.FingerprintSensor
import com.android.systemui.biometrics.shared.model.toFingerprintSensor
import com.google.common.truth.Truth.assertThat
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.launch
@@ -75,13 +88,28 @@ import org.mockito.stubbing.OngoingStubbing
class FingerprintManagerInteractorTest {
@JvmField @Rule var rule = MockitoJUnit.rule()
private lateinit var underTest: FingerprintManagerInteractor
private var context: Context = ApplicationProvider.getApplicationContext()
private lateinit var enrolledFingerprintsInteractorUnderTest: EnrolledFingerprintsInteractor
private lateinit var generateChallengeInteractorUnderTest: GenerateChallengeInteractor
private lateinit var removeFingerprintsInteractorUnderTest: RemoveFingerprintInteractor
private lateinit var renameFingerprintsInteractorUnderTest: RenameFingerprintInteractor
private lateinit var authenticateInteractorImplUnderTest: AuthenticateInteractorImpl
private lateinit var canEnrollFingerprintsInteractorUnderTest: CanEnrollFingerprintsInteractor
private lateinit var enrollInteractorUnderTest: EnrollFingerprintInteractor
private val userId = 0
private var backgroundDispatcher = StandardTestDispatcher()
@Mock private lateinit var fingerprintManager: FingerprintManager
@Mock private lateinit var gateKeeperPasswordProvider: GatekeeperPasswordProvider
private var testScope = TestScope(backgroundDispatcher)
private var backgroundScope = testScope.backgroundScope
private val flow: FingerprintFlow = Default
private val maxFingerprints = 5
private val currUser = MutableStateFlow(0)
private val userRepo =
object : UserRepo {
override val currentUser: Flow<Int> = currUser
}
@Before
fun setup() {
@@ -89,7 +117,7 @@ class FingerprintManagerInteractorTest {
FingerprintSensorPropertiesInternal(
0 /* sensorId */,
SensorProperties.STRENGTH_STRONG,
5 /* maxEnrollmentsPerUser */,
maxFingerprints,
listOf<ComponentInfoInternal>(),
FingerprintSensorProperties.TYPE_POWER_BUTTON,
false /* halControlsIllumination */,
@@ -97,20 +125,37 @@ class FingerprintManagerInteractorTest {
listOf<SensorLocationInternal>(SensorLocationInternal.DEFAULT),
)
.toFingerprintSensor()
val fingerprintSensorRepository =
object : FingerprintSensorRepository {
override val fingerprintSensor: Flow<FingerprintSensor> = flowOf(sensor)
override val hasSideFps: Flow<Boolean> = flowOf(false)
}
underTest =
FingerprintManagerInteractorImpl(
context,
backgroundDispatcher,
val settingsRepository = FingerprintSettingsRepositoryImpl(maxFingerprints)
val fingerprintEnrollmentRepository =
FingerprintEnrollmentRepositoryImpl(
fingerprintManager,
fingerprintSensorRepository,
gateKeeperPasswordProvider,
FingerprintEnrollInteractorImpl(context, fingerprintManager, Default),
userRepo,
settingsRepository,
backgroundDispatcher,
backgroundScope,
)
enrolledFingerprintsInteractorUnderTest =
EnrolledFingerprintsInteractorImpl(fingerprintManager, userId)
generateChallengeInteractorUnderTest =
GenerateChallengeInteractorImpl(fingerprintManager, userId, gateKeeperPasswordProvider)
removeFingerprintsInteractorUnderTest =
RemoveFingerprintsInteractorImpl(fingerprintManager, userId)
renameFingerprintsInteractorUnderTest =
RenameFingerprintsInteractorImpl(fingerprintManager, userId, backgroundDispatcher)
authenticateInteractorImplUnderTest = AuthenticateInteractorImpl(fingerprintManager, userId)
canEnrollFingerprintsInteractorUnderTest =
CanEnrollFingerprintsInteractorImpl(fingerprintEnrollmentRepository)
enrollInteractorUnderTest = EnrollFingerprintInteractorImpl(userId, fingerprintManager, flow)
}
@Test
@@ -119,7 +164,8 @@ class FingerprintManagerInteractorTest {
whenever(fingerprintManager.getEnrolledFingerprints(anyInt())).thenReturn(emptyList())
val emptyFingerprintList: List<Fingerprint> = emptyList()
assertThat(underTest.enrolledFingerprints.last()).isEqualTo(emptyFingerprintList)
assertThat(enrolledFingerprintsInteractorUnderTest.enrolledFingerprints.last())
.isEqualTo(emptyFingerprintList)
}
@Test
@@ -129,7 +175,7 @@ class FingerprintManagerInteractorTest {
val fingerprintList: List<Fingerprint> = listOf(expected)
whenever(fingerprintManager.getEnrolledFingerprints(anyInt())).thenReturn(fingerprintList)
val list = underTest.enrolledFingerprints.last()
val list = enrolledFingerprintsInteractorUnderTest.enrolledFingerprints.last()
assertThat(list!!.size).isEqualTo(fingerprintList.size)
val actual = list[0]
assertThat(actual.name).isEqualTo(expected.name)
@@ -138,24 +184,51 @@ class FingerprintManagerInteractorTest {
}
@Test
fun testCanEnrollFingerprint() =
fun testCanEnrollFingerprintSucceeds() =
testScope.runTest {
val fingerprintList1: List<Fingerprint> =
val fingerprintList: List<Fingerprint> =
listOf(
Fingerprint("Finger 1,", 2, 3L),
Fingerprint("Finger 2,", 3, 3L),
Fingerprint("Finger 3,", 4, 3L),
Fingerprint("Finger 1", 2, 3L),
Fingerprint("Finger 2", 3, 3L),
Fingerprint("Finger 3", 4, 3L),
)
val fingerprintList2: List<Fingerprint> =
fingerprintList1.plus(
listOf(Fingerprint("Finger 4,", 5, 3L), Fingerprint("Finger 5,", 6, 3L))
)
whenever(fingerprintManager.getEnrolledFingerprints(anyInt()))
.thenReturn(fingerprintList1)
.thenReturn(fingerprintList2)
whenever(fingerprintManager.getEnrolledFingerprints(anyInt())).thenReturn(fingerprintList)
assertThat(underTest.canEnrollFingerprints.last()).isTrue()
assertThat(underTest.canEnrollFingerprints.last()).isFalse()
var result: Boolean? = null
val job =
testScope.launch {
canEnrollFingerprintsInteractorUnderTest.canEnrollFingerprints.collect { result = it }
}
runCurrent()
job.cancelAndJoin()
assertThat(result).isTrue()
}
@Test
fun testCanEnrollFingerprintFails() =
testScope.runTest {
val fingerprintList: List<Fingerprint> =
listOf(
Fingerprint("Finger 1", 2, 3L),
Fingerprint("Finger 2", 3, 3L),
Fingerprint("Finger 3", 4, 3L),
Fingerprint("Finger 4", 5, 3L),
Fingerprint("Finger 5", 6, 3L),
)
whenever(fingerprintManager.getEnrolledFingerprints(anyInt())).thenReturn(fingerprintList)
var result: Boolean? = null
val job =
testScope.launch {
canEnrollFingerprintsInteractorUnderTest.canEnrollFingerprints.collect { result = it }
}
runCurrent()
job.cancelAndJoin()
assertThat(result).isFalse()
}
@Test
@@ -178,7 +251,8 @@ class FingerprintManagerInteractorTest {
argumentCaptor()
var result: Pair<Long, ByteArray?>? = null
val job = testScope.launch { result = underTest.generateChallenge(1L) }
val job =
testScope.launch { result = generateChallengeInteractorUnderTest.generateChallenge(1L) }
runCurrent()
verify(fingerprintManager).generateChallenge(anyInt(), capture(generateChallengeCallback))
@@ -201,7 +275,10 @@ class FingerprintManagerInteractorTest {
var result: Boolean? = null
val job =
testScope.launch { result = underTest.removeFingerprint(fingerprintViewModelToRemove) }
testScope.launch {
result =
removeFingerprintsInteractorUnderTest.removeFingerprint(fingerprintViewModelToRemove)
}
runCurrent()
verify(fingerprintManager)
@@ -224,7 +301,10 @@ class FingerprintManagerInteractorTest {
var result: Boolean? = null
val job =
testScope.launch { result = underTest.removeFingerprint(fingerprintViewModelToRemove) }
testScope.launch {
result =
removeFingerprintsInteractorUnderTest.removeFingerprint(fingerprintViewModelToRemove)
}
runCurrent()
verify(fingerprintManager)
@@ -246,7 +326,7 @@ class FingerprintManagerInteractorTest {
testScope.runTest {
val fingerprintToRename = FingerprintData("Finger 2", 1, 2L)
underTest.renameFingerprint(fingerprintToRename, "Woo")
renameFingerprintsInteractorUnderTest.renameFingerprint(fingerprintToRename, "Woo")
verify(fingerprintManager).rename(eq(fingerprintToRename.fingerId), anyInt(), safeEq("Woo"))
}
@@ -257,7 +337,7 @@ class FingerprintManagerInteractorTest {
val fingerprint = Fingerprint("Woooo", 100, 101L)
var result: FingerprintAuthAttemptModel? = null
val job = launch { result = underTest.authenticate() }
val job = launch { result = authenticateInteractorImplUnderTest.authenticate() }
val authCallback: ArgumentCaptor<FingerprintManager.AuthenticationCallback> = argumentCaptor()
@@ -284,7 +364,7 @@ class FingerprintManagerInteractorTest {
fun testAuth_lockout() =
testScope.runTest {
var result: FingerprintAuthAttemptModel? = null
val job = launch { result = underTest.authenticate() }
val job = launch { result = authenticateInteractorImplUnderTest.authenticate() }
val authCallback: ArgumentCaptor<FingerprintManager.AuthenticationCallback> = argumentCaptor()
@@ -314,7 +394,7 @@ class FingerprintManagerInteractorTest {
val token = byteArrayOf(5, 3, 2)
var result: FingerEnrollState? = null
val job = launch {
underTest
enrollInteractorUnderTest
.enroll(token, EnrollReason.FindSensor, FingerprintEnrollOptions.Builder().build())
.collect { result = it }
}
@@ -343,7 +423,7 @@ class FingerprintManagerInteractorTest {
val token = byteArrayOf(5, 3, 2)
var result: FingerEnrollState? = null
val job = launch {
underTest
enrollInteractorUnderTest
.enroll(token, EnrollReason.FindSensor, FingerprintEnrollOptions.Builder().build())
.collect { result = it }
}
@@ -372,7 +452,7 @@ class FingerprintManagerInteractorTest {
val token = byteArrayOf(5, 3, 2)
var result: FingerEnrollState? = null
val job = launch {
underTest
enrollInteractorUnderTest
.enroll(token, EnrollReason.FindSensor, FingerprintEnrollOptions.Builder().build())
.collect { result = it }
}

View File

@@ -99,9 +99,10 @@ class FingerprintEnrollFindSensorViewModelV2Test {
backgroundViewModel.inForeground()
enrollViewModel =
FingerprintEnrollViewModel(
fakeFingerprintManagerInteractor,
gatekeeperViewModel,
navigationViewModel,
fakeFingerprintManagerInteractor,
fakeFingerprintManagerInteractor,
)
accessibilityInteractor =
object : AccessibilityInteractor {

View File

@@ -49,8 +49,7 @@ class RFPSIconTouchViewModelTest {
fun setup() {
Dispatchers.setMain(backgroundDispatcher)
testScope = TestScope(backgroundDispatcher)
rfpsIconTouchViewModel =
RFPSIconTouchViewModel()
rfpsIconTouchViewModel = RFPSIconTouchViewModel()
}
@After

View File

@@ -88,9 +88,10 @@ class FingerprintEnrollEnrollingViewModelTest {
backgroundViewModel.inForeground()
val fingerprintEnrollViewModel =
FingerprintEnrollViewModel(
fakeFingerprintManagerInteractor,
gateKeeperViewModel,
navigationViewModel,
fakeFingerprintManagerInteractor,
fakeFingerprintManagerInteractor,
)
enrollEnrollingViewModel =
FingerprintEnrollEnrollingViewModel(fingerprintEnrollViewModel, backgroundViewModel)

View File

@@ -67,10 +67,11 @@ class FingerprintSettingsNavigationViewModelTest {
underTest =
FingerprintSettingsNavigationViewModel.FingerprintSettingsNavigationModelFactory(
defaultUserId,
fakeFingerprintManagerInteractor,
backgroundDispatcher,
null,
null,
fakeFingerprintManagerInteractor,
fakeFingerprintManagerInteractor,
)
.create(FingerprintSettingsNavigationViewModel::class.java)
}
@@ -272,10 +273,11 @@ class FingerprintSettingsNavigationViewModelTest {
underTest =
FingerprintSettingsNavigationViewModel.FingerprintSettingsNavigationModelFactory(
defaultUserId,
fakeFingerprintManagerInteractor,
backgroundDispatcher,
token,
challenge,
fakeFingerprintManagerInteractor,
fakeFingerprintManagerInteractor,
)
.create(FingerprintSettingsNavigationViewModel::class.java)
@@ -299,10 +301,11 @@ class FingerprintSettingsNavigationViewModelTest {
underTest =
FingerprintSettingsNavigationViewModel.FingerprintSettingsNavigationModelFactory(
defaultUserId,
fakeFingerprintManagerInteractor,
backgroundDispatcher,
token,
challenge,
fakeFingerprintManagerInteractor,
fakeFingerprintManagerInteractor,
)
.create(FingerprintSettingsNavigationViewModel::class.java)
@@ -331,10 +334,11 @@ class FingerprintSettingsNavigationViewModelTest {
underTest =
FingerprintSettingsNavigationViewModel.FingerprintSettingsNavigationModelFactory(
defaultUserId,
fakeFingerprintManagerInteractor,
backgroundDispatcher,
token,
challenge,
fakeFingerprintManagerInteractor,
fakeFingerprintManagerInteractor,
)
.create(FingerprintSettingsNavigationViewModel::class.java)

View File

@@ -73,19 +73,25 @@ class FingerprintSettingsViewModelTest {
navigationViewModel =
FingerprintSettingsNavigationViewModel.FingerprintSettingsNavigationModelFactory(
defaultUserId,
fakeFingerprintManagerInteractor,
backgroundDispatcher,
null,
null,
fakeFingerprintManagerInteractor,
fakeFingerprintManagerInteractor,
)
.create(FingerprintSettingsNavigationViewModel::class.java)
underTest =
FingerprintSettingsViewModel.FingerprintSettingsViewModelFactory(
defaultUserId,
fakeFingerprintManagerInteractor,
backgroundDispatcher,
navigationViewModel,
fakeFingerprintManagerInteractor,
fakeFingerprintManagerInteractor,
fakeFingerprintManagerInteractor,
fakeFingerprintManagerInteractor,
fakeFingerprintManagerInteractor,
fakeFingerprintManagerInteractor,
)
.create(FingerprintSettingsViewModel::class.java)
}
@@ -114,14 +120,7 @@ class FingerprintSettingsViewModelTest {
fakeFingerprintManagerInteractor.enrolledFingerprintsInternal =
mutableListOf(FingerprintData("a", 1, 3L))
underTest =
FingerprintSettingsViewModel.FingerprintSettingsViewModelFactory(
defaultUserId,
fakeFingerprintManagerInteractor,
backgroundDispatcher,
navigationViewModel,
)
.create(FingerprintSettingsViewModel::class.java)
recreateSettingsViewModel()
var authAttempt: FingerprintAuthAttemptModel? = null
val job = launch { underTest.authFlow.take(5).collectLatest { authAttempt = it } }
@@ -156,14 +155,7 @@ class FingerprintSettingsViewModelTest {
fakeFingerprintManagerInteractor.enrolledFingerprintsInternal =
mutableListOf(FingerprintData("a", 1, 3L))
underTest =
FingerprintSettingsViewModel.FingerprintSettingsViewModelFactory(
defaultUserId,
fakeFingerprintManagerInteractor,
backgroundDispatcher,
navigationViewModel,
)
.create(FingerprintSettingsViewModel::class.java)
recreateSettingsViewModel()
var authAttempt: FingerprintAuthAttemptModel? = null
val job = launch { underTest.authFlow.take(5).collectLatest { authAttempt = it } }
@@ -198,14 +190,7 @@ class FingerprintSettingsViewModelTest {
val success = FingerprintAuthAttemptModel.Success(1)
fakeFingerprintManagerInteractor.authenticateAttempt = success
underTest =
FingerprintSettingsViewModel.FingerprintSettingsViewModelFactory(
defaultUserId,
fakeFingerprintManagerInteractor,
backgroundDispatcher,
navigationViewModel,
)
.create(FingerprintSettingsViewModel::class.java)
recreateSettingsViewModel()
var authAttempt: FingerprintAuthAttemptModel? = null
@@ -225,14 +210,7 @@ class FingerprintSettingsViewModelTest {
fakeFingerprintManagerInteractor.enrolledFingerprintsInternal =
mutableListOf(fingerprintToDelete)
underTest =
FingerprintSettingsViewModel.FingerprintSettingsViewModelFactory(
defaultUserId,
fakeFingerprintManagerInteractor,
backgroundDispatcher,
navigationViewModel,
)
.create(FingerprintSettingsViewModel::class.java)
recreateSettingsViewModel()
var dialog: PreferenceViewModel? = null
val dialogJob = launch { underTest.isShowingDialog.collect { dialog = it } }
@@ -261,14 +239,7 @@ class FingerprintSettingsViewModelTest {
fakeFingerprintManagerInteractor.enrolledFingerprintsInternal =
mutableListOf(fingerprintToRename)
underTest =
FingerprintSettingsViewModel.FingerprintSettingsViewModelFactory(
defaultUserId,
fakeFingerprintManagerInteractor,
backgroundDispatcher,
navigationViewModel,
)
.create(FingerprintSettingsViewModel::class.java)
recreateSettingsViewModel()
var dialog: PreferenceViewModel? = null
val dialogJob = launch { underTest.isShowingDialog.collect { dialog = it } }
@@ -299,14 +270,7 @@ class FingerprintSettingsViewModelTest {
fakeFingerprintManagerInteractor.enrolledFingerprintsInternal =
mutableListOf(fingerprintToDelete)
underTest =
FingerprintSettingsViewModel.FingerprintSettingsViewModelFactory(
defaultUserId,
fakeFingerprintManagerInteractor,
backgroundDispatcher,
navigationViewModel,
)
.create(FingerprintSettingsViewModel::class.java)
recreateSettingsViewModel()
var dialog: PreferenceViewModel? = null
val dialogJob = launch { underTest.isShowingDialog.collect { dialog = it } }
@@ -390,6 +354,22 @@ class FingerprintSettingsViewModelTest {
assertThat(authAttempt).isEqualTo(null)
}
private fun recreateSettingsViewModel() {
underTest =
FingerprintSettingsViewModel.FingerprintSettingsViewModelFactory(
defaultUserId,
backgroundDispatcher,
navigationViewModel,
fakeFingerprintManagerInteractor,
fakeFingerprintManagerInteractor,
fakeFingerprintManagerInteractor,
fakeFingerprintManagerInteractor,
fakeFingerprintManagerInteractor,
fakeFingerprintManagerInteractor,
)
.create(FingerprintSettingsViewModel::class.java)
}
private fun setupAuth(): MutableList<FingerprintData> {
fakeFingerprintManagerInteractor.sensorProp =
FingerprintSensorPropertiesInternal(
@@ -409,14 +389,7 @@ class FingerprintSettingsViewModelTest {
val success = FingerprintAuthAttemptModel.Success(1)
fakeFingerprintManagerInteractor.authenticateAttempt = success
underTest =
FingerprintSettingsViewModel.FingerprintSettingsViewModelFactory(
defaultUserId,
fakeFingerprintManagerInteractor,
backgroundDispatcher,
navigationViewModel,
)
.create(FingerprintSettingsViewModel::class.java)
recreateSettingsViewModel()
return fingerprints
}