Merge "Moving FingerprintSettings to Kotlin"
This commit is contained in:
committed by
Android (Google) Code Review
commit
cde233abf0
@@ -69,6 +69,7 @@ android_library {
|
|||||||
"androidx.appcompat_appcompat",
|
"androidx.appcompat_appcompat",
|
||||||
"androidx.cardview_cardview",
|
"androidx.cardview_cardview",
|
||||||
"androidx.compose.runtime_runtime-livedata",
|
"androidx.compose.runtime_runtime-livedata",
|
||||||
|
"androidx.activity_activity-ktx",
|
||||||
"androidx.preference_preference",
|
"androidx.preference_preference",
|
||||||
"androidx.recyclerview_recyclerview",
|
"androidx.recyclerview_recyclerview",
|
||||||
"androidx.window_window",
|
"androidx.window_window",
|
||||||
|
@@ -4914,6 +4914,20 @@
|
|||||||
<activity android:name=".spa.SpaBridgeActivity" android:exported="false"/>
|
<activity android:name=".spa.SpaBridgeActivity" android:exported="false"/>
|
||||||
<activity android:name=".spa.SpaAppBridgeActivity" android:exported="false"/>
|
<activity android:name=".spa.SpaAppBridgeActivity" android:exported="false"/>
|
||||||
|
|
||||||
|
<activity android:name=".Settings$FingerprintSettingsActivityV2"
|
||||||
|
android:label="@string/security_settings_fingerprint_preference_title"
|
||||||
|
android:exported="false"
|
||||||
|
android:icon="@drawable/ic_fingerprint_header">
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="android.settings.FINGERPRINT_SETTINGS_V2" />
|
||||||
|
<category android:name="android.intent.category.DEFAULT" />
|
||||||
|
</intent-filter>
|
||||||
|
<meta-data android:name="com.android.settings.FRAGMENT_CLASS"
|
||||||
|
android:value="com.android.settings.biometrics.fingerprint2.ui.fragment.FingerprintSettingsV2Fragment" />
|
||||||
|
<meta-data android:name="com.android.settings.HIGHLIGHT_MENU_KEY"
|
||||||
|
android:value="@string/menu_key_security"/>
|
||||||
|
</activity>
|
||||||
|
|
||||||
<activity-alias android:name="UsageStatsActivity"
|
<activity-alias android:name="UsageStatsActivity"
|
||||||
android:exported="true"
|
android:exported="true"
|
||||||
android:label="@string/testing_usage_stats"
|
android:label="@string/testing_usage_stats"
|
||||||
|
18
res/xml/security_settings_fingerprint_limbo.xml
Normal file
18
res/xml/security_settings_fingerprint_limbo.xml
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!--
|
||||||
|
~ 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.
|
||||||
|
-->
|
||||||
|
|
||||||
|
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"/>
|
@@ -73,6 +73,7 @@ public class Settings extends SettingsActivity {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static class FingerprintSettingsActivity extends SettingsActivity { /* empty */ }
|
public static class FingerprintSettingsActivity extends SettingsActivity { /* empty */ }
|
||||||
|
public static class FingerprintSettingsActivityV2 extends SettingsActivity { /* empty */ }
|
||||||
public static class CombinedBiometricSettingsActivity extends SettingsActivity { /* empty */ }
|
public static class CombinedBiometricSettingsActivity extends SettingsActivity { /* empty */ }
|
||||||
public static class CombinedBiometricProfileSettingsActivity extends SettingsActivity { /* empty */ }
|
public static class CombinedBiometricProfileSettingsActivity extends SettingsActivity { /* empty */ }
|
||||||
public static class TetherSettingsActivity extends SettingsActivity {
|
public static class TetherSettingsActivity extends SettingsActivity {
|
||||||
|
@@ -0,0 +1,128 @@
|
|||||||
|
/*
|
||||||
|
* 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.binder
|
||||||
|
|
||||||
|
import androidx.lifecycle.LifecycleCoroutineScope
|
||||||
|
import com.android.settings.biometrics.fingerprint2.ui.fragment.FingerprintSettingsV2Fragment
|
||||||
|
import com.android.settings.biometrics.fingerprint2.ui.viewmodel.EnrollAdditionalFingerprint
|
||||||
|
import com.android.settings.biometrics.fingerprint2.ui.viewmodel.EnrollFirstFingerprint
|
||||||
|
import com.android.settings.biometrics.fingerprint2.ui.viewmodel.FingerprintSettingsViewModel
|
||||||
|
import com.android.settings.biometrics.fingerprint2.ui.viewmodel.FinishSettings
|
||||||
|
import com.android.settings.biometrics.fingerprint2.ui.viewmodel.FinishSettingsWithResult
|
||||||
|
import com.android.settings.biometrics.fingerprint2.ui.viewmodel.LaunchConfirmDeviceCredential
|
||||||
|
import com.android.settings.biometrics.fingerprint2.ui.viewmodel.ShowSettings
|
||||||
|
import kotlinx.coroutines.flow.filterNotNull
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Binds a [FingerprintSettingsViewModel] to a [FingerprintSettingsV2Fragment]
|
||||||
|
*/
|
||||||
|
object FingerprintViewBinder {
|
||||||
|
|
||||||
|
interface Binding {
|
||||||
|
fun onConfirmDevice(wasSuccessful: Boolean, theGateKeeperPasswordHandle: Long?)
|
||||||
|
fun onEnrollSuccess()
|
||||||
|
fun onEnrollAdditionalFailure()
|
||||||
|
fun onEnrollFirstFailure(reason: String)
|
||||||
|
fun onEnrollFirstFailure(reason: String, resultCode: Int)
|
||||||
|
fun onEnrollFirst(token: ByteArray?, keyChallenge: Long?)
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Initial listener for the first enrollment request */
|
||||||
|
fun bind(
|
||||||
|
viewModel: FingerprintSettingsViewModel,
|
||||||
|
lifecycleScope: LifecycleCoroutineScope,
|
||||||
|
token: ByteArray?,
|
||||||
|
challenge: Long?,
|
||||||
|
launchFullFingerprintEnrollment: (
|
||||||
|
userId: Int,
|
||||||
|
gateKeeperPasswordHandle: Long?,
|
||||||
|
challenge: Long?,
|
||||||
|
challengeToken: ByteArray?
|
||||||
|
) -> Unit,
|
||||||
|
launchAddFingerprint: (userId: Int, challengeToken: ByteArray?) -> Unit,
|
||||||
|
launchConfirmOrChooseLock: (userId: Int) -> Unit,
|
||||||
|
finish: () -> Unit,
|
||||||
|
setResultExternal: (resultCode: Int) -> Unit,
|
||||||
|
): Binding {
|
||||||
|
|
||||||
|
lifecycleScope.launch {
|
||||||
|
viewModel.nextStep.filterNotNull().collect { nextStep ->
|
||||||
|
when (nextStep) {
|
||||||
|
is EnrollFirstFingerprint -> launchFullFingerprintEnrollment(
|
||||||
|
nextStep.userId,
|
||||||
|
nextStep.gateKeeperPasswordHandle,
|
||||||
|
nextStep.challenge,
|
||||||
|
nextStep.challengeToken
|
||||||
|
)
|
||||||
|
|
||||||
|
is EnrollAdditionalFingerprint -> launchAddFingerprint(
|
||||||
|
nextStep.userId, nextStep.challengeToken
|
||||||
|
)
|
||||||
|
|
||||||
|
is LaunchConfirmDeviceCredential -> launchConfirmOrChooseLock(nextStep.userId)
|
||||||
|
|
||||||
|
is FinishSettings -> {
|
||||||
|
println("Finishing due to ${nextStep.reason}")
|
||||||
|
finish()
|
||||||
|
}
|
||||||
|
|
||||||
|
is FinishSettingsWithResult -> {
|
||||||
|
println("Finishing with result ${nextStep.result} due to ${nextStep.reason}")
|
||||||
|
setResultExternal(nextStep.result)
|
||||||
|
finish()
|
||||||
|
}
|
||||||
|
|
||||||
|
is ShowSettings -> println("show settings")
|
||||||
|
}
|
||||||
|
|
||||||
|
viewModel.onUiCommandExecuted()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
viewModel.updateTokenAndChallenge(token, if (challenge == -1L) null else challenge)
|
||||||
|
|
||||||
|
return object : Binding {
|
||||||
|
override fun onConfirmDevice(
|
||||||
|
wasSuccessful: Boolean, theGateKeeperPasswordHandle: Long?
|
||||||
|
) {
|
||||||
|
viewModel.onConfirmDevice(wasSuccessful, theGateKeeperPasswordHandle)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onEnrollSuccess() {
|
||||||
|
viewModel.onEnrollSuccess()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onEnrollAdditionalFailure() {
|
||||||
|
viewModel.onEnrollAdditionalFailure()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onEnrollFirstFailure(reason: String) {
|
||||||
|
viewModel.onEnrollFirstFailure(reason)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onEnrollFirstFailure(reason: String, resultCode: Int) {
|
||||||
|
viewModel.onEnrollFirstFailure(reason, resultCode)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onEnrollFirst(token: ByteArray?, keyChallenge: Long?) {
|
||||||
|
viewModel.onEnrollFirst(token, keyChallenge)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@@ -0,0 +1,252 @@
|
|||||||
|
/*
|
||||||
|
* 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.fragment
|
||||||
|
|
||||||
|
import android.app.Activity
|
||||||
|
import android.app.settings.SettingsEnums
|
||||||
|
import android.content.Context.FINGERPRINT_SERVICE
|
||||||
|
import android.content.Intent
|
||||||
|
import android.hardware.fingerprint.FingerprintManager
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.util.FeatureFlagUtils
|
||||||
|
import android.util.Log
|
||||||
|
import androidx.activity.result.contract.ActivityResultContracts
|
||||||
|
import androidx.lifecycle.ViewModelProvider
|
||||||
|
import androidx.lifecycle.lifecycleScope
|
||||||
|
import com.android.settings.R
|
||||||
|
import com.android.settings.Utils
|
||||||
|
import com.android.settings.biometrics.BiometricEnrollBase
|
||||||
|
import com.android.settings.biometrics.fingerprint.FingerprintEnrollEnrolling
|
||||||
|
import com.android.settings.biometrics.fingerprint.FingerprintEnrollIntroductionInternal
|
||||||
|
import com.android.settings.biometrics.fingerprint2.ui.binder.FingerprintViewBinder
|
||||||
|
import com.android.settings.biometrics.fingerprint2.ui.viewmodel.FingerprintSettingsViewModel
|
||||||
|
import com.android.settings.core.SettingsBaseActivity
|
||||||
|
import com.android.settings.dashboard.DashboardFragment
|
||||||
|
import com.android.settings.password.ChooseLockGeneric
|
||||||
|
import com.android.settings.password.ChooseLockSettingsHelper
|
||||||
|
import com.android.settingslib.transition.SettingsTransitionHelper
|
||||||
|
|
||||||
|
const val TAG = "FingerprintSettingsV2Fragment"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A class responsible for showing FingerprintSettings. Typical activity Flows are
|
||||||
|
* 1. Settings > FingerprintSettings > PIN/PATTERN/PASS -> FingerprintSettings
|
||||||
|
* 2. FingerprintSettings -> FingerprintEnrollment fow
|
||||||
|
*
|
||||||
|
* This page typically allows for
|
||||||
|
* 1. Fingerprint deletion
|
||||||
|
* 2. Fingerprint enrollment
|
||||||
|
* 3. Renaming a fingerprint
|
||||||
|
* 4. Enabling/Disabling a feature
|
||||||
|
*/
|
||||||
|
class FingerprintSettingsV2Fragment : DashboardFragment() {
|
||||||
|
private lateinit var binding: FingerprintViewBinder.Binding
|
||||||
|
|
||||||
|
private val launchFirstEnrollmentListener =
|
||||||
|
registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
|
||||||
|
|
||||||
|
val resultCode = result.resultCode
|
||||||
|
val data = result.data
|
||||||
|
|
||||||
|
Log.d(
|
||||||
|
TAG, "onEnrollFirstFingerprint($resultCode, $data)"
|
||||||
|
)
|
||||||
|
if (resultCode != BiometricEnrollBase.RESULT_FINISHED || data == null) {
|
||||||
|
if (resultCode == BiometricEnrollBase.RESULT_TIMEOUT) {
|
||||||
|
binding.onEnrollFirstFailure(
|
||||||
|
"Received RESULT_TIMEOUT when enrolling", resultCode
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
binding.onEnrollFirstFailure("Incorrect resultCode or data was null")
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
val token =
|
||||||
|
data.getByteArrayExtra(ChooseLockSettingsHelper.EXTRA_KEY_CHALLENGE_TOKEN)
|
||||||
|
val keyChallenge = data.getExtra(BiometricEnrollBase.EXTRA_KEY_CHALLENGE) as Long?
|
||||||
|
binding.onEnrollFirst(token, keyChallenge)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Result listener for launching enrollments **after** a user has reached the settings page. */
|
||||||
|
private val launchAdditionalFingerprintListener =
|
||||||
|
registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
|
||||||
|
val resultCode = result.resultCode
|
||||||
|
Log.d(
|
||||||
|
TAG, "onEnrollAdditionalFingerprint($resultCode)"
|
||||||
|
)
|
||||||
|
|
||||||
|
if (resultCode == BiometricEnrollBase.RESULT_TIMEOUT) {
|
||||||
|
binding.onEnrollAdditionalFailure()
|
||||||
|
} else {
|
||||||
|
binding.onEnrollSuccess()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Result listener for ChooseLock activity flow. */
|
||||||
|
private val confirmDeviceResultListener =
|
||||||
|
registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
|
||||||
|
val resultCode = result.resultCode
|
||||||
|
val data = result.data
|
||||||
|
onConfirmDevice(resultCode, data)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
|
||||||
|
// This is needed to support ChooseLockSettingBuilder...show(). All other activity
|
||||||
|
// calls should use the registerForActivity method call.
|
||||||
|
super.onActivityResult(requestCode, resultCode, data)
|
||||||
|
val wasSuccessful =
|
||||||
|
resultCode == BiometricEnrollBase.RESULT_FINISHED || resultCode == Activity.RESULT_OK
|
||||||
|
val gateKeeperPasswordHandle =
|
||||||
|
data?.getExtra(ChooseLockSettingsHelper.EXTRA_KEY_GK_PW_HANDLE) as Long?
|
||||||
|
binding.onConfirmDevice(wasSuccessful, gateKeeperPasswordHandle)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
override fun onCreate(icicle: Bundle?) {
|
||||||
|
super.onCreate(icicle)
|
||||||
|
if (!FeatureFlagUtils.isEnabled(
|
||||||
|
context, FeatureFlagUtils.SETTINGS_BIOMETRICS2_FINGERPRINT_SETTINGS
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
Log.d(
|
||||||
|
TAG, "Finishing due to feature not being enabled"
|
||||||
|
)
|
||||||
|
finish()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
val viewModel = ViewModelProvider(
|
||||||
|
this, FingerprintSettingsViewModel.FingerprintSettingsViewModelFactory(
|
||||||
|
requireContext().applicationContext.userId, requireContext().getSystemService(
|
||||||
|
FINGERPRINT_SERVICE
|
||||||
|
) as FingerprintManager
|
||||||
|
)
|
||||||
|
)[FingerprintSettingsViewModel::class.java]
|
||||||
|
|
||||||
|
val token = intent.getByteArrayExtra(ChooseLockSettingsHelper.EXTRA_KEY_CHALLENGE_TOKEN)
|
||||||
|
val challenge = intent.getLongExtra(BiometricEnrollBase.EXTRA_KEY_CHALLENGE, -1L)
|
||||||
|
|
||||||
|
binding = FingerprintViewBinder.bind(
|
||||||
|
viewModel,
|
||||||
|
lifecycleScope,
|
||||||
|
token,
|
||||||
|
challenge,
|
||||||
|
::launchFullFingerprintEnrollment,
|
||||||
|
::launchAddFingerprint,
|
||||||
|
::launchConfirmOrChooseLock,
|
||||||
|
::finish,
|
||||||
|
::setResultExternal,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getMetricsCategory(): Int {
|
||||||
|
return SettingsEnums.FINGERPRINT
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getPreferenceScreenResId(): Int {
|
||||||
|
return R.xml.security_settings_fingerprint_limbo
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getLogTag(): String {
|
||||||
|
return TAG
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helper function that will try and launch confirm lock, if that fails we will prompt user
|
||||||
|
* to choose a PIN/PATTERN/PASS.
|
||||||
|
*/
|
||||||
|
private fun launchConfirmOrChooseLock(userId: Int) {
|
||||||
|
val intent = Intent()
|
||||||
|
val builder = ChooseLockSettingsHelper.Builder(requireActivity(), this)
|
||||||
|
val launched = builder.setRequestCode(BiometricEnrollBase.CONFIRM_REQUEST)
|
||||||
|
.setTitle(getString(R.string.security_settings_fingerprint_preference_title))
|
||||||
|
.setRequestGatekeeperPasswordHandle(true).setUserId(userId).setForegroundOnly(true)
|
||||||
|
.setReturnCredentials(true).show()
|
||||||
|
if (!launched) {
|
||||||
|
intent.setClassName(
|
||||||
|
Utils.SETTINGS_PACKAGE_NAME, ChooseLockGeneric::class.java.name
|
||||||
|
)
|
||||||
|
intent.putExtra(
|
||||||
|
ChooseLockGeneric.ChooseLockGenericFragment.HIDE_INSECURE_OPTIONS, true
|
||||||
|
)
|
||||||
|
intent.putExtra(ChooseLockSettingsHelper.EXTRA_KEY_REQUEST_GK_PW_HANDLE, true)
|
||||||
|
intent.putExtra(Intent.EXTRA_USER_ID, userId)
|
||||||
|
confirmDeviceResultListener.launch(intent)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helper for confirming a PIN/PATTERN/PASS
|
||||||
|
*/
|
||||||
|
private fun onConfirmDevice(resultCode: Int, data: Intent?) {
|
||||||
|
val wasSuccessful =
|
||||||
|
resultCode == BiometricEnrollBase.RESULT_FINISHED || resultCode == Activity.RESULT_OK
|
||||||
|
val gateKeeperPasswordHandle =
|
||||||
|
data?.getExtra(ChooseLockSettingsHelper.EXTRA_KEY_GK_PW_HANDLE) as Long?
|
||||||
|
binding.onConfirmDevice(wasSuccessful, gateKeeperPasswordHandle)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helper function to launch fingerprint enrollment(This should be the default behavior
|
||||||
|
* when a user enters their PIN/PATTERN/PASS and no fingerprints are enrolled.
|
||||||
|
*/
|
||||||
|
private fun launchFullFingerprintEnrollment(
|
||||||
|
userId: Int,
|
||||||
|
gateKeeperPasswordHandle: Long?,
|
||||||
|
challenge: Long?,
|
||||||
|
challengeToken: ByteArray?,
|
||||||
|
) {
|
||||||
|
val intent = Intent()
|
||||||
|
intent.setClassName(
|
||||||
|
Utils.SETTINGS_PACKAGE_NAME, FingerprintEnrollIntroductionInternal::class.java.name
|
||||||
|
)
|
||||||
|
intent.putExtra(BiometricEnrollBase.EXTRA_FROM_SETTINGS_SUMMARY, true)
|
||||||
|
intent.putExtra(
|
||||||
|
SettingsBaseActivity.EXTRA_PAGE_TRANSITION_TYPE,
|
||||||
|
SettingsTransitionHelper.TransitionType.TRANSITION_SLIDE
|
||||||
|
)
|
||||||
|
|
||||||
|
intent.putExtra(Intent.EXTRA_USER_ID, userId)
|
||||||
|
|
||||||
|
if (gateKeeperPasswordHandle != null) {
|
||||||
|
intent.putExtra(
|
||||||
|
ChooseLockSettingsHelper.EXTRA_KEY_GK_PW_HANDLE, gateKeeperPasswordHandle
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
intent.putExtra(
|
||||||
|
ChooseLockSettingsHelper.EXTRA_KEY_CHALLENGE_TOKEN, challengeToken
|
||||||
|
)
|
||||||
|
intent.putExtra(BiometricEnrollBase.EXTRA_KEY_CHALLENGE, challenge)
|
||||||
|
}
|
||||||
|
launchFirstEnrollmentListener.launch(intent)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun setResultExternal(resultCode: Int) {
|
||||||
|
setResult(resultCode)
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Helper to launch an add fingerprint request */
|
||||||
|
private fun launchAddFingerprint(userId: Int, challengeToken: ByteArray?) {
|
||||||
|
val intent = Intent()
|
||||||
|
intent.setClassName(
|
||||||
|
Utils.SETTINGS_PACKAGE_NAME, FingerprintEnrollEnrolling::class.qualifiedName.toString()
|
||||||
|
)
|
||||||
|
intent.putExtra(Intent.EXTRA_USER_ID, userId)
|
||||||
|
intent.putExtra(ChooseLockSettingsHelper.EXTRA_KEY_CHALLENGE_TOKEN, challengeToken)
|
||||||
|
launchAdditionalFingerprintListener.launch(intent)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@@ -0,0 +1,187 @@
|
|||||||
|
/*
|
||||||
|
* 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.viewmodel
|
||||||
|
|
||||||
|
import android.hardware.fingerprint.FingerprintManager
|
||||||
|
import androidx.lifecycle.ViewModel
|
||||||
|
import androidx.lifecycle.ViewModelProvider
|
||||||
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
|
import kotlinx.coroutines.flow.asStateFlow
|
||||||
|
import kotlinx.coroutines.flow.update
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Models the UI state for fingerprint settings.
|
||||||
|
*/
|
||||||
|
class FingerprintSettingsViewModel(
|
||||||
|
private val userId: Int,
|
||||||
|
gateKeeperPassword: Long?,
|
||||||
|
theChallenge: Long?,
|
||||||
|
theChallengeToken: ByteArray?,
|
||||||
|
private val fingerprintManager: FingerprintManager
|
||||||
|
) : ViewModel() {
|
||||||
|
|
||||||
|
private val _nextStep: MutableStateFlow<NextStepViewModel?> = MutableStateFlow(null)
|
||||||
|
/**
|
||||||
|
* This flow represents the high level state for the FingerprintSettingsV2Fragment. The
|
||||||
|
* consumer of this flow should call [onUiCommandExecuted] which will set the state to null,
|
||||||
|
* confirming that the UI has consumed the last command and is ready to consume another
|
||||||
|
* command.
|
||||||
|
*/
|
||||||
|
val nextStep = _nextStep.asStateFlow()
|
||||||
|
|
||||||
|
|
||||||
|
private var gateKeeperPasswordHandle: Long? = gateKeeperPassword
|
||||||
|
private var challenge: Long? = theChallenge
|
||||||
|
private var challengeToken: ByteArray? = theChallengeToken
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Indicates to the view model that a confirm device credential action has been completed
|
||||||
|
* with a [theGateKeeperPasswordHandle] which will be used for [FingerprintManager]
|
||||||
|
* operations such as [FingerprintManager.enroll].
|
||||||
|
*/
|
||||||
|
fun onConfirmDevice(wasSuccessful: Boolean, theGateKeeperPasswordHandle: Long?) {
|
||||||
|
|
||||||
|
if (!wasSuccessful) {
|
||||||
|
launchFinishSettings("ConfirmDeviceCredential was unsuccessful")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (theGateKeeperPasswordHandle == null) {
|
||||||
|
launchFinishSettings("ConfirmDeviceCredential gatekeeper password was null")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
gateKeeperPasswordHandle = theGateKeeperPasswordHandle
|
||||||
|
launchEnrollNextStep()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Notifies that enrollment was successful.
|
||||||
|
*/
|
||||||
|
fun onEnrollSuccess() {
|
||||||
|
_nextStep.update {
|
||||||
|
ShowSettings(userId)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Notifies that an additional enrollment failed.
|
||||||
|
*/
|
||||||
|
fun onEnrollAdditionalFailure() {
|
||||||
|
launchFinishSettings("Failed to enroll additional fingerprint")
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Notifies that the first enrollment failed.
|
||||||
|
*/
|
||||||
|
fun onEnrollFirstFailure(reason: String) {
|
||||||
|
launchFinishSettings(reason)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Notifies that first enrollment failed (with resultCode)
|
||||||
|
*/
|
||||||
|
fun onEnrollFirstFailure(reason: String, resultCode: Int) {
|
||||||
|
launchFinishSettings(reason, resultCode)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Notifies that a users first enrollment succeeded.
|
||||||
|
*/
|
||||||
|
fun onEnrollFirst(token: ByteArray?, keyChallenge: Long?) {
|
||||||
|
if (token == null) {
|
||||||
|
launchFinishSettings("Error, empty token")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (keyChallenge == null) {
|
||||||
|
launchFinishSettings("Error, empty keyChallenge")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
challengeToken = token
|
||||||
|
challenge = keyChallenge
|
||||||
|
|
||||||
|
_nextStep.update {
|
||||||
|
ShowSettings(userId)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Indicates if this settings activity has been called with correct token and challenge
|
||||||
|
* and that we do not need to launch confirm device credential.
|
||||||
|
*/
|
||||||
|
fun updateTokenAndChallenge(token: ByteArray?, theChallenge: Long?) {
|
||||||
|
challengeToken = token
|
||||||
|
challenge = theChallenge
|
||||||
|
if (challengeToken == null) {
|
||||||
|
_nextStep.update {
|
||||||
|
LaunchConfirmDeviceCredential(userId)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
launchEnrollNextStep()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Indicates a UI command has been consumed by the UI, and the logic can send another
|
||||||
|
* UI command.
|
||||||
|
*/
|
||||||
|
fun onUiCommandExecuted() {
|
||||||
|
_nextStep.update {
|
||||||
|
null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun launchEnrollNextStep() {
|
||||||
|
if (fingerprintManager.getEnrolledFingerprints(userId).isEmpty()) {
|
||||||
|
_nextStep.update {
|
||||||
|
EnrollFirstFingerprint(userId, gateKeeperPasswordHandle, challenge, challengeToken)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
_nextStep.update {
|
||||||
|
ShowSettings(userId)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun launchFinishSettings(reason: String) {
|
||||||
|
_nextStep.update {
|
||||||
|
FinishSettings(reason)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun launchFinishSettings(reason: String, errorCode: Int) {
|
||||||
|
_nextStep.update {
|
||||||
|
FinishSettingsWithResult(errorCode, reason)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class FingerprintSettingsViewModelFactory(
|
||||||
|
private val userId: Int,
|
||||||
|
private val fingerprintManager: FingerprintManager,
|
||||||
|
) : ViewModelProvider.Factory {
|
||||||
|
|
||||||
|
@Suppress("UNCHECKED_CAST")
|
||||||
|
override fun <T : ViewModel> create(
|
||||||
|
modelClass: Class<T>,
|
||||||
|
): T {
|
||||||
|
|
||||||
|
return FingerprintSettingsViewModel(
|
||||||
|
userId, null, null, null, fingerprintManager
|
||||||
|
) as T
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,48 @@
|
|||||||
|
/*
|
||||||
|
* 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.viewmodel
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A class to represent a next step for FingerprintSettings. This is typically to perform an action
|
||||||
|
* such that launches another activity such as EnrollFirstFingerprint() or
|
||||||
|
* LaunchConfirmDeviceCredential().
|
||||||
|
*/
|
||||||
|
sealed class NextStepViewModel
|
||||||
|
|
||||||
|
data class EnrollFirstFingerprint(
|
||||||
|
val userId: Int, val gateKeeperPasswordHandle: Long?,
|
||||||
|
val challenge: Long?,
|
||||||
|
val challengeToken: ByteArray?,
|
||||||
|
) : NextStepViewModel()
|
||||||
|
|
||||||
|
data class EnrollAdditionalFingerprint(
|
||||||
|
val userId: Int,
|
||||||
|
val challengeToken: ByteArray?,
|
||||||
|
) : NextStepViewModel()
|
||||||
|
|
||||||
|
data class FinishSettings(
|
||||||
|
val reason: String
|
||||||
|
) : NextStepViewModel()
|
||||||
|
|
||||||
|
data class FinishSettingsWithResult(
|
||||||
|
val result: Int, val reason: String
|
||||||
|
) : NextStepViewModel()
|
||||||
|
|
||||||
|
data class ShowSettings(val userId: Int) : NextStepViewModel()
|
||||||
|
|
||||||
|
data class LaunchConfirmDeviceCredential(val userId: Int) : NextStepViewModel()
|
||||||
|
|
@@ -72,6 +72,7 @@ import com.android.settings.biometrics.combination.CombinedBiometricProfileSetti
|
|||||||
import com.android.settings.biometrics.combination.CombinedBiometricSettings;
|
import com.android.settings.biometrics.combination.CombinedBiometricSettings;
|
||||||
import com.android.settings.biometrics.face.FaceSettings;
|
import com.android.settings.biometrics.face.FaceSettings;
|
||||||
import com.android.settings.biometrics.fingerprint.FingerprintSettings;
|
import com.android.settings.biometrics.fingerprint.FingerprintSettings;
|
||||||
|
import com.android.settings.biometrics.fingerprint2.ui.fragment.FingerprintSettingsV2Fragment;
|
||||||
import com.android.settings.bluetooth.BluetoothBroadcastDialog;
|
import com.android.settings.bluetooth.BluetoothBroadcastDialog;
|
||||||
import com.android.settings.bluetooth.BluetoothDeviceDetailsFragment;
|
import com.android.settings.bluetooth.BluetoothDeviceDetailsFragment;
|
||||||
import com.android.settings.bluetooth.BluetoothFindBroadcastsFragment;
|
import com.android.settings.bluetooth.BluetoothFindBroadcastsFragment;
|
||||||
@@ -266,6 +267,7 @@ public class SettingsGateway {
|
|||||||
AssistGestureSettings.class.getName(),
|
AssistGestureSettings.class.getName(),
|
||||||
FaceSettings.class.getName(),
|
FaceSettings.class.getName(),
|
||||||
FingerprintSettings.FingerprintSettingsFragment.class.getName(),
|
FingerprintSettings.FingerprintSettingsFragment.class.getName(),
|
||||||
|
FingerprintSettingsV2Fragment.class.getName(),
|
||||||
CombinedBiometricSettings.class.getName(),
|
CombinedBiometricSettings.class.getName(),
|
||||||
CombinedBiometricProfileSettings.class.getName(),
|
CombinedBiometricProfileSettings.class.getName(),
|
||||||
SwipeToNotificationSettings.class.getName(),
|
SwipeToNotificationSettings.class.getName(),
|
||||||
|
@@ -32,6 +32,7 @@ android_test {
|
|||||||
"platform-test-annotations",
|
"platform-test-annotations",
|
||||||
"truth-prebuilt",
|
"truth-prebuilt",
|
||||||
"androidx.test.uiautomator_uiautomator",
|
"androidx.test.uiautomator_uiautomator",
|
||||||
|
"kotlinx_coroutines_test",
|
||||||
// Don't add SettingsLib libraries here - you can use them directly as they are in the
|
// Don't add SettingsLib libraries here - you can use them directly as they are in the
|
||||||
// instrumented Settings app.
|
// instrumented Settings app.
|
||||||
],
|
],
|
||||||
@@ -39,8 +40,7 @@ android_test {
|
|||||||
errorprone: {
|
errorprone: {
|
||||||
javacflags: ["-Xep:CheckReturnValue:WARN"]
|
javacflags: ["-Xep:CheckReturnValue:WARN"]
|
||||||
},
|
},
|
||||||
|
// Include all test java/kotlin files.
|
||||||
// Include all test java files.
|
|
||||||
srcs: [
|
srcs: [
|
||||||
"src/**/*.java",
|
"src/**/*.java",
|
||||||
"src/**/*.kt",
|
"src/**/*.kt",
|
||||||
|
@@ -0,0 +1,332 @@
|
|||||||
|
/*
|
||||||
|
* 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.fingerprint2.viewmodel
|
||||||
|
|
||||||
|
import android.hardware.fingerprint.Fingerprint
|
||||||
|
import android.hardware.fingerprint.FingerprintManager
|
||||||
|
import com.android.settings.biometrics.fingerprint2.ui.viewmodel.EnrollFirstFingerprint
|
||||||
|
import com.android.settings.biometrics.fingerprint2.ui.viewmodel.FinishSettings
|
||||||
|
import com.android.settings.biometrics.fingerprint2.ui.viewmodel.FinishSettingsWithResult
|
||||||
|
import com.android.settings.biometrics.fingerprint2.ui.viewmodel.LaunchConfirmDeviceCredential
|
||||||
|
import com.android.settings.biometrics.fingerprint2.ui.viewmodel.NextStepViewModel
|
||||||
|
import com.android.settings.biometrics.fingerprint2.ui.viewmodel.ShowSettings
|
||||||
|
import com.android.settings.biometrics.fingerprint2.ui.viewmodel.FingerprintSettingsViewModel
|
||||||
|
import com.google.common.truth.Truth.assertThat
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import kotlinx.coroutines.test.runCurrent
|
||||||
|
import kotlinx.coroutines.test.runTest
|
||||||
|
import org.junit.Before
|
||||||
|
import org.junit.Rule
|
||||||
|
import org.junit.Test
|
||||||
|
import org.junit.runner.RunWith
|
||||||
|
import org.mockito.Mock
|
||||||
|
import org.mockito.Mockito.anyInt
|
||||||
|
import org.mockito.junit.MockitoJUnit
|
||||||
|
import org.mockito.junit.MockitoJUnitRunner
|
||||||
|
import org.mockito.Mockito.`when` as whenever
|
||||||
|
|
||||||
|
@RunWith(MockitoJUnitRunner::class)
|
||||||
|
class FingerprintSettingsViewModelTest {
|
||||||
|
|
||||||
|
@JvmField
|
||||||
|
@Rule
|
||||||
|
var rule = MockitoJUnit.rule()
|
||||||
|
|
||||||
|
@Mock
|
||||||
|
private lateinit var fingerprintManager: FingerprintManager
|
||||||
|
private lateinit var underTest: FingerprintSettingsViewModel
|
||||||
|
private val defaultUserId = 0
|
||||||
|
|
||||||
|
@Before
|
||||||
|
fun setup() {
|
||||||
|
// @formatter:off
|
||||||
|
underTest = FingerprintSettingsViewModel.FingerprintSettingsViewModelFactory(
|
||||||
|
defaultUserId,
|
||||||
|
fingerprintManager,
|
||||||
|
).create(FingerprintSettingsViewModel::class.java)
|
||||||
|
// @formatter:on
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testNoGateKeeper_launchesConfirmDeviceCredential() = runTest {
|
||||||
|
var nextStep: NextStepViewModel? = null
|
||||||
|
val job = launch {
|
||||||
|
underTest.nextStep.collect {
|
||||||
|
nextStep = it
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
underTest.updateTokenAndChallenge(null, null)
|
||||||
|
|
||||||
|
runCurrent()
|
||||||
|
assertThat(nextStep).isEqualTo(LaunchConfirmDeviceCredential(defaultUserId))
|
||||||
|
job.cancel()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testConfirmDevice_fails() = runTest {
|
||||||
|
var nextStep: NextStepViewModel? = null
|
||||||
|
val job = launch {
|
||||||
|
underTest.nextStep.collect {
|
||||||
|
nextStep = it
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
underTest.updateTokenAndChallenge(null, null)
|
||||||
|
underTest.onConfirmDevice(false, null)
|
||||||
|
|
||||||
|
runCurrent()
|
||||||
|
|
||||||
|
assertThat(nextStep).isInstanceOf(FinishSettings::class.java)
|
||||||
|
job.cancel()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun confirmDeviceSuccess_noGateKeeper() = runTest {
|
||||||
|
var nextStep: NextStepViewModel? = null
|
||||||
|
val job = launch {
|
||||||
|
underTest.nextStep.collect {
|
||||||
|
nextStep = it
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
underTest.updateTokenAndChallenge(null, null)
|
||||||
|
underTest.onConfirmDevice(true, null)
|
||||||
|
|
||||||
|
runCurrent()
|
||||||
|
|
||||||
|
assertThat(nextStep).isInstanceOf(FinishSettings::class.java)
|
||||||
|
job.cancel()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun confirmDeviceSuccess_launchesEnrollment_ifNoPreviousEnrollments() = runTest {
|
||||||
|
whenever(fingerprintManager.getEnrolledFingerprints(anyInt())).thenReturn(emptyList())
|
||||||
|
|
||||||
|
var nextStep: NextStepViewModel? = null
|
||||||
|
val job = launch {
|
||||||
|
underTest.nextStep.collect {
|
||||||
|
nextStep = it
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
underTest.updateTokenAndChallenge(null, null)
|
||||||
|
underTest.onConfirmDevice(true, 10L)
|
||||||
|
|
||||||
|
runCurrent()
|
||||||
|
|
||||||
|
assertThat(nextStep).isEqualTo(EnrollFirstFingerprint(defaultUserId, 10L, null, null))
|
||||||
|
job.cancel()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun firstEnrollment_fails() = runTest {
|
||||||
|
whenever(fingerprintManager.getEnrolledFingerprints(anyInt())).thenReturn(emptyList())
|
||||||
|
|
||||||
|
var nextStep: NextStepViewModel? = null
|
||||||
|
val job = launch {
|
||||||
|
underTest.nextStep.collect {
|
||||||
|
nextStep = it
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
underTest.updateTokenAndChallenge(null, null)
|
||||||
|
underTest.onConfirmDevice(true, 10L)
|
||||||
|
underTest.onEnrollFirstFailure("We failed!!")
|
||||||
|
|
||||||
|
runCurrent()
|
||||||
|
|
||||||
|
assertThat(nextStep).isInstanceOf(FinishSettings::class.java)
|
||||||
|
job.cancel()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun firstEnrollment_failsWithReason() = runTest {
|
||||||
|
whenever(fingerprintManager.getEnrolledFingerprints(anyInt())).thenReturn(emptyList())
|
||||||
|
|
||||||
|
var nextStep: NextStepViewModel? = null
|
||||||
|
val job = launch {
|
||||||
|
underTest.nextStep.collect {
|
||||||
|
nextStep = it
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val failStr = "We failed!!"
|
||||||
|
val failReason = 101
|
||||||
|
|
||||||
|
underTest.updateTokenAndChallenge(null, null)
|
||||||
|
underTest.onConfirmDevice(true, 10L)
|
||||||
|
underTest.onEnrollFirstFailure(failStr, failReason)
|
||||||
|
|
||||||
|
runCurrent()
|
||||||
|
|
||||||
|
assertThat(nextStep).isEqualTo(FinishSettingsWithResult(failReason, failStr))
|
||||||
|
job.cancel()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun firstEnrollmentSucceeds_noToken() = runTest {
|
||||||
|
whenever(fingerprintManager.getEnrolledFingerprints(anyInt())).thenReturn(emptyList())
|
||||||
|
|
||||||
|
var nextStep: NextStepViewModel? = null
|
||||||
|
val job = launch {
|
||||||
|
underTest.nextStep.collect {
|
||||||
|
nextStep = it
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
underTest.updateTokenAndChallenge(null, null)
|
||||||
|
underTest.onConfirmDevice(true, 10L)
|
||||||
|
underTest.onEnrollFirst(null, null)
|
||||||
|
|
||||||
|
runCurrent()
|
||||||
|
|
||||||
|
assertThat(nextStep).isEqualTo(FinishSettings("Error, empty token"))
|
||||||
|
job.cancel()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun firstEnrollmentSucceeds_noKeyChallenge() = runTest {
|
||||||
|
whenever(fingerprintManager.getEnrolledFingerprints(anyInt())).thenReturn(emptyList())
|
||||||
|
|
||||||
|
var nextStep: NextStepViewModel? = null
|
||||||
|
val job = launch {
|
||||||
|
underTest.nextStep.collect {
|
||||||
|
nextStep = it
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val byteArray = ByteArray(1) {
|
||||||
|
3
|
||||||
|
}
|
||||||
|
|
||||||
|
underTest.updateTokenAndChallenge(null, null)
|
||||||
|
underTest.onConfirmDevice(true, 10L)
|
||||||
|
underTest.onEnrollFirst(byteArray, null)
|
||||||
|
|
||||||
|
runCurrent()
|
||||||
|
|
||||||
|
assertThat(nextStep).isEqualTo(FinishSettings("Error, empty keyChallenge"))
|
||||||
|
job.cancel()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun firstEnrollment_succeeds() = runTest {
|
||||||
|
whenever(fingerprintManager.getEnrolledFingerprints(anyInt())).thenReturn(emptyList())
|
||||||
|
|
||||||
|
var nextStep: NextStepViewModel? = null
|
||||||
|
val job = launch {
|
||||||
|
underTest.nextStep.collect {
|
||||||
|
nextStep = it
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val byteArray = ByteArray(1) {
|
||||||
|
3
|
||||||
|
}
|
||||||
|
val keyChallenge = 89L
|
||||||
|
|
||||||
|
underTest.updateTokenAndChallenge(null, null)
|
||||||
|
underTest.onConfirmDevice(true, 10L)
|
||||||
|
underTest.onEnrollFirst(byteArray, keyChallenge)
|
||||||
|
|
||||||
|
runCurrent()
|
||||||
|
|
||||||
|
assertThat(nextStep).isEqualTo(ShowSettings(defaultUserId))
|
||||||
|
job.cancel()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun confirmDeviceCredential_withEnrolledFingerprint_showsSettings() = runTest {
|
||||||
|
whenever(fingerprintManager.getEnrolledFingerprints(anyInt())).thenReturn(
|
||||||
|
listOf(
|
||||||
|
Fingerprint(
|
||||||
|
"a", 1, 2, 3L
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
var nextStep: NextStepViewModel? = null
|
||||||
|
val job = launch {
|
||||||
|
underTest.nextStep.collect {
|
||||||
|
nextStep = it
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
underTest.updateTokenAndChallenge(null, null)
|
||||||
|
underTest.onConfirmDevice(true, 10L)
|
||||||
|
|
||||||
|
runCurrent()
|
||||||
|
|
||||||
|
assertThat(nextStep).isEqualTo(ShowSettings(defaultUserId))
|
||||||
|
job.cancel()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun enrollAdditionalFingerprints_fails() = runTest {
|
||||||
|
whenever(fingerprintManager.getEnrolledFingerprints(anyInt())).thenReturn(
|
||||||
|
listOf(
|
||||||
|
Fingerprint(
|
||||||
|
"a", 1, 2, 3L
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
var nextStep: NextStepViewModel? = null
|
||||||
|
val job = launch {
|
||||||
|
underTest.nextStep.collect {
|
||||||
|
nextStep = it
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
underTest.updateTokenAndChallenge(null, null)
|
||||||
|
underTest.onConfirmDevice(true, 10L)
|
||||||
|
underTest.onEnrollAdditionalFailure()
|
||||||
|
|
||||||
|
runCurrent()
|
||||||
|
|
||||||
|
assertThat(nextStep).isInstanceOf(FinishSettings::class.java)
|
||||||
|
job.cancel()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun enrollAdditional_success() = runTest {
|
||||||
|
whenever(fingerprintManager.getEnrolledFingerprints(anyInt())).thenReturn(
|
||||||
|
listOf(
|
||||||
|
Fingerprint(
|
||||||
|
"a", 1, 2, 3L
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
var nextStep: NextStepViewModel? = null
|
||||||
|
val job = launch {
|
||||||
|
underTest.nextStep.collect {
|
||||||
|
nextStep = it
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
underTest.updateTokenAndChallenge(null, null)
|
||||||
|
underTest.onConfirmDevice(true, 10L)
|
||||||
|
underTest.onEnrollSuccess()
|
||||||
|
|
||||||
|
runCurrent()
|
||||||
|
|
||||||
|
assertThat(nextStep).isEqualTo(ShowSettings(defaultUserId))
|
||||||
|
job.cancel()
|
||||||
|
}
|
||||||
|
}
|
Reference in New Issue
Block a user