Moved FakeFingerprintManagerInteractor

Test: atest
Bug: 295206367
Change-Id: If9f3b9dc88260c0725f70d3444c4f9a4b6ee5c2f
This commit is contained in:
Joshua McCloskey
2023-09-08 16:04:25 +00:00
parent cb624a3155
commit 1ec202576d
28 changed files with 366 additions and 223 deletions

View File

@@ -55,6 +55,9 @@ android_library {
],
srcs: ["src/**/*.java", "src/**/*.kt"],
exclude_srcs: [
"src/com/android/settings/biometrics/fingerprint2/shared/**/*.kt",
],
use_resource_processor: true,
resource_dirs: [
"res",
@@ -113,6 +116,7 @@ android_library {
"SystemUIUnfoldLib",
"aconfig_settings_flags_lib",
"android.content.pm.flags-aconfig-java",
"FingerprintManagerInteractor",
],
plugins: [

View File

@@ -0,0 +1,62 @@
/*
* 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.conversion
import android.hardware.fingerprint.FingerprintManager
import android.hardware.fingerprint.FingerprintSensorProperties
import android.hardware.fingerprint.FingerprintSensorPropertiesInternal
import com.android.settings.biometrics.fingerprint2.shared.model.EnrollReason
import com.android.settings.biometrics.fingerprint2.shared.model.FingerprintSensorPropertyViewModel
import com.android.settings.biometrics.fingerprint2.shared.model.SensorStrength
import com.android.settings.biometrics.fingerprint2.shared.model.SensorType
class Util {
companion object {
fun sensorPropsToViewModel(
props: FingerprintSensorPropertiesInternal
): FingerprintSensorPropertyViewModel {
val sensorStrength: SensorStrength =
when (props.sensorStrength) {
FingerprintSensorProperties.STRENGTH_CONVENIENCE -> SensorStrength.Convenient
FingerprintSensorProperties.STRENGTH_WEAK -> SensorStrength.Weak
FingerprintSensorProperties.STRENGTH_STRONG -> SensorStrength.Strong
else -> SensorStrength.Unknown
}
val sensorType: SensorType =
when (props.sensorType) {
FingerprintSensorProperties.TYPE_UDFPS_OPTICAL -> SensorType.Optical
FingerprintSensorProperties.TYPE_UDFPS_ULTRASONIC -> SensorType.Ultrasonic
FingerprintSensorProperties.TYPE_REAR -> SensorType.RFPS
FingerprintSensorProperties.TYPE_POWER_BUTTON -> SensorType.SFPS
else -> SensorType.Unknown
}
return FingerprintSensorPropertyViewModel(
props.sensorId,
sensorStrength,
props.maxEnrollmentsPerUser,
sensorType
)
}
}
}
fun EnrollReason.toOriginalReason(): Int {
return when (this) {
EnrollReason.EnrollEnrolling -> FingerprintManager.ENROLL_ENROLL
EnrollReason.FindSensor -> FingerprintManager.ENROLL_FIND_SENSOR
}
}

View File

@@ -21,15 +21,16 @@ import android.content.Intent
import android.hardware.fingerprint.FingerprintManager
import android.hardware.fingerprint.FingerprintManager.GenerateChallengeCallback
import android.hardware.fingerprint.FingerprintManager.RemovalCallback
import android.hardware.fingerprint.FingerprintSensorPropertiesInternal
import android.os.CancellationSignal
import android.util.Log
import com.android.settings.biometrics.GatekeeperPasswordProvider
import com.android.settings.biometrics.fingerprint2.conversion.Util
import com.android.settings.biometrics.fingerprint2.conversion.toOriginalReason
import com.android.settings.biometrics.fingerprint2.shared.domain.interactor.FingerprintManagerInteractor
import com.android.settings.biometrics.fingerprint2.shared.model.EnrollReason
import com.android.settings.biometrics.fingerprint2.shared.model.FingerprintAuthAttemptViewModel
import com.android.settings.biometrics.fingerprint2.shared.model.FingerprintViewModel
import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.EnrollReason
import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.FingerEnrollStateViewModel
import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.toOriginalReason
import com.android.settings.biometrics.fingerprint2.shared.model.FingerEnrollStateViewModel
import com.android.settings.password.ChooseLockSettingsHelper
import kotlin.coroutines.resume
import kotlin.coroutines.suspendCoroutine
@@ -45,59 +46,6 @@ import kotlinx.coroutines.withContext
private const val TAG = "FingerprintManagerInteractor"
/** Encapsulates business logic related to managing fingerprints. */
interface FingerprintManagerInteractor {
/** Returns the list of current fingerprints. */
val enrolledFingerprints: Flow<List<FingerprintViewModel>>
/** 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<FingerprintSensorPropertiesInternal?>
/** Runs [FingerprintManager.authenticate] */
suspend fun authenticate(): FingerprintAuthAttemptViewModel
/**
* 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
* [FingerprintManager.enroll]
*
* @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. Returning the [FingerEnrollStateViewModel] that represents this fingerprint
* enrollment state.
*/
suspend fun enroll(
hardwareAuthToken: ByteArray?,
enrollReason: EnrollReason,
): Flow<FingerEnrollStateViewModel>
/**
* Removes the given fingerprint, returning true if it was successfully removed and false
* otherwise
*/
suspend fun removeFingerprint(fp: FingerprintViewModel): Boolean
/** Renames the given fingerprint if one exists */
suspend fun renameFingerprint(fp: FingerprintViewModel, newName: String)
/** Indicates if the device has side fingerprint */
suspend fun hasSideFps(): Boolean
/** Indicates if the press to auth feature has been enabled */
suspend fun pressToAuthEnabled(): Boolean
}
class FingerprintManagerInteractorImpl(
applicationContext: Context,
private val backgroundDispatcher: CoroutineDispatcher,
@@ -144,7 +92,10 @@ class FingerprintManagerInteractorImpl(
override val sensorPropertiesInternal = flow {
val sensorPropertiesInternal = fingerprintManager.sensorPropertiesInternal
emit(if (sensorPropertiesInternal.isEmpty()) null else sensorPropertiesInternal.first())
emit(
if (sensorPropertiesInternal.isEmpty()) null
else Util.sensorPropsToViewModel(sensorPropertiesInternal.first())
)
}
override val maxEnrollableFingerprints = flow { emit(maxFingerprints) }
@@ -183,7 +134,7 @@ class FingerprintManagerInteractorImpl(
cancellationSignal,
applicationContext.userId,
enrollmentCallback,
enrollReason.toOriginalReason()
enrollReason.toOriginalReason(),
)
awaitClose {
// If the stream has not been ended, and the user has stopped collecting the flow

View File

@@ -0,0 +1,13 @@
// This library is mainly used for fakes which will be shared across are various types of tests
// unit/robo/screenshot etc.
//
// This library shouldn't have many dependencies.
android_library {
name: "FingerprintManagerInteractor",
srcs: [
"**/*.kt"
],
static_libs: [
"kotlinx-coroutines-android",
],
}

View File

@@ -0,0 +1,18 @@
<!--
~ 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.
-->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.android.settings.biometrics.fingerprint2.shared">
</manifest>

View File

@@ -0,0 +1,82 @@
/*
* 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.shared.domain.interactor
import com.android.settings.biometrics.fingerprint2.shared.model.EnrollReason
import com.android.settings.biometrics.fingerprint2.shared.model.FingerprintAuthAttemptViewModel
import com.android.settings.biometrics.fingerprint2.shared.model.FingerprintSensorPropertyViewModel
import com.android.settings.biometrics.fingerprint2.shared.model.FingerprintViewModel
import com.android.settings.biometrics.fingerprint2.shared.model.FingerEnrollStateViewModel
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<FingerprintViewModel>>
/** 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<FingerprintSensorPropertyViewModel?>
/** Runs the authenticate flow */
suspend fun authenticate(): FingerprintAuthAttemptViewModel
/**
* 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. Returning the [FingerEnrollStateViewModel] that represents this fingerprint
* enrollment state.
*/
suspend fun enroll(
hardwareAuthToken: ByteArray?,
enrollReason: EnrollReason,
): Flow<FingerEnrollStateViewModel>
/**
* Removes the given fingerprint, returning true if it was successfully removed and false
* otherwise
*/
suspend fun removeFingerprint(fp: FingerprintViewModel): Boolean
/** Renames the given fingerprint if one exists */
suspend fun renameFingerprint(fp: FingerprintViewModel, newName: String)
/** Indicates if the device has side fingerprint */
suspend fun hasSideFps(): Boolean
/** Indicates if the press to auth feature has been enabled */
suspend fun pressToAuthEnabled(): Boolean
}

View File

@@ -13,24 +13,16 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel
import android.hardware.fingerprint.FingerprintManager
package com.android.settings.biometrics.fingerprint2.shared.model
/**
* The reason for enrollment. Represents [FingerprintManager.EnrollReason]
*/
/** The reason for enrollment */
enum class EnrollReason {
/** The enroll happens on education screen. */
/**
* The enroll happens on education screen. This is to support legacy flows where we require the
* user to touch the sensor before going ahead to the EnrollEnrolling flow
*/
FindSensor,
/** The enroll happens on enrolling screen. */
EnrollEnrolling
}
/** Convert EnrollReason to original [FingerprintManager.EnrollReason]. */
fun EnrollReason.toOriginalReason(): Int {
return when (this) {
EnrollReason.EnrollEnrolling -> FingerprintManager.ENROLL_ENROLL
EnrollReason.FindSensor -> FingerprintManager.ENROLL_FIND_SENSOR
}
}

View File

@@ -14,7 +14,7 @@
* limitations under the License.
*/
package com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel
package com.android.settings.biometrics.fingerprint2.shared.model
import android.annotation.StringRes

View File

@@ -32,3 +32,36 @@ sealed class FingerprintAuthAttemptViewModel {
val message: String,
) : FingerprintAuthAttemptViewModel()
}
/** The various types of fingerprint sensors */
sealed class SensorType {
/** Rear fingerprint sensor */
data object RFPS : SensorType()
/** Optical under display sensor */
data object Optical : SensorType()
/** Ultrasonic under display sensor */
data object Ultrasonic : SensorType()
/** Side fingerprint sensor */
data object SFPS : SensorType()
/** Unkonwn fingerprint sensor */
data object Unknown : SensorType()
}
/** The strength of a given sensor */
sealed class SensorStrength {
data object Convenient : SensorStrength()
data object Weak : SensorStrength()
data object Strong : SensorStrength()
data object Unknown : SensorStrength()
}
data class FingerprintSensorPropertyViewModel(
val sensorId: Int,
val sensorStrength: SensorStrength,
val maxEnrollmentsPerUser: Int,
val sensorType: SensorType
)

View File

@@ -29,6 +29,7 @@ import com.airbnb.lottie.LottieAnimationView
import com.android.settings.R
import com.android.settings.biometrics.fingerprint.FingerprintErrorDialog
import com.android.settings.biometrics.fingerprint.FingerprintFindSensorAnimation
import com.android.settings.biometrics.fingerprint2.shared.model.SensorType
import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.FingerprintEnrollFindSensorViewModel
import com.android.systemui.biometrics.shared.model.FingerprintSensorType
import com.google.android.setupcompat.template.FooterBarMixin
@@ -65,9 +66,9 @@ class FingerprintEnrollFindSensorV2Fragment : Fragment() {
viewModel.sensorType.collect {
contentLayoutId =
when (it) {
FingerprintSensorType.UDFPS_OPTICAL,
FingerprintSensorType.UDFPS_ULTRASONIC -> R.layout.udfps_enroll_find_sensor_layout
FingerprintSensorType.POWER_BUTTON -> R.layout.sfps_enroll_find_sensor_layout
SensorType.Optical,
SensorType.Ultrasonic -> R.layout.udfps_enroll_find_sensor_layout
SensorType.SFPS -> R.layout.sfps_enroll_find_sensor_layout
else -> R.layout.fingerprint_v2_enroll_find_sensor
}
}
@@ -170,14 +171,14 @@ class FingerprintEnrollFindSensorV2Fragment : Fragment() {
illustrationLottie?.visibility = View.VISIBLE
}
private fun setTexts(sensorType: FingerprintSensorType, view: GlifLayout) {
private fun setTexts(sensorType: SensorType, view: GlifLayout) {
when (sensorType) {
FingerprintSensorType.UDFPS_OPTICAL,
FingerprintSensorType.UDFPS_ULTRASONIC -> {
SensorType.Optical,
SensorType.Ultrasonic -> {
view.setHeaderText(R.string.security_settings_udfps_enroll_find_sensor_title)
view.setDescriptionText(R.string.security_settings_udfps_enroll_find_sensor_message)
}
FingerprintSensorType.POWER_BUTTON -> {
SensorType.SFPS -> {
view.setHeaderText(R.string.security_settings_sfps_enroll_find_sensor_title)
view.setDescriptionText(R.string.security_settings_sfps_enroll_find_sensor_message)
}

View File

@@ -35,10 +35,10 @@ import androidx.lifecycle.lifecycleScope
import com.android.settings.R
import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.FingerprintEnrollNavigationViewModel
import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.FingerprintEnrollViewModel
import com.android.settings.biometrics.fingerprint2.shared.model.SensorType
import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.FingerprintGatekeeperViewModel
import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.FingerprintScrollViewModel
import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.Unicorn
import com.android.systemui.biometrics.shared.model.FingerprintSensorType
import com.google.android.setupcompat.template.FooterBarMixin
import com.google.android.setupcompat.template.FooterButton
import com.google.android.setupdesign.GlifLayout
@@ -144,8 +144,8 @@ class FingerprintEnrollIntroV2Fragment : Fragment(R.layout.fingerprint_v2_enroll
val iconShield: ImageView = view.requireViewById(R.id.icon_shield)
val footerMessage6: TextView = view.requireViewById(R.id.footer_message_6)
when (sensorType) {
FingerprintSensorType.UDFPS_ULTRASONIC,
FingerprintSensorType.UDFPS_OPTICAL -> {
SensorType.Ultrasonic,
SensorType.Optical -> {
footerMessage6.visibility = View.VISIBLE
iconShield.visibility = View.VISIBLE
}

View File

@@ -20,6 +20,9 @@ import android.hardware.fingerprint.FingerprintManager
import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.viewModelScope
import com.android.settings.biometrics.fingerprint2.shared.model.EnrollReason
import com.android.settings.biometrics.fingerprint2.shared.model.FingerEnrollStateViewModel
import com.android.settings.biometrics.fingerprint2.shared.model.SensorType
import com.android.systemui.biometrics.shared.model.FingerprintSensorType
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
@@ -42,13 +45,13 @@ class FingerprintEnrollFindSensorViewModel(
orientationStateViewModel: OrientationStateViewModel
) : ViewModel() {
/** Represents the stream of sensor type. */
val sensorType: Flow<FingerprintSensorType> =
val sensorType: Flow<SensorType> =
fingerprintEnrollViewModel.sensorType.filterWhenEducationIsShown()
private val _isUdfps: Flow<Boolean> =
sensorType.map {
it == FingerprintSensorType.UDFPS_OPTICAL || it == FingerprintSensorType.UDFPS_ULTRASONIC
it == SensorType.Optical || it == SensorType.Ultrasonic
}
private val _isSfps: Flow<Boolean> = sensorType.map { it == FingerprintSensorType.POWER_BUTTON }
private val _isSfps: Flow<Boolean> = sensorType.map { it == SensorType.RFPS }
private val _isRearSfps: Flow<Boolean> =
combineTransform(_isSfps, _isUdfps) { v1, v2 -> !v1 && !v2 }
@@ -92,8 +95,8 @@ class FingerprintEnrollFindSensorViewModel(
) { sensorType, hasValidGatekeeperInfo, gatekeeperInfo, navigationViewModel ->
val shouldStartEnroll =
navigationViewModel.currStep == Education &&
sensorType != FingerprintSensorType.UDFPS_OPTICAL &&
sensorType != FingerprintSensorType.UDFPS_ULTRASONIC &&
sensorType != SensorType.Optical &&
sensorType != SensorType.Ultrasonic &&
hasValidGatekeeperInfo
if (shouldStartEnroll) (gatekeeperInfo as GatekeeperInfo.GatekeeperPasswordInfo).token
else null

View File

@@ -17,13 +17,14 @@ package com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel
import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
import com.android.settings.biometrics.fingerprint2.domain.interactor.FingerprintManagerInteractor
import com.android.settings.biometrics.fingerprint2.shared.domain.interactor.FingerprintManagerInteractor
import com.android.settings.biometrics.fingerprint2.shared.model.EnrollReason
import com.android.settings.biometrics.fingerprint2.shared.model.FingerEnrollStateViewModel
import com.android.settings.biometrics.fingerprint2.shared.model.SensorType
import com.android.systemui.biometrics.shared.model.FingerprintSensorType
import com.android.systemui.biometrics.shared.model.toSensorType
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.filterNotNull
import kotlinx.coroutines.flow.flowOn
@@ -39,17 +40,15 @@ class FingerprintEnrollViewModel(
backgroundDispatcher: CoroutineDispatcher,
) : ViewModel() {
/** Represents the stream of [FingerprintSensorType] */
val sensorType: Flow<FingerprintSensorType> =
fingerprintManagerInteractor.sensorPropertiesInternal.filterNotNull().map {
it.sensorType.toSensorType()
}
private var _enrollReason: MutableStateFlow<EnrollReason> =
MutableStateFlow(EnrollReason.FindSensor)
private var _hardwareAuthToken: MutableStateFlow<ByteArray?> = MutableStateFlow(null)
private var _consumerShouldEnroll: MutableStateFlow<Boolean> = MutableStateFlow(false)
/** Represents the stream of [FingerprintSensorType] */
val sensorType: Flow<SensorType> =
fingerprintManagerInteractor.sensorPropertiesInternal.filterNotNull().map { it.sensorType }
/**
* A flow that contains a [FingerprintEnrollViewModel] which contains the relevant information for
* an enrollment process

View File

@@ -20,7 +20,7 @@ import android.util.Log
import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.viewModelScope
import com.android.settings.biometrics.fingerprint2.domain.interactor.FingerprintManagerInteractor
import com.android.settings.biometrics.fingerprint2.shared.domain.interactor.FingerprintManagerInteractor
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
@@ -39,7 +39,7 @@ interface Validator {
/**
* The [EnrollType] for fingerprint enrollment indicates information on how the flow should behave.
*/
sealed class EnrollType()
sealed class EnrollType
/** The default enrollment experience, typically called from Settings */
object Default : EnrollType()

View File

@@ -21,7 +21,7 @@ import android.util.Log
import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.viewModelScope
import com.android.settings.biometrics.fingerprint2.domain.interactor.FingerprintManagerInteractor
import com.android.settings.biometrics.fingerprint2.shared.domain.interactor.FingerprintManagerInteractor
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow

View File

@@ -27,6 +27,7 @@ import kotlinx.coroutines.flow.update
class FingerprintScrollViewModel : ViewModel() {
private val _hasReadConsentScreen: MutableStateFlow<Boolean> = MutableStateFlow(false)
/** Indicates if a user has consented to FingerprintEnrollment */
val hasReadConsentScreen: Flow<Boolean> = _hasReadConsentScreen.asStateFlow()
@@ -35,7 +36,7 @@ class FingerprintScrollViewModel : ViewModel() {
_hasReadConsentScreen.update { true }
}
class FingerprintScrollViewModelFactory() : ViewModelProvider.Factory {
class FingerprintScrollViewModelFactory : ViewModelProvider.Factory {
@Suppress("UNCHECKED_CAST")
override fun <T : ViewModel> create(

View File

@@ -34,7 +34,6 @@ import com.android.settings.biometrics.fingerprint2.ui.settings.viewmodel.Prefer
import com.android.settings.biometrics.fingerprint2.ui.settings.viewmodel.ShowSettings
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.flow.filterNotNull
import kotlinx.coroutines.launch
@@ -55,7 +54,6 @@ object FingerprintSettingsViewBinder {
challenge: Long?,
challengeToken: ByteArray?
)
/** Helper to launch an add fingerprint request */
fun launchAddFingerprint(userId: Int, challengeToken: ByteArray?)
/**
@@ -63,10 +61,8 @@ object FingerprintSettingsViewBinder {
* choose a PIN/PATTERN/PASS.
*/
fun launchConfirmOrChooseLock(userId: Int)
/** Used to indicate that FingerprintSettings is finished. */
fun finish()
/** Indicates what result should be set for the returning callee */
fun setResultExternal(resultCode: Int)
/** Indicates the settings UI should be shown */

View File

@@ -106,8 +106,9 @@ class FingerprintSettingsRenameDialog : InstrumentedDialogFragment() {
val dialog = FingerprintSettingsRenameDialog()
val onClick =
DialogInterface.OnClickListener { _, _ ->
val dialogTextField = dialog.requireDialog()
.requireViewById(R.id.fingerprint_rename_field) as ImeAwareEditText
val dialogTextField =
dialog.requireDialog().requireViewById(R.id.fingerprint_rename_field)
as ImeAwareEditText
val newName = dialogTextField.text.toString()
if (!TextUtils.equals(newName, fp.name)) {
Log.d(TAG, "rename $fp.name to $newName for $dialog")

View File

@@ -21,7 +21,7 @@ import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.viewModelScope
import com.android.settings.biometrics.BiometricEnrollBase
import com.android.settings.biometrics.fingerprint2.domain.interactor.FingerprintManagerInteractor
import com.android.settings.biometrics.fingerprint2.shared.domain.interactor.FingerprintManagerInteractor
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
@@ -32,17 +32,18 @@ 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 userId: Int,
private val fingerprintManagerInteractor: FingerprintManagerInteractor,
private val backgroundDispatcher: CoroutineDispatcher,
tokenInit: ByteArray?,
challengeInit: Long?,
) : ViewModel() {
private var token = tokenInit
private var challenge = challengeInit
private val _nextStep: MutableStateFlow<NextStepViewModel?> = MutableStateFlow(null)
/** This flow represents the high level state for the FingerprintSettingsV2Fragment. */
val nextStep: StateFlow<NextStepViewModel?> = _nextStep.asStateFlow()
@@ -118,8 +119,8 @@ class FingerprintSettingsNavigationViewModel(
launchFinishSettings("Error, empty keyChallenge")
return
}
token = theToken!!
challenge = theChallenge!!
token = theToken
challenge = theChallenge
showSettingsHelper()
}
@@ -170,12 +171,13 @@ class FingerprintSettingsNavigationViewModel(
private fun launchFinishSettings(reason: String, errorCode: Int) {
_nextStep.update { FinishSettingsWithResult(errorCode, reason) }
}
class FingerprintSettingsNavigationModelFactory(
private val userId: Int,
private val interactor: FingerprintManagerInteractor,
private val backgroundDispatcher: CoroutineDispatcher,
private val token: ByteArray?,
private val challenge: Long?,
private val userId: Int,
private val interactor: FingerprintManagerInteractor,
private val backgroundDispatcher: CoroutineDispatcher,
private val token: ByteArray?,
private val challenge: Long?,
) : ViewModelProvider.Factory {
@Suppress("UNCHECKED_CAST")

View File

@@ -21,11 +21,10 @@ import android.util.Log
import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.viewModelScope
import com.android.settings.biometrics.fingerprint2.domain.interactor.FingerprintManagerInteractor
import com.android.settings.biometrics.fingerprint2.shared.domain.interactor.FingerprintManagerInteractor
import com.android.settings.biometrics.fingerprint2.shared.model.FingerprintAuthAttemptViewModel
import com.android.settings.biometrics.fingerprint2.shared.model.FingerprintViewModel
import com.android.systemui.biometrics.shared.model.FingerprintSensorType
import com.android.systemui.biometrics.shared.model.toSensorType
import com.android.settings.biometrics.fingerprint2.shared.model.SensorType
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableSharedFlow
@@ -90,10 +89,8 @@ class FingerprintSettingsViewModel(
private val _consumerShouldAuthenticate: MutableStateFlow<Boolean> = MutableStateFlow(false)
private val _fingerprintSensorType: Flow<FingerprintSensorType> =
fingerprintManagerInteractor.sensorPropertiesInternal.filterNotNull().map {
it.sensorType.toSensorType()
}
private val _fingerprintSensorType: Flow<SensorType> =
fingerprintManagerInteractor.sensorPropertiesInternal.filterNotNull().map { it.sensorType }
private val _sensorNullOrEmpty: Flow<Boolean> =
fingerprintManagerInteractor.sensorPropertiesInternal.map { it == null }
@@ -149,10 +146,7 @@ class FingerprintSettingsViewModel(
if (sensorNullOrEmpty) {
return@combine false
}
if (
listOf(FingerprintSensorType.UDFPS_ULTRASONIC, FingerprintSensorType.UDFPS_OPTICAL)
.contains(sensorType)
) {
if (listOf(SensorType.Ultrasonic, SensorType.Optical).contains(sensorType)) {
return@combine false
}

View File

@@ -61,6 +61,7 @@ android_robolectric_test {
"flag-junit",
"aconfig_settings_flags_lib",
"platform-test-annotations",
"Settings-testutils2",
],
libs: [

9
tests/shared/Android.bp Normal file
View File

@@ -0,0 +1,9 @@
android_library {
name: "Settings-testutils2",
srcs: [
"src/**/*.kt"
],
libs: [
"FingerprintManagerInteractor",
],
}

View File

@@ -0,0 +1,20 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright (C) 2022 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.
-->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.android.settings.testutils2">
</manifest>

View File

@@ -14,18 +14,19 @@
* limitations under the License.
*/
package com.android.settings.fingerprint2.domain.interactor
package com.android.settings.testutils2
import android.hardware.biometrics.SensorProperties
import android.hardware.fingerprint.FingerprintSensorProperties.TYPE_POWER_BUTTON
import android.hardware.fingerprint.FingerprintSensorPropertiesInternal
import com.android.settings.biometrics.fingerprint2.domain.interactor.FingerprintManagerInteractor
import com.android.settings.biometrics.fingerprint2.shared.domain.interactor.FingerprintManagerInteractor
import com.android.settings.biometrics.fingerprint2.shared.model.FingerprintAuthAttemptViewModel
import com.android.settings.biometrics.fingerprint2.shared.model.FingerprintSensorPropertyViewModel
import com.android.settings.biometrics.fingerprint2.shared.model.FingerprintViewModel
import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.EnrollReason
import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.FingerEnrollStateViewModel
import com.android.settings.biometrics.fingerprint2.shared.model.EnrollReason
import com.android.settings.biometrics.fingerprint2.shared.model.FingerEnrollStateViewModel
import com.android.settings.biometrics.fingerprint2.shared.model.SensorStrength
import com.android.settings.biometrics.fingerprint2.shared.model.SensorType
import kotlinx.coroutines.flow.Flow
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 {
@@ -37,17 +38,8 @@ class FakeFingerprintManagerInteractor : FingerprintManagerInteractor {
val enrollStateViewModel = FingerEnrollStateViewModel.EnrollProgress(1)
var pressToAuthEnabled = true
var sensorProps =
listOf(
FingerprintSensorPropertiesInternal(
0 /* sensorId */,
SensorProperties.STRENGTH_STRONG,
5 /* maxEnrollmentsPerUser */,
emptyList() /* ComponentInfoInternal */,
TYPE_POWER_BUTTON,
true /* resetLockoutRequiresHardwareAuthToken */
)
)
var sensorProp =
FingerprintSensorPropertyViewModel(0 /* sensorId */, SensorStrength.Strong, 5, SensorType.SFPS)
override suspend fun authenticate(): FingerprintAuthAttemptViewModel {
return authenticateAttempt
@@ -65,8 +57,8 @@ class FakeFingerprintManagerInteractor : FingerprintManagerInteractor {
emit(enrolledFingerprintsInternal.size < enrollableFingerprints)
}
override val sensorPropertiesInternal: Flow<FingerprintSensorPropertiesInternal?> = flow {
emit(sensorProps.first())
override val sensorPropertiesInternal: Flow<FingerprintSensorPropertyViewModel?> = flow {
emit(sensorProp)
}
override val maxEnrollableFingerprints: Flow<Int> = flow { emit(enrollableFingerprints) }
@@ -74,7 +66,7 @@ class FakeFingerprintManagerInteractor : FingerprintManagerInteractor {
override suspend fun enroll(
hardwareAuthToken: ByteArray?,
enrollReason: EnrollReason
): Flow<FingerEnrollStateViewModel> = flow { emit(enrollStateViewModel) }
): Flow<FingerEnrollStateViewModel> = flowOf(enrollStateViewModel)
override suspend fun removeFingerprint(fp: FingerprintViewModel): Boolean {
return enrolledFingerprintsInternal.remove(fp)
@@ -87,7 +79,7 @@ class FakeFingerprintManagerInteractor : FingerprintManagerInteractor {
}
override suspend fun hasSideFps(): Boolean {
return sensorProps.any { it.isAnySidefpsType }
return sensorProp.sensorType == SensorType.SFPS
}
override suspend fun pressToAuthEnabled(): Boolean {

View File

@@ -28,6 +28,7 @@ android_test {
"truth-prebuilt",
"kotlinx_coroutines_test",
"flag-junit",
"Settings-testutils2",
// Don't add SettingsLib libraries here - you can use them directly as they are in the
// instrumented Settings app.
],

View File

@@ -26,12 +26,12 @@ 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.domain.interactor.FingerprintManagerInteractor
import com.android.settings.biometrics.fingerprint2.shared.domain.interactor.FingerprintManagerInteractor
import com.android.settings.biometrics.fingerprint2.domain.interactor.FingerprintManagerInteractorImpl
import com.android.settings.biometrics.fingerprint2.shared.model.EnrollReason
import com.android.settings.biometrics.fingerprint2.shared.model.FingerprintAuthAttemptViewModel
import com.android.settings.biometrics.fingerprint2.shared.model.FingerprintViewModel
import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.EnrollReason.FindSensor
import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.FingerEnrollStateViewModel
import com.android.settings.biometrics.fingerprint2.shared.model.FingerEnrollStateViewModel
import com.android.settings.password.ChooseLockSettingsHelper
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.cancelAndJoin
@@ -283,7 +283,7 @@ class FingerprintManagerInteractorTest {
testScope.runTest {
val token = byteArrayOf(5, 3, 2)
var result: FingerEnrollStateViewModel? = null
val job = launch { underTest.enroll(token, FindSensor).collect { result = it } }
val job = launch { underTest.enroll(token, EnrollReason.FindSensor).collect { result = it } }
val enrollCallback: ArgumentCaptor<FingerprintManager.EnrollmentCallback> = argumentCaptor()
runCurrent()
@@ -307,7 +307,7 @@ class FingerprintManagerInteractorTest {
testScope.runTest {
val token = byteArrayOf(5, 3, 2)
var result: FingerEnrollStateViewModel? = null
val job = launch { underTest.enroll(token, FindSensor).collect { result = it } }
val job = launch { underTest.enroll(token, EnrollReason.FindSensor).collect { result = it } }
val enrollCallback: ArgumentCaptor<FingerprintManager.EnrollmentCallback> = argumentCaptor()
runCurrent()
@@ -331,7 +331,7 @@ class FingerprintManagerInteractorTest {
testScope.runTest {
val token = byteArrayOf(5, 3, 2)
var result: FingerEnrollStateViewModel? = null
val job = launch { underTest.enroll(token, FindSensor).collect { result = it } }
val job = launch { underTest.enroll(token, EnrollReason.FindSensor).collect { result = it } }
val enrollCallback: ArgumentCaptor<FingerprintManager.EnrollmentCallback> = argumentCaptor()
runCurrent()

View File

@@ -14,7 +14,7 @@
* limitations under the License.
*/
package com.android.settings.fingerprint2.settings.viewmodel
package com.android.settings.fingerprint2.ui.settings
import androidx.arch.core.executor.testing.InstantTaskExecutorRule
import com.android.settings.biometrics.BiometricEnrollBase
@@ -26,7 +26,7 @@ import com.android.settings.biometrics.fingerprint2.ui.settings.viewmodel.Finish
import com.android.settings.biometrics.fingerprint2.ui.settings.viewmodel.LaunchConfirmDeviceCredential
import com.android.settings.biometrics.fingerprint2.ui.settings.viewmodel.NextStepViewModel
import com.android.settings.biometrics.fingerprint2.ui.settings.viewmodel.ShowSettings
import com.android.settings.fingerprint2.domain.interactor.FakeFingerprintManagerInteractor
import com.android.settings.testutils2.FakeFingerprintManagerInteractor
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
@@ -132,22 +132,6 @@ class FingerprintSettingsNavigationViewModelTest {
job.cancel()
}
@Test
fun firstEnrollment_fails() =
testScope.runTest {
fakeFingerprintManagerInteractor.enrolledFingerprintsInternal = mutableListOf()
var nextStep: NextStepViewModel? = null
val job = launch { underTest.nextStep.collect { nextStep = it } }
underTest.onConfirmDevice(true, 10L)
underTest.onEnrollFirstFailure("We failed!!")
runCurrent()
assertThat(nextStep).isInstanceOf(FinishSettings::class.java)
job.cancel()
}
@Test
fun firstEnrollment_failsWithReason() =
testScope.runTest {

View File

@@ -14,18 +14,18 @@
* limitations under the License.
*/
package com.android.settings.fingerprint2.settings.viewmodel
package com.android.settings.fingerprint2.ui.settings
import android.hardware.biometrics.SensorProperties
import android.hardware.fingerprint.FingerprintSensorProperties
import android.hardware.fingerprint.FingerprintSensorPropertiesInternal
import androidx.arch.core.executor.testing.InstantTaskExecutorRule
import com.android.settings.biometrics.fingerprint2.shared.model.FingerprintAuthAttemptViewModel
import com.android.settings.biometrics.fingerprint2.shared.model.FingerprintSensorPropertyViewModel
import com.android.settings.biometrics.fingerprint2.shared.model.FingerprintViewModel
import com.android.settings.biometrics.fingerprint2.shared.model.SensorStrength
import com.android.settings.biometrics.fingerprint2.shared.model.SensorType
import com.android.settings.biometrics.fingerprint2.ui.settings.viewmodel.FingerprintSettingsNavigationViewModel
import com.android.settings.biometrics.fingerprint2.ui.settings.viewmodel.FingerprintSettingsViewModel
import com.android.settings.biometrics.fingerprint2.ui.settings.viewmodel.PreferenceViewModel
import com.android.settings.fingerprint2.domain.interactor.FakeFingerprintManagerInteractor
import com.android.settings.testutils2.FakeFingerprintManagerInteractor
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.collectLatest
@@ -95,16 +95,12 @@ class FingerprintSettingsViewModelTest {
@Test
fun authenticate_DoesNotRun_ifOptical() =
testScope.runTest {
fakeFingerprintManagerInteractor.sensorProps =
listOf(
FingerprintSensorPropertiesInternal(
0 /* sensorId */,
SensorProperties.STRENGTH_STRONG,
5 /* maxEnrollmentsPerUser */,
emptyList() /* ComponentInfoInternal */,
FingerprintSensorProperties.TYPE_UDFPS_OPTICAL,
true /* resetLockoutRequiresHardwareAuthToken */
)
fakeFingerprintManagerInteractor.sensorProp =
FingerprintSensorPropertyViewModel(
0 /* sensorId */,
SensorStrength.Strong,
5 /* maxEnrollmentsPerUser */,
SensorType.Optical,
)
fakeFingerprintManagerInteractor.enrolledFingerprintsInternal =
mutableListOf(FingerprintViewModel("a", 1, 3L))
@@ -135,16 +131,12 @@ class FingerprintSettingsViewModelTest {
@Test
fun authenticate_DoesNotRun_ifUltrasonic() =
testScope.runTest {
fakeFingerprintManagerInteractor.sensorProps =
listOf(
FingerprintSensorPropertiesInternal(
0 /* sensorId */,
SensorProperties.STRENGTH_STRONG,
5 /* maxEnrollmentsPerUser */,
emptyList() /* ComponentInfoInternal */,
FingerprintSensorProperties.TYPE_UDFPS_ULTRASONIC,
true /* resetLockoutRequiresHardwareAuthToken */
)
fakeFingerprintManagerInteractor.sensorProp =
FingerprintSensorPropertyViewModel(
0 /* sensorId */,
SensorStrength.Strong,
5 /* maxEnrollmentsPerUser */,
SensorType.Ultrasonic,
)
fakeFingerprintManagerInteractor.enrolledFingerprintsInternal =
mutableListOf(FingerprintViewModel("a", 1, 3L))
@@ -173,16 +165,12 @@ class FingerprintSettingsViewModelTest {
@Test
fun authenticate_DoesRun_ifNotUdfps() =
testScope.runTest {
fakeFingerprintManagerInteractor.sensorProps =
listOf(
FingerprintSensorPropertiesInternal(
0 /* sensorId */,
SensorProperties.STRENGTH_STRONG,
5 /* maxEnrollmentsPerUser */,
emptyList() /* ComponentInfoInternal */,
FingerprintSensorProperties.TYPE_POWER_BUTTON,
true /* resetLockoutRequiresHardwareAuthToken */
)
fakeFingerprintManagerInteractor.sensorProp =
FingerprintSensorPropertyViewModel(
0 /* sensorId */,
SensorStrength.Strong,
5 /* maxEnrollmentsPerUser */,
SensorType.SFPS
)
fakeFingerprintManagerInteractor.enrolledFingerprintsInternal =
mutableListOf(FingerprintViewModel("a", 1, 3L))
@@ -383,16 +371,12 @@ class FingerprintSettingsViewModelTest {
}
private fun setupAuth(): MutableList<FingerprintViewModel> {
fakeFingerprintManagerInteractor.sensorProps =
listOf(
FingerprintSensorPropertiesInternal(
0 /* sensorId */,
SensorProperties.STRENGTH_STRONG,
5 /* maxEnrollmentsPerUser */,
emptyList() /* ComponentInfoInternal */,
FingerprintSensorProperties.TYPE_POWER_BUTTON,
true /* resetLockoutRequiresHardwareAuthToken */
)
fakeFingerprintManagerInteractor.sensorProp =
FingerprintSensorPropertyViewModel(
0 /* sensorId */,
SensorStrength.Strong,
5 /* maxEnrollmentsPerUser */,
SensorType.SFPS
)
val fingerprints =
mutableListOf(FingerprintViewModel("a", 1, 3L), FingerprintViewModel("b", 2, 5L))