Remove FingerprintStateViewModel.
Since FingerprintStateViewModel is too general as a view model, this CL removes it and adds more concrete flows in FingerprintSettingsViewModel and FingerprintEnrollViewModel. Test: atest FingerprintManagerInteractorTest Test: atest FingerprintSettingsViewModelTest Test: Verified enroll/deletion/renaming/authentication flows on Settings Change-Id: I3a0662195c4989de0813b92bccda9d36a7f7e32a
This commit is contained in:
@@ -47,6 +47,12 @@ interface FingerprintManagerInteractor {
|
|||||||
/** Returns the max enrollable fingerprints, note during SUW this might be 1 */
|
/** Returns the max enrollable fingerprints, note during SUW this might be 1 */
|
||||||
val maxEnrollableFingerprints: Flow<Int>
|
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] */
|
/** Runs [FingerprintManager.authenticate] */
|
||||||
suspend fun authenticate(): FingerprintAuthAttemptViewModel
|
suspend fun authenticate(): FingerprintAuthAttemptViewModel
|
||||||
|
|
||||||
@@ -60,9 +66,6 @@ interface FingerprintManagerInteractor {
|
|||||||
*/
|
*/
|
||||||
suspend fun generateChallenge(gateKeeperPasswordHandle: Long): Pair<Long, ByteArray>
|
suspend fun generateChallenge(gateKeeperPasswordHandle: Long): Pair<Long, ByteArray>
|
||||||
|
|
||||||
/** Returns true if a user can enroll a fingerprint false otherwise. */
|
|
||||||
fun canEnrollFingerprints(numFingerprints: Int): Flow<Boolean>
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Removes the given fingerprint, returning true if it was successfully removed and false
|
* Removes the given fingerprint, returning true if it was successfully removed and false
|
||||||
* otherwise
|
* otherwise
|
||||||
@@ -77,9 +80,6 @@ interface FingerprintManagerInteractor {
|
|||||||
|
|
||||||
/** Indicates if the press to auth feature has been enabled */
|
/** Indicates if the press to auth feature has been enabled */
|
||||||
suspend fun pressToAuthEnabled(): Boolean
|
suspend fun pressToAuthEnabled(): Boolean
|
||||||
|
|
||||||
/** Retrieves the sensor properties of a device */
|
|
||||||
suspend fun sensorPropertiesInternal(): List<FingerprintSensorPropertiesInternal>
|
|
||||||
}
|
}
|
||||||
|
|
||||||
class FingerprintManagerInteractorImpl(
|
class FingerprintManagerInteractorImpl(
|
||||||
@@ -120,8 +120,15 @@ class FingerprintManagerInteractorImpl(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun canEnrollFingerprints(numFingerprints: Int): Flow<Boolean> = flow {
|
override val canEnrollFingerprints: Flow<Boolean> = flow {
|
||||||
emit(numFingerprints < maxFingerprints)
|
emit(
|
||||||
|
fingerprintManager.getEnrolledFingerprints(applicationContext.userId).size < maxFingerprints
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
override val sensorPropertiesInternal = flow {
|
||||||
|
val sensorPropertiesInternal = fingerprintManager.sensorPropertiesInternal
|
||||||
|
emit(if (sensorPropertiesInternal.isEmpty()) null else sensorPropertiesInternal.first())
|
||||||
}
|
}
|
||||||
|
|
||||||
override val maxEnrollableFingerprints = flow { emit(maxFingerprints) }
|
override val maxEnrollableFingerprints = flow { emit(maxFingerprints) }
|
||||||
@@ -165,11 +172,6 @@ class FingerprintManagerInteractorImpl(
|
|||||||
it.resume(pressToAuthProvider())
|
it.resume(pressToAuthProvider())
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun sensorPropertiesInternal(): List<FingerprintSensorPropertiesInternal> =
|
|
||||||
suspendCancellableCoroutine {
|
|
||||||
it.resume(fingerprintManager.sensorPropertiesInternal)
|
|
||||||
}
|
|
||||||
|
|
||||||
override suspend fun authenticate(): FingerprintAuthAttemptViewModel =
|
override suspend fun authenticate(): FingerprintAuthAttemptViewModel =
|
||||||
suspendCancellableCoroutine { c: CancellableContinuation<FingerprintAuthAttemptViewModel> ->
|
suspendCancellableCoroutine { c: CancellableContinuation<FingerprintAuthAttemptViewModel> ->
|
||||||
val authenticationCallback =
|
val authenticationCallback =
|
||||||
|
@@ -16,18 +16,6 @@
|
|||||||
|
|
||||||
package com.android.settings.biometrics.fingerprint2.shared.model
|
package com.android.settings.biometrics.fingerprint2.shared.model
|
||||||
|
|
||||||
import android.hardware.fingerprint.FingerprintSensorPropertiesInternal
|
|
||||||
|
|
||||||
/** Represents the fingerprint data nad the relevant state. */
|
|
||||||
data class FingerprintStateViewModel(
|
|
||||||
val fingerprintViewModels: List<FingerprintViewModel>,
|
|
||||||
val canEnroll: Boolean,
|
|
||||||
val maxFingerprints: Int,
|
|
||||||
val hasSideFps: Boolean,
|
|
||||||
val pressToAuth: Boolean,
|
|
||||||
val sensorProps: FingerprintSensorPropertiesInternal,
|
|
||||||
)
|
|
||||||
|
|
||||||
data class FingerprintViewModel(
|
data class FingerprintViewModel(
|
||||||
val name: String,
|
val name: String,
|
||||||
val fingerId: Int,
|
val fingerId: Int,
|
||||||
|
@@ -47,10 +47,10 @@ import com.android.settings.biometrics.fingerprint2.ui.enrollment.fragment.Finge
|
|||||||
import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.Confirmation
|
import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.Confirmation
|
||||||
import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.Education
|
import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.Education
|
||||||
import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.Enrollment
|
import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.Enrollment
|
||||||
|
import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.FingerprintEnrollViewModel
|
||||||
import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.FingerprintEnrollmentNavigationViewModel
|
import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.FingerprintEnrollmentNavigationViewModel
|
||||||
import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.FingerprintGatekeeperViewModel
|
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.FingerprintScrollViewModel
|
||||||
import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.FingerprintViewModel
|
|
||||||
import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.Finish
|
import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.Finish
|
||||||
import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.GatekeeperInfo
|
import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.GatekeeperInfo
|
||||||
import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.Intro
|
import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.Intro
|
||||||
@@ -179,8 +179,10 @@ class FingerprintEnrollmentV2Activity : FragmentActivity() {
|
|||||||
)[FingerprintEnrollmentNavigationViewModel::class.java]
|
)[FingerprintEnrollmentNavigationViewModel::class.java]
|
||||||
|
|
||||||
// Initialize FingerprintViewModel
|
// Initialize FingerprintViewModel
|
||||||
ViewModelProvider(this, FingerprintViewModel.FingerprintViewModelFactory(interactor))[
|
ViewModelProvider(
|
||||||
FingerprintViewModel::class.java]
|
this,
|
||||||
|
FingerprintEnrollViewModel.FingerprintEnrollViewModelFactory(interactor)
|
||||||
|
)[FingerprintEnrollViewModel::class.java]
|
||||||
|
|
||||||
// Initialize scroll view model
|
// Initialize scroll view model
|
||||||
ViewModelProvider(this, FingerprintScrollViewModel.FingerprintScrollViewModelFactory())[
|
ViewModelProvider(this, FingerprintScrollViewModel.FingerprintScrollViewModelFactory())[
|
||||||
|
@@ -34,10 +34,10 @@ import androidx.fragment.app.Fragment
|
|||||||
import androidx.lifecycle.ViewModelProvider
|
import androidx.lifecycle.ViewModelProvider
|
||||||
import androidx.lifecycle.lifecycleScope
|
import androidx.lifecycle.lifecycleScope
|
||||||
import com.android.settings.R
|
import com.android.settings.R
|
||||||
|
import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.FingerprintEnrollViewModel
|
||||||
import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.FingerprintEnrollmentNavigationViewModel
|
import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.FingerprintEnrollmentNavigationViewModel
|
||||||
import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.FingerprintGatekeeperViewModel
|
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.FingerprintScrollViewModel
|
||||||
import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.FingerprintViewModel
|
|
||||||
import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.Unicorn
|
import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.Unicorn
|
||||||
import com.google.android.setupcompat.template.FooterBarMixin
|
import com.google.android.setupcompat.template.FooterBarMixin
|
||||||
import com.google.android.setupcompat.template.FooterButton
|
import com.google.android.setupcompat.template.FooterButton
|
||||||
@@ -76,7 +76,7 @@ class FingerprintEnrollmentIntroV2Fragment : Fragment(R.layout.fingerprint_v2_en
|
|||||||
private lateinit var footerBarMixin: FooterBarMixin
|
private lateinit var footerBarMixin: FooterBarMixin
|
||||||
private lateinit var textModel: TextModel
|
private lateinit var textModel: TextModel
|
||||||
private lateinit var navigationViewModel: FingerprintEnrollmentNavigationViewModel
|
private lateinit var navigationViewModel: FingerprintEnrollmentNavigationViewModel
|
||||||
private lateinit var fingerprintStateViewModel: FingerprintViewModel
|
private lateinit var fingerprintEnrollViewModel: FingerprintEnrollViewModel
|
||||||
private lateinit var fingerprintScrollViewModel: FingerprintScrollViewModel
|
private lateinit var fingerprintScrollViewModel: FingerprintScrollViewModel
|
||||||
private lateinit var gateKeeperViewModel: FingerprintGatekeeperViewModel
|
private lateinit var gateKeeperViewModel: FingerprintGatekeeperViewModel
|
||||||
|
|
||||||
@@ -84,8 +84,8 @@ class FingerprintEnrollmentIntroV2Fragment : Fragment(R.layout.fingerprint_v2_en
|
|||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
navigationViewModel =
|
navigationViewModel =
|
||||||
ViewModelProvider(requireActivity())[FingerprintEnrollmentNavigationViewModel::class.java]
|
ViewModelProvider(requireActivity())[FingerprintEnrollmentNavigationViewModel::class.java]
|
||||||
fingerprintStateViewModel =
|
fingerprintEnrollViewModel =
|
||||||
ViewModelProvider(requireActivity())[FingerprintViewModel::class.java]
|
ViewModelProvider(requireActivity())[FingerprintEnrollViewModel::class.java]
|
||||||
fingerprintScrollViewModel =
|
fingerprintScrollViewModel =
|
||||||
ViewModelProvider(requireActivity())[FingerprintScrollViewModel::class.java]
|
ViewModelProvider(requireActivity())[FingerprintScrollViewModel::class.java]
|
||||||
gateKeeperViewModel =
|
gateKeeperViewModel =
|
||||||
@@ -98,13 +98,11 @@ class FingerprintEnrollmentIntroV2Fragment : Fragment(R.layout.fingerprint_v2_en
|
|||||||
lifecycleScope.launch {
|
lifecycleScope.launch {
|
||||||
combine(
|
combine(
|
||||||
navigationViewModel.enrollType,
|
navigationViewModel.enrollType,
|
||||||
fingerprintStateViewModel.fingerprintStateViewModel,
|
fingerprintEnrollViewModel.sensorType,
|
||||||
) { enrollType, fingerprintStateViewModel ->
|
) { enrollType, sensorType ->
|
||||||
Pair(enrollType, fingerprintStateViewModel)
|
Pair(enrollType, sensorType)
|
||||||
}
|
}
|
||||||
.collect { (enrollType, fingerprintStateViewModel) ->
|
.collect { (enrollType, sensorType) ->
|
||||||
val sensorProps = fingerprintStateViewModel?.sensorProps
|
|
||||||
|
|
||||||
textModel =
|
textModel =
|
||||||
when (enrollType) {
|
when (enrollType) {
|
||||||
Unicorn -> getUnicornTextModel()
|
Unicorn -> getUnicornTextModel()
|
||||||
@@ -145,7 +143,7 @@ class FingerprintEnrollmentIntroV2Fragment : Fragment(R.layout.fingerprint_v2_en
|
|||||||
|
|
||||||
val iconShield: ImageView = view.requireViewById(R.id.icon_shield)
|
val iconShield: ImageView = view.requireViewById(R.id.icon_shield)
|
||||||
val footerMessage6: TextView = view.requireViewById(R.id.footer_message_6)
|
val footerMessage6: TextView = view.requireViewById(R.id.footer_message_6)
|
||||||
when (sensorProps?.sensorType) {
|
when (sensorType) {
|
||||||
FingerprintSensorProperties.TYPE_UDFPS_ULTRASONIC,
|
FingerprintSensorProperties.TYPE_UDFPS_ULTRASONIC,
|
||||||
FingerprintSensorProperties.TYPE_UDFPS_OPTICAL -> {
|
FingerprintSensorProperties.TYPE_UDFPS_OPTICAL -> {
|
||||||
footerMessage6.visibility = View.VISIBLE
|
footerMessage6.visibility = View.VISIBLE
|
||||||
|
@@ -0,0 +1,44 @@
|
|||||||
|
/*
|
||||||
|
* 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.ui.enrollment.viewmodel
|
||||||
|
|
||||||
|
import android.hardware.fingerprint.FingerprintSensorProperties
|
||||||
|
import androidx.lifecycle.ViewModel
|
||||||
|
import androidx.lifecycle.ViewModelProvider
|
||||||
|
import com.android.settings.biometrics.fingerprint2.domain.interactor.FingerprintManagerInteractor
|
||||||
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
import kotlinx.coroutines.flow.transform
|
||||||
|
|
||||||
|
/** Represents all of the fingerprint information needed for fingerprint enrollment. */
|
||||||
|
class FingerprintEnrollViewModel(fingerprintManagerInteractor: FingerprintManagerInteractor) :
|
||||||
|
ViewModel() {
|
||||||
|
|
||||||
|
/** Represents the stream of [FingerprintSensorProperties.SensorType] */
|
||||||
|
val sensorType: Flow<Int> =
|
||||||
|
fingerprintManagerInteractor.sensorPropertiesInternal.transform { it?.sensorType }
|
||||||
|
|
||||||
|
class FingerprintEnrollViewModelFactory(val interactor: FingerprintManagerInteractor) :
|
||||||
|
ViewModelProvider.Factory {
|
||||||
|
|
||||||
|
@Suppress("UNCHECKED_CAST")
|
||||||
|
override fun <T : ViewModel> create(
|
||||||
|
modelClass: Class<T>,
|
||||||
|
): T {
|
||||||
|
return FingerprintEnrollViewModel(interactor) as T
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -1,81 +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.ui.enrollment.viewmodel
|
|
||||||
|
|
||||||
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.model.FingerprintStateViewModel
|
|
||||||
import kotlinx.coroutines.flow.Flow
|
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
|
||||||
import kotlinx.coroutines.flow.asStateFlow
|
|
||||||
import kotlinx.coroutines.flow.last
|
|
||||||
import kotlinx.coroutines.flow.update
|
|
||||||
import kotlinx.coroutines.launch
|
|
||||||
|
|
||||||
/** Represents all of the fingerprint information needed for fingerprint enrollment. */
|
|
||||||
class FingerprintViewModel(fingerprintManagerInteractor: FingerprintManagerInteractor) :
|
|
||||||
ViewModel() {
|
|
||||||
|
|
||||||
private val _fingerprintViewModel: MutableStateFlow<FingerprintStateViewModel?> =
|
|
||||||
MutableStateFlow(null)
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A flow that contains a [FingerprintStateViewModel] which contains the relevant information for
|
|
||||||
* enrollment
|
|
||||||
*/
|
|
||||||
val fingerprintStateViewModel: Flow<FingerprintStateViewModel?> =
|
|
||||||
_fingerprintViewModel.asStateFlow()
|
|
||||||
|
|
||||||
init {
|
|
||||||
viewModelScope.launch {
|
|
||||||
val enrolledFingerprints =
|
|
||||||
fingerprintManagerInteractor.enrolledFingerprints.last().map {
|
|
||||||
com.android.settings.biometrics.fingerprint2.shared.model.FingerprintViewModel(
|
|
||||||
it.name,
|
|
||||||
it.fingerId,
|
|
||||||
it.deviceId
|
|
||||||
)
|
|
||||||
}
|
|
||||||
val sensorProps = fingerprintManagerInteractor.sensorPropertiesInternal().first()
|
|
||||||
val maxFingerprints = 5
|
|
||||||
_fingerprintViewModel.update {
|
|
||||||
FingerprintStateViewModel(
|
|
||||||
enrolledFingerprints,
|
|
||||||
enrolledFingerprints.size < maxFingerprints,
|
|
||||||
maxFingerprints,
|
|
||||||
sensorProps.isAnySidefpsType,
|
|
||||||
false,
|
|
||||||
sensorProps,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class FingerprintViewModelFactory(val interactor: FingerprintManagerInteractor) :
|
|
||||||
ViewModelProvider.Factory {
|
|
||||||
|
|
||||||
@Suppress("UNCHECKED_CAST")
|
|
||||||
override fun <T : ViewModel> create(
|
|
||||||
modelClass: Class<T>,
|
|
||||||
): T {
|
|
||||||
|
|
||||||
return FingerprintViewModel(interactor) as T
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@@ -20,7 +20,6 @@ import android.hardware.fingerprint.FingerprintManager
|
|||||||
import android.util.Log
|
import android.util.Log
|
||||||
import androidx.lifecycle.LifecycleCoroutineScope
|
import androidx.lifecycle.LifecycleCoroutineScope
|
||||||
import com.android.settings.biometrics.fingerprint2.shared.model.FingerprintAuthAttemptViewModel
|
import com.android.settings.biometrics.fingerprint2.shared.model.FingerprintAuthAttemptViewModel
|
||||||
import com.android.settings.biometrics.fingerprint2.shared.model.FingerprintStateViewModel
|
|
||||||
import com.android.settings.biometrics.fingerprint2.shared.model.FingerprintViewModel
|
import com.android.settings.biometrics.fingerprint2.shared.model.FingerprintViewModel
|
||||||
import com.android.settings.biometrics.fingerprint2.ui.settings.binder.FingerprintSettingsViewBinder.FingerprintView
|
import com.android.settings.biometrics.fingerprint2.ui.settings.binder.FingerprintSettingsViewBinder.FingerprintView
|
||||||
import com.android.settings.biometrics.fingerprint2.ui.settings.viewmodel.EnrollAdditionalFingerprint
|
import com.android.settings.biometrics.fingerprint2.ui.settings.viewmodel.EnrollAdditionalFingerprint
|
||||||
@@ -35,6 +34,7 @@ import com.android.settings.biometrics.fingerprint2.ui.settings.viewmodel.Prefer
|
|||||||
import com.android.settings.biometrics.fingerprint2.ui.settings.viewmodel.ShowSettings
|
import com.android.settings.biometrics.fingerprint2.ui.settings.viewmodel.ShowSettings
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.Job
|
import kotlinx.coroutines.Job
|
||||||
|
import kotlinx.coroutines.flow.collect
|
||||||
import kotlinx.coroutines.flow.collectLatest
|
import kotlinx.coroutines.flow.collectLatest
|
||||||
import kotlinx.coroutines.flow.filterNotNull
|
import kotlinx.coroutines.flow.filterNotNull
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
@@ -70,7 +70,11 @@ object FingerprintSettingsViewBinder {
|
|||||||
/** Indicates what result should be set for the returning callee */
|
/** Indicates what result should be set for the returning callee */
|
||||||
fun setResultExternal(resultCode: Int)
|
fun setResultExternal(resultCode: Int)
|
||||||
/** Indicates the settings UI should be shown */
|
/** Indicates the settings UI should be shown */
|
||||||
fun showSettings(state: FingerprintStateViewModel)
|
fun showSettings(enrolledFingerprints: List<FingerprintViewModel>)
|
||||||
|
/** Updates the add fingerprints preference */
|
||||||
|
fun updateAddFingerprintsPreference(canEnroll: Boolean, maxFingerprints: Int)
|
||||||
|
/** Updates the sfps fingerprints preference */
|
||||||
|
fun updateSfpsPreference(isSfpsPrefVisible: Boolean)
|
||||||
/** Indicates that a user has been locked out */
|
/** Indicates that a user has been locked out */
|
||||||
fun userLockout(authAttemptViewModel: FingerprintAuthAttemptViewModel.Error)
|
fun userLockout(authAttemptViewModel: FingerprintAuthAttemptViewModel.Error)
|
||||||
/** Indicates a fingerprint preference should be highlighted */
|
/** Indicates a fingerprint preference should be highlighted */
|
||||||
@@ -93,9 +97,13 @@ object FingerprintSettingsViewBinder {
|
|||||||
/** Result listener for launching enrollments **after** a user has reached the settings page. */
|
/** Result listener for launching enrollments **after** a user has reached the settings page. */
|
||||||
|
|
||||||
// Settings display flow
|
// Settings display flow
|
||||||
|
lifecycleScope.launch { viewModel.enrolledFingerprints.collect { view.showSettings(it) } }
|
||||||
lifecycleScope.launch {
|
lifecycleScope.launch {
|
||||||
viewModel.fingerprintState.filterNotNull().collect { view.showSettings(it) }
|
viewModel.addFingerprintPrefInfo.collect { (enablePref, maxFingerprints) ->
|
||||||
|
view.updateAddFingerprintsPreference(enablePref, maxFingerprints)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
lifecycleScope.launch { viewModel.isSfpsPrefVisible.collect { view.updateSfpsPreference(it) } }
|
||||||
|
|
||||||
// Dialog flow
|
// Dialog flow
|
||||||
lifecycleScope.launch {
|
lifecycleScope.launch {
|
||||||
|
@@ -47,7 +47,6 @@ import com.android.settings.biometrics.fingerprint.FingerprintEnrollEnrolling
|
|||||||
import com.android.settings.biometrics.fingerprint.FingerprintEnrollIntroductionInternal
|
import com.android.settings.biometrics.fingerprint.FingerprintEnrollIntroductionInternal
|
||||||
import com.android.settings.biometrics.fingerprint2.domain.interactor.FingerprintManagerInteractorImpl
|
import com.android.settings.biometrics.fingerprint2.domain.interactor.FingerprintManagerInteractorImpl
|
||||||
import com.android.settings.biometrics.fingerprint2.shared.model.FingerprintAuthAttemptViewModel
|
import com.android.settings.biometrics.fingerprint2.shared.model.FingerprintAuthAttemptViewModel
|
||||||
import com.android.settings.biometrics.fingerprint2.shared.model.FingerprintStateViewModel
|
|
||||||
import com.android.settings.biometrics.fingerprint2.shared.model.FingerprintViewModel
|
import com.android.settings.biometrics.fingerprint2.shared.model.FingerprintViewModel
|
||||||
import com.android.settings.biometrics.fingerprint2.ui.settings.binder.FingerprintSettingsViewBinder
|
import com.android.settings.biometrics.fingerprint2.ui.settings.binder.FingerprintSettingsViewBinder
|
||||||
import com.android.settings.biometrics.fingerprint2.ui.settings.viewmodel.FingerprintSettingsNavigationViewModel
|
import com.android.settings.biometrics.fingerprint2.ui.settings.viewmodel.FingerprintSettingsNavigationViewModel
|
||||||
@@ -304,44 +303,52 @@ class FingerprintSettingsV2Fragment :
|
|||||||
settingsViewModel.onDeleteClicked(fingerprintViewModel)
|
settingsViewModel.onDeleteClicked(fingerprintViewModel)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun showSettings(state: FingerprintStateViewModel) {
|
override fun showSettings(enrolledFingerprints: List<FingerprintViewModel>) {
|
||||||
val category =
|
val category =
|
||||||
this@FingerprintSettingsV2Fragment.findPreference(KEY_FINGERPRINTS_ENROLLED_CATEGORY)
|
this@FingerprintSettingsV2Fragment.findPreference(KEY_FINGERPRINTS_ENROLLED_CATEGORY)
|
||||||
as PreferenceCategory?
|
as PreferenceCategory?
|
||||||
|
|
||||||
category?.removeAll()
|
category?.removeAll()
|
||||||
|
|
||||||
state.fingerprintViewModels.forEach { fingerprint ->
|
enrolledFingerprints.forEach { fingerprint ->
|
||||||
category?.addPreference(
|
category?.addPreference(
|
||||||
FingerprintSettingsPreference(
|
FingerprintSettingsPreference(
|
||||||
requireContext(),
|
requireContext(),
|
||||||
fingerprint,
|
fingerprint,
|
||||||
this@FingerprintSettingsV2Fragment,
|
this@FingerprintSettingsV2Fragment,
|
||||||
state.fingerprintViewModels.size == 1,
|
enrolledFingerprints.size == 1,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
category?.isVisible = true
|
category?.isVisible = true
|
||||||
|
|
||||||
createFingerprintsFooterPreference(state.canEnroll, state.maxFingerprints)
|
|
||||||
preferenceScreen.isVisible = true
|
preferenceScreen.isVisible = true
|
||||||
|
addFooter()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun updateAddFingerprintsPreference(canEnroll: Boolean, maxFingerprints: Int) {
|
||||||
|
val pref = this@FingerprintSettingsV2Fragment.findPreference<Preference>(KEY_FINGERPRINT_ADD)
|
||||||
|
val maxSummary = context?.getString(R.string.fingerprint_add_max, maxFingerprints) ?: ""
|
||||||
|
pref?.summary = maxSummary
|
||||||
|
pref?.isEnabled = canEnroll
|
||||||
|
pref?.setOnPreferenceClickListener {
|
||||||
|
navigationViewModel.onAddFingerprintClicked()
|
||||||
|
true
|
||||||
|
}
|
||||||
|
pref?.isVisible = true
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun updateSfpsPreference(isSfpsPrefVisible: Boolean) {
|
||||||
val sideFpsPref =
|
val sideFpsPref =
|
||||||
this@FingerprintSettingsV2Fragment.findPreference(KEY_FINGERPRINT_SIDE_FPS_CATEGORY)
|
this@FingerprintSettingsV2Fragment.findPreference(KEY_FINGERPRINT_SIDE_FPS_CATEGORY)
|
||||||
as PreferenceCategory?
|
as PreferenceCategory?
|
||||||
sideFpsPref?.isVisible = false
|
sideFpsPref?.isVisible = isSfpsPrefVisible
|
||||||
|
val otherPref =
|
||||||
if (state.hasSideFps) {
|
this@FingerprintSettingsV2Fragment.findPreference(KEY_FINGERPRINT_SIDE_FPS_SCREEN_ON_TO_AUTH)
|
||||||
sideFpsPref?.isVisible = state.fingerprintViewModels.isNotEmpty()
|
as Preference?
|
||||||
val otherPref =
|
otherPref?.isVisible = isSfpsPrefVisible
|
||||||
this@FingerprintSettingsV2Fragment.findPreference(
|
|
||||||
KEY_FINGERPRINT_SIDE_FPS_SCREEN_ON_TO_AUTH
|
|
||||||
) as Preference?
|
|
||||||
otherPref?.isVisible = state.fingerprintViewModels.isNotEmpty()
|
|
||||||
}
|
|
||||||
addFooter(state.hasSideFps)
|
|
||||||
}
|
}
|
||||||
private fun addFooter(hasSideFps: Boolean) {
|
|
||||||
|
private fun addFooter() {
|
||||||
val footer =
|
val footer =
|
||||||
this@FingerprintSettingsV2Fragment.findPreference(KEY_FINGERPRINT_FOOTER)
|
this@FingerprintSettingsV2Fragment.findPreference(KEY_FINGERPRINT_FOOTER)
|
||||||
as PreferenceCategory?
|
as PreferenceCategory?
|
||||||
@@ -380,10 +387,8 @@ class FingerprintSettingsV2Fragment :
|
|||||||
footerColumns.add(column1)
|
footerColumns.add(column1)
|
||||||
val column2 = FooterColumn()
|
val column2 = FooterColumn()
|
||||||
column2.title = getText(R.string.security_fingerprint_disclaimer_lockscreen_disabled_2)
|
column2.title = getText(R.string.security_fingerprint_disclaimer_lockscreen_disabled_2)
|
||||||
if (hasSideFps) {
|
column2.learnMoreOverrideText =
|
||||||
column2.learnMoreOverrideText =
|
getText(R.string.security_settings_fingerprint_settings_footer_learn_more)
|
||||||
getText(R.string.security_settings_fingerprint_settings_footer_learn_more)
|
|
||||||
}
|
|
||||||
column2.learnMoreOnClickListener = learnMoreClickListener
|
column2.learnMoreOnClickListener = learnMoreClickListener
|
||||||
footerColumns.add(column2)
|
footerColumns.add(column2)
|
||||||
} else {
|
} else {
|
||||||
@@ -394,10 +399,8 @@ class FingerprintSettingsV2Fragment :
|
|||||||
DeviceHelper.getDeviceName(requireActivity())
|
DeviceHelper.getDeviceName(requireActivity())
|
||||||
)
|
)
|
||||||
column.learnMoreOnClickListener = learnMoreClickListener
|
column.learnMoreOnClickListener = learnMoreClickListener
|
||||||
if (hasSideFps) {
|
column.learnMoreOverrideText =
|
||||||
column.learnMoreOverrideText =
|
getText(R.string.security_settings_fingerprint_settings_footer_learn_more)
|
||||||
getText(R.string.security_settings_fingerprint_settings_footer_learn_more)
|
|
||||||
}
|
|
||||||
footerColumns.add(column)
|
footerColumns.add(column)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -550,18 +553,6 @@ class FingerprintSettingsV2Fragment :
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun createFingerprintsFooterPreference(canEnroll: Boolean, maxFingerprints: Int) {
|
|
||||||
val pref = this@FingerprintSettingsV2Fragment.findPreference<Preference>(KEY_FINGERPRINT_ADD)
|
|
||||||
val maxSummary = context?.getString(R.string.fingerprint_add_max, maxFingerprints) ?: ""
|
|
||||||
pref?.summary = maxSummary
|
|
||||||
pref?.isEnabled = canEnroll
|
|
||||||
pref?.setOnPreferenceClickListener {
|
|
||||||
navigationViewModel.onAddFingerprintClicked()
|
|
||||||
true
|
|
||||||
}
|
|
||||||
pref?.isVisible = true
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun fingerprintPreferences(): List<FingerprintSettingsPreference?> {
|
private fun fingerprintPreferences(): List<FingerprintSettingsPreference?> {
|
||||||
val category =
|
val category =
|
||||||
this@FingerprintSettingsV2Fragment.findPreference(KEY_FINGERPRINTS_ENROLLED_CATEGORY)
|
this@FingerprintSettingsV2Fragment.findPreference(KEY_FINGERPRINTS_ENROLLED_CATEGORY)
|
||||||
|
@@ -18,26 +18,27 @@ package com.android.settings.biometrics.fingerprint2.ui.settings.viewmodel
|
|||||||
|
|
||||||
import android.hardware.fingerprint.FingerprintManager
|
import android.hardware.fingerprint.FingerprintManager
|
||||||
import android.hardware.fingerprint.FingerprintSensorProperties
|
import android.hardware.fingerprint.FingerprintSensorProperties
|
||||||
import android.hardware.fingerprint.FingerprintSensorPropertiesInternal
|
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import androidx.lifecycle.ViewModel
|
import androidx.lifecycle.ViewModel
|
||||||
import androidx.lifecycle.ViewModelProvider
|
import androidx.lifecycle.ViewModelProvider
|
||||||
import androidx.lifecycle.viewModelScope
|
import androidx.lifecycle.viewModelScope
|
||||||
import com.android.settings.biometrics.fingerprint2.domain.interactor.FingerprintManagerInteractor
|
import com.android.settings.biometrics.fingerprint2.domain.interactor.FingerprintManagerInteractor
|
||||||
import com.android.settings.biometrics.fingerprint2.shared.model.FingerprintAuthAttemptViewModel
|
import com.android.settings.biometrics.fingerprint2.shared.model.FingerprintAuthAttemptViewModel
|
||||||
import com.android.settings.biometrics.fingerprint2.shared.model.FingerprintStateViewModel
|
|
||||||
import com.android.settings.biometrics.fingerprint2.shared.model.FingerprintViewModel
|
import com.android.settings.biometrics.fingerprint2.shared.model.FingerprintViewModel
|
||||||
import kotlinx.coroutines.CoroutineDispatcher
|
import kotlinx.coroutines.CoroutineDispatcher
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
|
import kotlinx.coroutines.flow.asStateFlow
|
||||||
import kotlinx.coroutines.flow.combine
|
import kotlinx.coroutines.flow.combine
|
||||||
import kotlinx.coroutines.flow.combineTransform
|
import kotlinx.coroutines.flow.combineTransform
|
||||||
import kotlinx.coroutines.flow.distinctUntilChanged
|
import kotlinx.coroutines.flow.distinctUntilChanged
|
||||||
import kotlinx.coroutines.flow.filterNotNull
|
import kotlinx.coroutines.flow.filterNotNull
|
||||||
|
import kotlinx.coroutines.flow.first
|
||||||
import kotlinx.coroutines.flow.flowOn
|
import kotlinx.coroutines.flow.flowOn
|
||||||
import kotlinx.coroutines.flow.last
|
import kotlinx.coroutines.flow.map
|
||||||
import kotlinx.coroutines.flow.sample
|
import kotlinx.coroutines.flow.sample
|
||||||
|
import kotlinx.coroutines.flow.transform
|
||||||
import kotlinx.coroutines.flow.transformLatest
|
import kotlinx.coroutines.flow.transformLatest
|
||||||
import kotlinx.coroutines.flow.update
|
import kotlinx.coroutines.flow.update
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
@@ -52,13 +53,30 @@ class FingerprintSettingsViewModel(
|
|||||||
private val backgroundDispatcher: CoroutineDispatcher,
|
private val backgroundDispatcher: CoroutineDispatcher,
|
||||||
private val navigationViewModel: FingerprintSettingsNavigationViewModel,
|
private val navigationViewModel: FingerprintSettingsNavigationViewModel,
|
||||||
) : ViewModel() {
|
) : ViewModel() {
|
||||||
|
private val _enrolledFingerprints: MutableStateFlow<List<FingerprintViewModel>?> =
|
||||||
private val _consumerShouldAuthenticate: MutableStateFlow<Boolean> = MutableStateFlow(false)
|
|
||||||
|
|
||||||
private val fingerprintSensorPropertiesInternal:
|
|
||||||
MutableStateFlow<List<FingerprintSensorPropertiesInternal>?> =
|
|
||||||
MutableStateFlow(null)
|
MutableStateFlow(null)
|
||||||
|
|
||||||
|
/** Represents the stream of enrolled fingerprints. */
|
||||||
|
val enrolledFingerprints: Flow<List<FingerprintViewModel>> =
|
||||||
|
_enrolledFingerprints.asStateFlow().filterNotNull().filterOnlyWhenSettingsIsShown()
|
||||||
|
|
||||||
|
/** 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()
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Represents the stream of visibility of sfps preference. */
|
||||||
|
val isSfpsPrefVisible: Flow<Boolean> =
|
||||||
|
_enrolledFingerprints.filterOnlyWhenSettingsIsShown().transform {
|
||||||
|
emit(fingerprintManagerInteractor.hasSideFps() && !it.isNullOrEmpty())
|
||||||
|
}
|
||||||
|
|
||||||
private val _isShowingDialog: MutableStateFlow<PreferenceViewModel?> = MutableStateFlow(null)
|
private val _isShowingDialog: MutableStateFlow<PreferenceViewModel?> = MutableStateFlow(null)
|
||||||
val isShowingDialog =
|
val isShowingDialog =
|
||||||
_isShowingDialog.combine(navigationViewModel.nextStep) { dialogFlow, nextStep ->
|
_isShowingDialog.combine(navigationViewModel.nextStep) { dialogFlow, nextStep ->
|
||||||
@@ -69,16 +87,13 @@ class FingerprintSettingsViewModel(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private val _fingerprintStateViewModel: MutableStateFlow<FingerprintStateViewModel?> =
|
private val _consumerShouldAuthenticate: MutableStateFlow<Boolean> = MutableStateFlow(false)
|
||||||
MutableStateFlow(null)
|
|
||||||
val fingerprintState: Flow<FingerprintStateViewModel?> =
|
private val _fingerprintSensorType: Flow<Int> =
|
||||||
_fingerprintStateViewModel.combineTransform(navigationViewModel.nextStep) {
|
fingerprintManagerInteractor.sensorPropertiesInternal.transform { it?.sensorType }
|
||||||
settingsShowingViewModel,
|
|
||||||
currStep ->
|
private val _sensorNullOrEmpty: Flow<Boolean> =
|
||||||
if (currStep != null && currStep is ShowSettings) {
|
fingerprintManagerInteractor.sensorPropertiesInternal.map{it ==null}
|
||||||
emit(settingsShowingViewModel)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private val _isLockedOut: MutableStateFlow<FingerprintAuthAttemptViewModel.Error?> =
|
private val _isLockedOut: MutableStateFlow<FingerprintAuthAttemptViewModel.Error?> =
|
||||||
MutableStateFlow(null)
|
MutableStateFlow(null)
|
||||||
@@ -86,7 +101,7 @@ class FingerprintSettingsViewModel(
|
|||||||
private val _authSucceeded: MutableSharedFlow<FingerprintAuthAttemptViewModel.Success?> =
|
private val _authSucceeded: MutableSharedFlow<FingerprintAuthAttemptViewModel.Success?> =
|
||||||
MutableSharedFlow()
|
MutableSharedFlow()
|
||||||
|
|
||||||
private val attemptsSoFar: MutableStateFlow<Int> = MutableStateFlow(0)
|
private val _attemptsSoFar: MutableStateFlow<Int> = MutableStateFlow(0)
|
||||||
/**
|
/**
|
||||||
* This is a very tricky flow. The current fingerprint manager APIs are not robust, and a proper
|
* This is a very tricky flow. The current fingerprint manager APIs are not robust, and a proper
|
||||||
* implementation would take quite a lot of code to implement, it might be easier to rewrite
|
* implementation would take quite a lot of code to implement, it might be easier to rewrite
|
||||||
@@ -101,11 +116,20 @@ class FingerprintSettingsViewModel(
|
|||||||
_isShowingDialog,
|
_isShowingDialog,
|
||||||
navigationViewModel.nextStep,
|
navigationViewModel.nextStep,
|
||||||
_consumerShouldAuthenticate,
|
_consumerShouldAuthenticate,
|
||||||
_fingerprintStateViewModel,
|
_enrolledFingerprints,
|
||||||
_isLockedOut,
|
_isLockedOut,
|
||||||
attemptsSoFar,
|
_attemptsSoFar,
|
||||||
fingerprintSensorPropertiesInternal
|
_fingerprintSensorType,
|
||||||
) { dialogShowing, step, resume, fingerprints, isLockedOut, attempts, sensorProps ->
|
_sensorNullOrEmpty
|
||||||
|
) {
|
||||||
|
dialogShowing,
|
||||||
|
step,
|
||||||
|
resume,
|
||||||
|
fingerprints,
|
||||||
|
isLockedOut,
|
||||||
|
attempts,
|
||||||
|
sensorType,
|
||||||
|
sensorNullOrEmpty ->
|
||||||
if (DEBUG) {
|
if (DEBUG) {
|
||||||
Log.d(
|
Log.d(
|
||||||
TAG,
|
TAG,
|
||||||
@@ -115,13 +139,13 @@ class FingerprintSettingsViewModel(
|
|||||||
"fingerprints=${fingerprints}," +
|
"fingerprints=${fingerprints}," +
|
||||||
"lockedOut=${isLockedOut}," +
|
"lockedOut=${isLockedOut}," +
|
||||||
"attempts=${attempts}," +
|
"attempts=${attempts}," +
|
||||||
"sensorProps=${sensorProps}"
|
"sensorType=${sensorType}" +
|
||||||
|
"sensorNullOrEmpty=${sensorNullOrEmpty}"
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
if (sensorProps.isNullOrEmpty()) {
|
if (sensorNullOrEmpty) {
|
||||||
return@combine false
|
return@combine false
|
||||||
}
|
}
|
||||||
val sensorType = sensorProps[0].sensorType
|
|
||||||
if (
|
if (
|
||||||
listOf(
|
listOf(
|
||||||
FingerprintSensorProperties.TYPE_UDFPS_OPTICAL,
|
FingerprintSensorProperties.TYPE_UDFPS_OPTICAL,
|
||||||
@@ -133,7 +157,7 @@ class FingerprintSettingsViewModel(
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (step != null && step is ShowSettings) {
|
if (step != null && step is ShowSettings) {
|
||||||
if (fingerprints?.fingerprintViewModels?.isNotEmpty() == true) {
|
if (fingerprints?.isNotEmpty() == true) {
|
||||||
return@combine dialogShowing == null && isLockedOut == null && resume && attempts < 15
|
return@combine dialogShowing == null && isLockedOut == null && resume && attempts < 15
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -172,18 +196,12 @@ class FingerprintSettingsViewModel(
|
|||||||
.flowOn(backgroundDispatcher)
|
.flowOn(backgroundDispatcher)
|
||||||
|
|
||||||
init {
|
init {
|
||||||
viewModelScope.launch {
|
|
||||||
fingerprintSensorPropertiesInternal.update {
|
|
||||||
fingerprintManagerInteractor.sensorPropertiesInternal()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
navigationViewModel.nextStep.filterNotNull().collect {
|
navigationViewModel.nextStep.filterNotNull().collect {
|
||||||
_isShowingDialog.update { null }
|
_isShowingDialog.update { null }
|
||||||
if (it is ShowSettings) {
|
if (it is ShowSettings) {
|
||||||
// reset state
|
// reset state
|
||||||
updateSettingsData()
|
updateEnrolledFingerprints()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -200,7 +218,7 @@ class FingerprintSettingsViewModel(
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun toString(): String {
|
override fun toString(): String {
|
||||||
return "userId: $userId\n" + "fingerprintState: ${_fingerprintStateViewModel.value}\n"
|
return "userId: $userId\n" + "enrolledFingerprints: ${_enrolledFingerprints.value}\n"
|
||||||
}
|
}
|
||||||
|
|
||||||
/** The fingerprint delete button has been clicked. */
|
/** The fingerprint delete button has been clicked. */
|
||||||
@@ -229,7 +247,7 @@ class FingerprintSettingsViewModel(
|
|||||||
fun deleteFingerprint(fp: FingerprintViewModel) {
|
fun deleteFingerprint(fp: FingerprintViewModel) {
|
||||||
viewModelScope.launch(backgroundDispatcher) {
|
viewModelScope.launch(backgroundDispatcher) {
|
||||||
if (fingerprintManagerInteractor.removeFingerprint(fp)) {
|
if (fingerprintManagerInteractor.removeFingerprint(fp)) {
|
||||||
updateSettingsData()
|
updateEnrolledFingerprints()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -238,45 +256,25 @@ class FingerprintSettingsViewModel(
|
|||||||
fun renameFingerprint(fp: FingerprintViewModel, newName: String) {
|
fun renameFingerprint(fp: FingerprintViewModel, newName: String) {
|
||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
fingerprintManagerInteractor.renameFingerprint(fp, newName)
|
fingerprintManagerInteractor.renameFingerprint(fp, newName)
|
||||||
updateSettingsData()
|
updateEnrolledFingerprints()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun attemptingAuth() {
|
private fun attemptingAuth() {
|
||||||
attemptsSoFar.update { it + 1 }
|
_attemptsSoFar.update { it + 1 }
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun onAuthSuccess(success: FingerprintAuthAttemptViewModel.Success) {
|
private suspend fun onAuthSuccess(success: FingerprintAuthAttemptViewModel.Success) {
|
||||||
_authSucceeded.emit(success)
|
_authSucceeded.emit(success)
|
||||||
attemptsSoFar.update { 0 }
|
_attemptsSoFar.update { 0 }
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun lockout(attemptViewModel: FingerprintAuthAttemptViewModel.Error) {
|
private fun lockout(attemptViewModel: FingerprintAuthAttemptViewModel.Error) {
|
||||||
_isLockedOut.update { attemptViewModel }
|
_isLockedOut.update { attemptViewModel }
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
private suspend fun updateEnrolledFingerprints() {
|
||||||
* This function is sort of a hack, it's used whenever we want to check for fingerprint state
|
_enrolledFingerprints.update { fingerprintManagerInteractor.enrolledFingerprints.first() }
|
||||||
* updates.
|
|
||||||
*/
|
|
||||||
private suspend fun updateSettingsData() {
|
|
||||||
Log.d(TAG, "update settings data called")
|
|
||||||
val fingerprints = fingerprintManagerInteractor.enrolledFingerprints.last()
|
|
||||||
val canEnrollFingerprint =
|
|
||||||
fingerprintManagerInteractor.canEnrollFingerprints(fingerprints.size).last()
|
|
||||||
val maxFingerprints = fingerprintManagerInteractor.maxEnrollableFingerprints.last()
|
|
||||||
val hasSideFps = fingerprintManagerInteractor.hasSideFps()
|
|
||||||
val pressToAuthEnabled = fingerprintManagerInteractor.pressToAuthEnabled()
|
|
||||||
_fingerprintStateViewModel.update {
|
|
||||||
FingerprintStateViewModel(
|
|
||||||
fingerprints,
|
|
||||||
canEnrollFingerprint,
|
|
||||||
maxFingerprints,
|
|
||||||
hasSideFps,
|
|
||||||
pressToAuthEnabled,
|
|
||||||
fingerprintManagerInteractor.sensorPropertiesInternal().first(),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Used to indicate whether the consumer of the view model is ready for authentication. */
|
/** Used to indicate whether the consumer of the view model is ready for authentication. */
|
||||||
@@ -284,6 +282,13 @@ class FingerprintSettingsViewModel(
|
|||||||
_consumerShouldAuthenticate.update { authenticate }
|
_consumerShouldAuthenticate.update { authenticate }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun <T> Flow<T>.filterOnlyWhenSettingsIsShown() =
|
||||||
|
combineTransform(navigationViewModel.nextStep) { value, currStep ->
|
||||||
|
if (currStep != null && currStep is ShowSettings) {
|
||||||
|
emit(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
class FingerprintSettingsViewModelFactory(
|
class FingerprintSettingsViewModelFactory(
|
||||||
private val userId: Int,
|
private val userId: Int,
|
||||||
private val interactor: FingerprintManagerInteractor,
|
private val interactor: FingerprintManagerInteractor,
|
||||||
@@ -307,7 +312,7 @@ class FingerprintSettingsViewModel(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private inline fun <T1, T2, T3, T4, T5, T6, T7, R> combine(
|
private inline fun <T1, T2, T3, T4, T5, T6, T7, T8, R> combine(
|
||||||
flow: Flow<T1>,
|
flow: Flow<T1>,
|
||||||
flow2: Flow<T2>,
|
flow2: Flow<T2>,
|
||||||
flow3: Flow<T3>,
|
flow3: Flow<T3>,
|
||||||
@@ -315,9 +320,10 @@ private inline fun <T1, T2, T3, T4, T5, T6, T7, R> combine(
|
|||||||
flow5: Flow<T5>,
|
flow5: Flow<T5>,
|
||||||
flow6: Flow<T6>,
|
flow6: Flow<T6>,
|
||||||
flow7: Flow<T7>,
|
flow7: Flow<T7>,
|
||||||
crossinline transform: suspend (T1, T2, T3, T4, T5, T6, T7) -> R
|
flow8: Flow<T8>,
|
||||||
|
crossinline transform: suspend (T1, T2, T3, T4, T5, T6, T7, T8) -> R
|
||||||
): Flow<R> {
|
): Flow<R> {
|
||||||
return combine(flow, flow2, flow3, flow4, flow5, flow6, flow7) { args: Array<*> ->
|
return combine(flow, flow2, flow3, flow4, flow5, flow6, flow7, flow8) { args: Array<*> ->
|
||||||
@Suppress("UNCHECKED_CAST")
|
@Suppress("UNCHECKED_CAST")
|
||||||
transform(
|
transform(
|
||||||
args[0] as T1,
|
args[0] as T1,
|
||||||
@@ -327,6 +333,7 @@ private inline fun <T1, T2, T3, T4, T5, T6, T7, R> combine(
|
|||||||
args[4] as T5,
|
args[4] as T5,
|
||||||
args[5] as T6,
|
args[5] as T6,
|
||||||
args[6] as T7,
|
args[6] as T7,
|
||||||
|
args[7] as T8,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -23,7 +23,7 @@ import com.android.settings.biometrics.fingerprint2.domain.interactor.Fingerprin
|
|||||||
import com.android.settings.biometrics.fingerprint2.shared.model.FingerprintAuthAttemptViewModel
|
import com.android.settings.biometrics.fingerprint2.shared.model.FingerprintAuthAttemptViewModel
|
||||||
import com.android.settings.biometrics.fingerprint2.shared.model.FingerprintViewModel
|
import com.android.settings.biometrics.fingerprint2.shared.model.FingerprintViewModel
|
||||||
import kotlinx.coroutines.flow.Flow
|
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. */
|
/** Fake to be used by other classes to easily fake the FingerprintManager implementation. */
|
||||||
class FakeFingerprintManagerInteractor : FingerprintManagerInteractor {
|
class FakeFingerprintManagerInteractor : FingerprintManagerInteractor {
|
||||||
@@ -53,15 +53,16 @@ class FakeFingerprintManagerInteractor : FingerprintManagerInteractor {
|
|||||||
override suspend fun generateChallenge(gateKeeperPasswordHandle: Long): Pair<Long, ByteArray> {
|
override suspend fun generateChallenge(gateKeeperPasswordHandle: Long): Pair<Long, ByteArray> {
|
||||||
return challengeToGenerate
|
return challengeToGenerate
|
||||||
}
|
}
|
||||||
override val enrolledFingerprints: Flow<List<FingerprintViewModel>> = flow {
|
override val enrolledFingerprints: Flow<List<FingerprintViewModel>> =
|
||||||
emit(enrolledFingerprintsInternal)
|
flowOf(enrolledFingerprintsInternal)
|
||||||
}
|
|
||||||
|
|
||||||
override fun canEnrollFingerprints(numFingerprints: Int): Flow<Boolean> = flow {
|
override val canEnrollFingerprints: Flow<Boolean> =
|
||||||
emit(numFingerprints < enrollableFingerprints)
|
flowOf(enrolledFingerprintsInternal.size < enrollableFingerprints)
|
||||||
}
|
|
||||||
|
|
||||||
override val maxEnrollableFingerprints: Flow<Int> = flow { emit(enrollableFingerprints) }
|
override val sensorPropertiesInternal: Flow<FingerprintSensorPropertiesInternal?> =
|
||||||
|
flowOf(sensorProps.first())
|
||||||
|
|
||||||
|
override val maxEnrollableFingerprints: Flow<Int> = flowOf(enrollableFingerprints)
|
||||||
|
|
||||||
override suspend fun removeFingerprint(fp: FingerprintViewModel): Boolean {
|
override suspend fun removeFingerprint(fp: FingerprintViewModel): Boolean {
|
||||||
return enrolledFingerprintsInternal.remove(fp)
|
return enrolledFingerprintsInternal.remove(fp)
|
||||||
@@ -80,7 +81,4 @@ class FakeFingerprintManagerInteractor : FingerprintManagerInteractor {
|
|||||||
override suspend fun pressToAuthEnabled(): Boolean {
|
override suspend fun pressToAuthEnabled(): Boolean {
|
||||||
return pressToAuthEnabled
|
return pressToAuthEnabled
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun sensorPropertiesInternal(): List<FingerprintSensorPropertiesInternal> =
|
|
||||||
sensorProps
|
|
||||||
}
|
}
|
||||||
|
@@ -18,7 +18,6 @@ package com.android.settings.fingerprint2.domain.interactor
|
|||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.content.res.Resources
|
|
||||||
import android.hardware.fingerprint.Fingerprint
|
import android.hardware.fingerprint.Fingerprint
|
||||||
import android.hardware.fingerprint.FingerprintManager
|
import android.hardware.fingerprint.FingerprintManager
|
||||||
import android.hardware.fingerprint.FingerprintManager.CryptoObject
|
import android.hardware.fingerprint.FingerprintManager.CryptoObject
|
||||||
@@ -51,8 +50,11 @@ import org.mockito.ArgumentMatchers.eq
|
|||||||
import org.mockito.ArgumentMatchers.nullable
|
import org.mockito.ArgumentMatchers.nullable
|
||||||
import org.mockito.Mock
|
import org.mockito.Mock
|
||||||
import org.mockito.Mockito
|
import org.mockito.Mockito
|
||||||
|
import org.mockito.Mockito.verify
|
||||||
|
import org.mockito.Mockito.`when`
|
||||||
import org.mockito.junit.MockitoJUnit
|
import org.mockito.junit.MockitoJUnit
|
||||||
import org.mockito.junit.MockitoJUnitRunner
|
import org.mockito.junit.MockitoJUnitRunner
|
||||||
|
import org.mockito.stubbing.OngoingStubbing
|
||||||
|
|
||||||
@RunWith(MockitoJUnitRunner::class)
|
@RunWith(MockitoJUnitRunner::class)
|
||||||
class FingerprintManagerInteractorTest {
|
class FingerprintManagerInteractorTest {
|
||||||
@@ -82,8 +84,7 @@ class FingerprintManagerInteractorTest {
|
|||||||
@Test
|
@Test
|
||||||
fun testEmptyFingerprints() =
|
fun testEmptyFingerprints() =
|
||||||
testScope.runTest {
|
testScope.runTest {
|
||||||
Mockito.`when`(fingerprintManager.getEnrolledFingerprints(Mockito.anyInt()))
|
whenever(fingerprintManager.getEnrolledFingerprints(anyInt())).thenReturn(emptyList())
|
||||||
.thenReturn(emptyList())
|
|
||||||
|
|
||||||
val emptyFingerprintList: List<Fingerprint> = emptyList()
|
val emptyFingerprintList: List<Fingerprint> = emptyList()
|
||||||
assertThat(underTest.enrolledFingerprints.last()).isEqualTo(emptyFingerprintList)
|
assertThat(underTest.enrolledFingerprints.last()).isEqualTo(emptyFingerprintList)
|
||||||
@@ -94,8 +95,7 @@ class FingerprintManagerInteractorTest {
|
|||||||
testScope.runTest {
|
testScope.runTest {
|
||||||
val expected = Fingerprint("Finger 1,", 2, 3L)
|
val expected = Fingerprint("Finger 1,", 2, 3L)
|
||||||
val fingerprintList: List<Fingerprint> = listOf(expected)
|
val fingerprintList: List<Fingerprint> = listOf(expected)
|
||||||
Mockito.`when`(fingerprintManager.getEnrolledFingerprints(Mockito.anyInt()))
|
whenever(fingerprintManager.getEnrolledFingerprints(anyInt())).thenReturn(fingerprintList)
|
||||||
.thenReturn(fingerprintList)
|
|
||||||
|
|
||||||
val list = underTest.enrolledFingerprints.last()
|
val list = underTest.enrolledFingerprints.last()
|
||||||
assertThat(list.size).isEqualTo(fingerprintList.size)
|
assertThat(list.size).isEqualTo(fingerprintList.size)
|
||||||
@@ -108,21 +108,22 @@ class FingerprintManagerInteractorTest {
|
|||||||
@Test
|
@Test
|
||||||
fun testCanEnrollFingerprint() =
|
fun testCanEnrollFingerprint() =
|
||||||
testScope.runTest {
|
testScope.runTest {
|
||||||
val mockContext = Mockito.mock(Context::class.java)
|
val fingerprintList1: List<Fingerprint> =
|
||||||
val resources = Mockito.mock(Resources::class.java)
|
listOf(
|
||||||
Mockito.`when`(mockContext.resources).thenReturn(resources)
|
Fingerprint("Finger 1,", 2, 3L),
|
||||||
Mockito.`when`(resources.getInteger(anyInt())).thenReturn(3)
|
Fingerprint("Finger 2,", 3, 3L),
|
||||||
underTest =
|
Fingerprint("Finger 3,", 4, 3L)
|
||||||
FingerprintManagerInteractorImpl(
|
|
||||||
mockContext,
|
|
||||||
backgroundDispatcher,
|
|
||||||
fingerprintManager,
|
|
||||||
gateKeeperPasswordProvider,
|
|
||||||
pressToAuthProvider,
|
|
||||||
)
|
)
|
||||||
|
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)
|
||||||
|
|
||||||
assertThat(underTest.canEnrollFingerprints(2).last()).isTrue()
|
assertThat(underTest.canEnrollFingerprints.last()).isTrue()
|
||||||
assertThat(underTest.canEnrollFingerprints(3).last()).isFalse()
|
assertThat(underTest.canEnrollFingerprints.last()).isFalse()
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -132,7 +133,7 @@ class FingerprintManagerInteractorTest {
|
|||||||
val challenge = 100L
|
val challenge = 100L
|
||||||
val intent = Intent()
|
val intent = Intent()
|
||||||
intent.putExtra(ChooseLockSettingsHelper.EXTRA_KEY_GK_PW_HANDLE, challenge)
|
intent.putExtra(ChooseLockSettingsHelper.EXTRA_KEY_GK_PW_HANDLE, challenge)
|
||||||
Mockito.`when`(
|
whenever(
|
||||||
gateKeeperPasswordProvider.requestGatekeeperHat(
|
gateKeeperPasswordProvider.requestGatekeeperHat(
|
||||||
any(Intent::class.java),
|
any(Intent::class.java),
|
||||||
anyLong(),
|
anyLong(),
|
||||||
@@ -148,8 +149,7 @@ class FingerprintManagerInteractorTest {
|
|||||||
val job = testScope.launch { result = underTest.generateChallenge(1L) }
|
val job = testScope.launch { result = underTest.generateChallenge(1L) }
|
||||||
runCurrent()
|
runCurrent()
|
||||||
|
|
||||||
Mockito.verify(fingerprintManager)
|
verify(fingerprintManager).generateChallenge(anyInt(), capture(generateChallengeCallback))
|
||||||
.generateChallenge(anyInt(), capture(generateChallengeCallback))
|
|
||||||
generateChallengeCallback.value.onChallengeGenerated(1, 2, challenge)
|
generateChallengeCallback.value.onChallengeGenerated(1, 2, challenge)
|
||||||
|
|
||||||
runCurrent()
|
runCurrent()
|
||||||
@@ -173,7 +173,7 @@ class FingerprintManagerInteractorTest {
|
|||||||
testScope.launch { result = underTest.removeFingerprint(fingerprintViewModelToRemove) }
|
testScope.launch { result = underTest.removeFingerprint(fingerprintViewModelToRemove) }
|
||||||
runCurrent()
|
runCurrent()
|
||||||
|
|
||||||
Mockito.verify(fingerprintManager)
|
verify(fingerprintManager)
|
||||||
.remove(any(Fingerprint::class.java), anyInt(), capture(removalCallback))
|
.remove(any(Fingerprint::class.java), anyInt(), capture(removalCallback))
|
||||||
removalCallback.value.onRemovalSucceeded(fingerprintToRemove, 1)
|
removalCallback.value.onRemovalSucceeded(fingerprintToRemove, 1)
|
||||||
|
|
||||||
@@ -197,7 +197,7 @@ class FingerprintManagerInteractorTest {
|
|||||||
testScope.launch { result = underTest.removeFingerprint(fingerprintViewModelToRemove) }
|
testScope.launch { result = underTest.removeFingerprint(fingerprintViewModelToRemove) }
|
||||||
runCurrent()
|
runCurrent()
|
||||||
|
|
||||||
Mockito.verify(fingerprintManager)
|
verify(fingerprintManager)
|
||||||
.remove(any(Fingerprint::class.java), anyInt(), capture(removalCallback))
|
.remove(any(Fingerprint::class.java), anyInt(), capture(removalCallback))
|
||||||
removalCallback.value.onRemovalError(
|
removalCallback.value.onRemovalError(
|
||||||
fingerprintToRemove,
|
fingerprintToRemove,
|
||||||
@@ -218,8 +218,7 @@ class FingerprintManagerInteractorTest {
|
|||||||
|
|
||||||
underTest.renameFingerprint(fingerprintToRename, "Woo")
|
underTest.renameFingerprint(fingerprintToRename, "Woo")
|
||||||
|
|
||||||
Mockito.verify(fingerprintManager)
|
verify(fingerprintManager).rename(eq(fingerprintToRename.fingerId), anyInt(), safeEq("Woo"))
|
||||||
.rename(eq(fingerprintToRename.fingerId), anyInt(), safeEq("Woo"))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -235,7 +234,7 @@ class FingerprintManagerInteractorTest {
|
|||||||
|
|
||||||
runCurrent()
|
runCurrent()
|
||||||
|
|
||||||
Mockito.verify(fingerprintManager)
|
verify(fingerprintManager)
|
||||||
.authenticate(
|
.authenticate(
|
||||||
nullable(CryptoObject::class.java),
|
nullable(CryptoObject::class.java),
|
||||||
any(CancellationSignal::class.java),
|
any(CancellationSignal::class.java),
|
||||||
@@ -263,7 +262,7 @@ class FingerprintManagerInteractorTest {
|
|||||||
|
|
||||||
runCurrent()
|
runCurrent()
|
||||||
|
|
||||||
Mockito.verify(fingerprintManager)
|
verify(fingerprintManager)
|
||||||
.authenticate(
|
.authenticate(
|
||||||
nullable(CryptoObject::class.java),
|
nullable(CryptoObject::class.java),
|
||||||
any(CancellationSignal::class.java),
|
any(CancellationSignal::class.java),
|
||||||
@@ -284,4 +283,5 @@ class FingerprintManagerInteractorTest {
|
|||||||
private fun <T : Any> safeEq(value: T): T = eq(value) ?: value
|
private fun <T : Any> safeEq(value: T): T = eq(value) ?: value
|
||||||
private fun <T> capture(argumentCaptor: ArgumentCaptor<T>): T = argumentCaptor.capture()
|
private fun <T> capture(argumentCaptor: ArgumentCaptor<T>): T = argumentCaptor.capture()
|
||||||
private fun <T> any(type: Class<T>): T = Mockito.any<T>(type)
|
private fun <T> any(type: Class<T>): T = Mockito.any<T>(type)
|
||||||
|
private fun <T> whenever(methodCall: T): OngoingStubbing<T> = `when`(methodCall)
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user