Basic structure for fingerprint enrollment.
Bug: N/A Test: Enroll introduction screen works as expected Test: User is prompted with pin/pattern/pass if the token is not present. Change-Id: I32a182b09c3bcd9be43428c500bfae7b39a74e63
This commit is contained in:
@@ -106,7 +106,9 @@ android_library {
|
|||||||
"SystemUIUnfoldLib",
|
"SystemUIUnfoldLib",
|
||||||
],
|
],
|
||||||
|
|
||||||
plugins: ["androidx.room_room-compiler-plugin"],
|
plugins: [
|
||||||
|
"androidx.room_room-compiler-plugin",
|
||||||
|
],
|
||||||
|
|
||||||
libs: [
|
libs: [
|
||||||
"telephony-common",
|
"telephony-common",
|
||||||
|
@@ -2566,6 +2566,16 @@
|
|||||||
</intent-filter>
|
</intent-filter>
|
||||||
</activity>
|
</activity>
|
||||||
|
|
||||||
|
<activity android:name=".biometrics.fingerprint2.enrollment.ui.activity.FingerprintEnrollmentV2Activity"
|
||||||
|
android:exported="true"
|
||||||
|
android:permission="android.permission.MANAGE_FINGERPRINT"
|
||||||
|
android:theme="@style/GlifTheme.Light">
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="android.settings.FINGERPRINT_SETUP" />
|
||||||
|
<category android:name="android.intent.category.DEFAULT" />
|
||||||
|
</intent-filter>
|
||||||
|
</activity>
|
||||||
|
|
||||||
<activity android:name=".biometrics.fingerprint.FingerprintSuggestionActivity"
|
<activity android:name=".biometrics.fingerprint.FingerprintSuggestionActivity"
|
||||||
android:exported="true"
|
android:exported="true"
|
||||||
android:permission="android.permission.MANAGE_FINGERPRINT"
|
android:permission="android.permission.MANAGE_FINGERPRINT"
|
||||||
|
44
res/layout/fingerprint_v2_enroll_find_sensor.xml
Normal file
44
res/layout/fingerprint_v2_enroll_find_sensor.xml
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
<?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.
|
||||||
|
-->
|
||||||
|
|
||||||
|
<com.google.android.setupdesign.GlifLayout
|
||||||
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:id="@+id/setup_wizard_layout"
|
||||||
|
style="?attr/fingerprint_layout_theme"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent">
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:clipToPadding="false"
|
||||||
|
android:clipChildren="false">
|
||||||
|
|
||||||
|
<Space
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="0dp"
|
||||||
|
android:layout_weight="1" />
|
||||||
|
|
||||||
|
<include
|
||||||
|
layout="@layout/fingerprint_enroll_find_sensor_graphic"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_gravity="center_horizontal"/>
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
</com.google.android.setupdesign.GlifLayout>
|
214
res/layout/fingerprint_v2_enroll_introduction.xml
Normal file
214
res/layout/fingerprint_v2_enroll_introduction.xml
Normal file
@@ -0,0 +1,214 @@
|
|||||||
|
<?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.
|
||||||
|
-->
|
||||||
|
|
||||||
|
<com.google.android.setupdesign.GlifLayout
|
||||||
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
style="?attr/fingerprint_layout_theme"
|
||||||
|
android:id="@+id/setup_wizard_layout"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent">
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
style="@style/SudContentFrame"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:clipChildren="false"
|
||||||
|
android:clipToPadding="false"
|
||||||
|
android:orientation="vertical">
|
||||||
|
|
||||||
|
<com.google.android.setupdesign.view.RichTextView
|
||||||
|
android:id="@+id/error_text"
|
||||||
|
style="@style/SudDescription.Glif"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content" />
|
||||||
|
|
||||||
|
<FrameLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content">
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
style="@style/SudContentIllustration"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:contentDescription="@null"
|
||||||
|
android:src="@drawable/fingerprint_enroll_introduction" />
|
||||||
|
|
||||||
|
</FrameLayout>
|
||||||
|
|
||||||
|
<!-- Contains the extra information text at the bottom -->
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="vertical">
|
||||||
|
|
||||||
|
<!-- How it works -->
|
||||||
|
<TextView
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
style="@style/BiometricEnrollIntroTitle"
|
||||||
|
android:text="@string/security_settings_fingerprint_v2_enroll_introduction_footer_title_2" />
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="horizontal">
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/icon_fingerprint"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:contentDescription="@null"
|
||||||
|
android:src="@drawable/ic_fingerprint_24dp"/>
|
||||||
|
<Space
|
||||||
|
android:layout_width="16dp"
|
||||||
|
android:layout_height="wrap_content"/>
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/footer_message_2"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
style="@style/BiometricEnrollIntroMessage" />
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="horizontal">
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/icon_device_locked"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:contentDescription="@null"
|
||||||
|
android:src="@drawable/ic_lock_24dp"/>
|
||||||
|
<Space
|
||||||
|
android:layout_width="16dp"
|
||||||
|
android:layout_height="wrap_content"/>
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/footer_message_3"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
style="@style/BiometricEnrollIntroMessage" />
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<!-- You're in control -->
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/footer_title_1"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
style="@style/BiometricEnrollIntroTitle" />
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="horizontal">
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/icon_trash_can"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:contentDescription="@null"
|
||||||
|
android:src="@drawable/ic_trash_can"/>
|
||||||
|
<Space
|
||||||
|
android:layout_width="16dp"
|
||||||
|
android:layout_height="wrap_content"/>
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/footer_message_4"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
style="@style/BiometricEnrollIntroMessage" />
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<!-- Keep in mind -->
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/footer_title_2"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
style="@style/BiometricEnrollIntroTitle"
|
||||||
|
android:text="@string/security_settings_face_enroll_introduction_info_title"/>
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="horizontal">
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/icon_info"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:contentDescription="@null"
|
||||||
|
android:src="@drawable/ic_info_outline_24dp"/>
|
||||||
|
<Space
|
||||||
|
android:layout_width="16dp"
|
||||||
|
android:layout_height="wrap_content"/>
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/footer_message_5"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
style="@style/BiometricEnrollIntroMessage" />
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="horizontal">
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/icon_shield"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:contentDescription="@null"
|
||||||
|
android:src="@drawable/ic_guarantee"/>
|
||||||
|
<Space
|
||||||
|
android:layout_width="16dp"
|
||||||
|
android:layout_height="wrap_content"/>
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/footer_message_6"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
style="@style/BiometricEnrollIntroMessage" />
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="horizontal">
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/icon_link"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:contentDescription="@null"
|
||||||
|
android:src="@drawable/ic_link_24dp"/>
|
||||||
|
<Space
|
||||||
|
android:layout_width="16dp"
|
||||||
|
android:layout_height="wrap_content"/>
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/footer_learn_more"
|
||||||
|
android:linksClickable="true"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
style="@style/BiometricEnrollIntroMessage"
|
||||||
|
android:paddingBottom="0dp"
|
||||||
|
android:text="@string/security_settings_fingerprint_v2_enroll_introduction_message_learn_more" />
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
</com.google.android.setupdesign.GlifLayout>
|
29
res/layout/fingerprint_v2_enroll_main.xml
Normal file
29
res/layout/fingerprint_v2_enroll_main.xml
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
<?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.
|
||||||
|
-->
|
||||||
|
|
||||||
|
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:orientation="vertical">
|
||||||
|
<androidx.fragment.app.FragmentContainerView
|
||||||
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:id="@+id/fragment_container_view"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
/>
|
||||||
|
|
||||||
|
</FrameLayout>
|
@@ -0,0 +1,259 @@
|
|||||||
|
/*
|
||||||
|
* 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.enrollment.ui.activity
|
||||||
|
|
||||||
|
import android.annotation.ColorInt
|
||||||
|
import android.app.Activity
|
||||||
|
import android.content.Intent
|
||||||
|
import android.content.res.ColorStateList
|
||||||
|
import android.graphics.Color
|
||||||
|
import android.hardware.fingerprint.FingerprintManager
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.provider.Settings
|
||||||
|
import android.util.Log
|
||||||
|
import androidx.activity.result.contract.ActivityResultContracts
|
||||||
|
import androidx.fragment.app.Fragment
|
||||||
|
import androidx.fragment.app.FragmentActivity
|
||||||
|
import androidx.lifecycle.ViewModelProvider
|
||||||
|
import androidx.lifecycle.lifecycleScope
|
||||||
|
import com.android.internal.widget.LockPatternUtils
|
||||||
|
import com.android.settings.R
|
||||||
|
import com.android.settings.SetupWizardUtils
|
||||||
|
import com.android.settings.Utils
|
||||||
|
import com.android.settings.Utils.SETTINGS_PACKAGE_NAME
|
||||||
|
import com.android.settings.biometrics.BiometricEnrollBase
|
||||||
|
import com.android.settings.biometrics.BiometricEnrollBase.CONFIRM_REQUEST
|
||||||
|
import com.android.settings.biometrics.BiometricEnrollBase.RESULT_FINISHED
|
||||||
|
import com.android.settings.biometrics.GatekeeperPasswordProvider
|
||||||
|
import com.android.settings.biometrics.fingerprint2.domain.interactor.FingerprintManagerInteractorImpl
|
||||||
|
import com.android.settings.biometrics.fingerprint2.enrollment.ui.fragment.FingerprintEnrollConfirmationV2Fragment
|
||||||
|
import com.android.settings.biometrics.fingerprint2.enrollment.ui.fragment.FingerprintEnrollEnrollingV2Fragment
|
||||||
|
import com.android.settings.biometrics.fingerprint2.enrollment.ui.fragment.FingerprintEnrollFindSensorV2Fragment
|
||||||
|
import com.android.settings.biometrics.fingerprint2.enrollment.ui.fragment.FingerprintEnrollmentIntroV2Fragment
|
||||||
|
import com.android.settings.biometrics.fingerprint2.enrollment.ui.viewmodel.Confirmation
|
||||||
|
import com.android.settings.biometrics.fingerprint2.enrollment.ui.viewmodel.Education
|
||||||
|
import com.android.settings.biometrics.fingerprint2.enrollment.ui.viewmodel.Enrollment
|
||||||
|
import com.android.settings.biometrics.fingerprint2.enrollment.ui.viewmodel.FingerprintEnrollmentNavigationViewModel
|
||||||
|
import com.android.settings.biometrics.fingerprint2.enrollment.ui.viewmodel.FingerprintGatekeeperViewModel
|
||||||
|
import com.android.settings.biometrics.fingerprint2.enrollment.ui.viewmodel.FingerprintScrollViewModel
|
||||||
|
import com.android.settings.biometrics.fingerprint2.enrollment.ui.viewmodel.FingerprintViewModel
|
||||||
|
import com.android.settings.biometrics.fingerprint2.enrollment.ui.viewmodel.Finish
|
||||||
|
import com.android.settings.biometrics.fingerprint2.enrollment.ui.viewmodel.GatekeeperInfo
|
||||||
|
import com.android.settings.biometrics.fingerprint2.enrollment.ui.viewmodel.Intro
|
||||||
|
import com.android.settings.biometrics.fingerprint2.enrollment.ui.viewmodel.LaunchConfirmDeviceCredential
|
||||||
|
import com.android.settings.password.ChooseLockGeneric
|
||||||
|
import com.android.settings.password.ChooseLockSettingsHelper
|
||||||
|
import com.android.settings.password.ChooseLockSettingsHelper.EXTRA_KEY_GK_PW_HANDLE
|
||||||
|
import com.google.android.setupdesign.util.ThemeHelper
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.flow.filterNotNull
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
|
private const val TAG = "FingerprintEnrollmentV2Activity"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This is the activity that controls the entire Fingerprint Enrollment experience through its
|
||||||
|
* children fragments.
|
||||||
|
*/
|
||||||
|
class FingerprintEnrollmentV2Activity : FragmentActivity() {
|
||||||
|
private lateinit var navigationViewModel: FingerprintEnrollmentNavigationViewModel
|
||||||
|
private lateinit var gatekeeperViewModel: FingerprintGatekeeperViewModel
|
||||||
|
private val coroutineDispatcher = Dispatchers.Default
|
||||||
|
|
||||||
|
/** 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?) {
|
||||||
|
super.onActivityResult(requestCode, resultCode, data)
|
||||||
|
if (requestCode == CONFIRM_REQUEST) {
|
||||||
|
onConfirmDevice(resultCode, data)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onAttachedToWindow() {
|
||||||
|
window.statusBarColor = getBackgroundColor()
|
||||||
|
super.onAttachedToWindow()
|
||||||
|
}
|
||||||
|
|
||||||
|
@ColorInt
|
||||||
|
private fun getBackgroundColor(): Int {
|
||||||
|
val stateList: ColorStateList? =
|
||||||
|
Utils.getColorAttr(applicationContext, android.R.attr.windowBackground)
|
||||||
|
return stateList?.defaultColor ?: Color.TRANSPARENT
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun onConfirmDevice(resultCode: Int, data: Intent?) {
|
||||||
|
val wasSuccessful = resultCode == RESULT_FINISHED || resultCode == Activity.RESULT_OK
|
||||||
|
val gateKeeperPasswordHandle = data?.getExtra(EXTRA_KEY_GK_PW_HANDLE) as Long?
|
||||||
|
lifecycleScope.launch {
|
||||||
|
gatekeeperViewModel.onConfirmDevice(wasSuccessful, gateKeeperPasswordHandle)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
|
super.onCreate(savedInstanceState)
|
||||||
|
setContentView(R.layout.fingerprint_v2_enroll_main)
|
||||||
|
|
||||||
|
setTheme(SetupWizardUtils.getTheme(applicationContext, intent))
|
||||||
|
ThemeHelper.trySetDynamicColor(applicationContext)
|
||||||
|
|
||||||
|
val backgroundDispatcher = Dispatchers.IO
|
||||||
|
|
||||||
|
val context = applicationContext
|
||||||
|
val fingerprintManager = context.getSystemService(FINGERPRINT_SERVICE) as FingerprintManager
|
||||||
|
|
||||||
|
val interactor =
|
||||||
|
FingerprintManagerInteractorImpl(
|
||||||
|
context,
|
||||||
|
backgroundDispatcher,
|
||||||
|
fingerprintManager,
|
||||||
|
GatekeeperPasswordProvider(LockPatternUtils(context))
|
||||||
|
) {
|
||||||
|
var toReturn: Int =
|
||||||
|
Settings.Secure.getIntForUser(
|
||||||
|
context.contentResolver,
|
||||||
|
Settings.Secure.SFPS_PERFORMANT_AUTH_ENABLED,
|
||||||
|
-1,
|
||||||
|
context.userId,
|
||||||
|
)
|
||||||
|
if (toReturn == -1) {
|
||||||
|
toReturn =
|
||||||
|
if (
|
||||||
|
context.resources.getBoolean(com.android.internal.R.bool.config_performantAuthDefault)
|
||||||
|
) {
|
||||||
|
1
|
||||||
|
} else {
|
||||||
|
0
|
||||||
|
}
|
||||||
|
Settings.Secure.putIntForUser(
|
||||||
|
context.contentResolver,
|
||||||
|
Settings.Secure.SFPS_PERFORMANT_AUTH_ENABLED,
|
||||||
|
toReturn,
|
||||||
|
context.userId
|
||||||
|
)
|
||||||
|
}
|
||||||
|
toReturn == 1
|
||||||
|
}
|
||||||
|
|
||||||
|
var challenge: Long? = intent.getExtra(BiometricEnrollBase.EXTRA_KEY_CHALLENGE) as Long?
|
||||||
|
val token = intent.getByteArrayExtra(ChooseLockSettingsHelper.EXTRA_KEY_CHALLENGE_TOKEN)
|
||||||
|
val gatekeeperInfo = FingerprintGatekeeperViewModel.toGateKeeperInfo(challenge, token)
|
||||||
|
|
||||||
|
gatekeeperViewModel =
|
||||||
|
ViewModelProvider(
|
||||||
|
this,
|
||||||
|
FingerprintGatekeeperViewModel.FingerprintGatekeeperViewModelFactory(
|
||||||
|
gatekeeperInfo,
|
||||||
|
interactor,
|
||||||
|
)
|
||||||
|
)[FingerprintGatekeeperViewModel::class.java]
|
||||||
|
|
||||||
|
navigationViewModel =
|
||||||
|
ViewModelProvider(
|
||||||
|
this,
|
||||||
|
FingerprintEnrollmentNavigationViewModel.FingerprintEnrollmentNavigationViewModelFactory(
|
||||||
|
backgroundDispatcher,
|
||||||
|
interactor,
|
||||||
|
gatekeeperViewModel,
|
||||||
|
gatekeeperInfo is GatekeeperInfo.GatekeeperPasswordInfo, /* canSkipConfirm */
|
||||||
|
)
|
||||||
|
)[FingerprintEnrollmentNavigationViewModel::class.java]
|
||||||
|
|
||||||
|
// Initialize FingerprintViewModel
|
||||||
|
ViewModelProvider(this, FingerprintViewModel.FingerprintViewModelFactory(interactor))[
|
||||||
|
FingerprintViewModel::class.java]
|
||||||
|
|
||||||
|
// Initialize scroll view model
|
||||||
|
ViewModelProvider(this, FingerprintScrollViewModel.FingerprintScrollViewModelFactory())[
|
||||||
|
FingerprintScrollViewModel::class.java]
|
||||||
|
|
||||||
|
lifecycleScope.launch {
|
||||||
|
navigationViewModel.navigationViewModel.filterNotNull().collect {
|
||||||
|
Log.d(TAG, "navigationStep $it")
|
||||||
|
val isForward = it.forward
|
||||||
|
val currStep = it.currStep
|
||||||
|
val theClass: Class<Fragment>? =
|
||||||
|
when (currStep) {
|
||||||
|
Confirmation -> FingerprintEnrollConfirmationV2Fragment::class.java as Class<Fragment>
|
||||||
|
Education -> FingerprintEnrollFindSensorV2Fragment::class.java as Class<Fragment>
|
||||||
|
Enrollment -> FingerprintEnrollEnrollingV2Fragment::class.java as Class<Fragment>
|
||||||
|
Intro -> FingerprintEnrollmentIntroV2Fragment::class.java as Class<Fragment>
|
||||||
|
else -> null
|
||||||
|
}
|
||||||
|
|
||||||
|
if (theClass != null) {
|
||||||
|
supportFragmentManager
|
||||||
|
.beginTransaction()
|
||||||
|
.setReorderingAllowed(true)
|
||||||
|
.add(R.id.fragment_container_view, theClass, null)
|
||||||
|
.commit()
|
||||||
|
} else {
|
||||||
|
|
||||||
|
if (currStep is Finish) {
|
||||||
|
if (currStep.resultCode != null) {
|
||||||
|
finishActivity(currStep.resultCode)
|
||||||
|
} else {
|
||||||
|
finish()
|
||||||
|
}
|
||||||
|
} else if (currStep == LaunchConfirmDeviceCredential) {
|
||||||
|
launchConfirmOrChooseLock(userId)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val fromSettingsSummary =
|
||||||
|
intent.getBooleanExtra(BiometricEnrollBase.EXTRA_FROM_SETTINGS_SUMMARY, false)
|
||||||
|
if (
|
||||||
|
fromSettingsSummary && GatekeeperPasswordProvider.containsGatekeeperPasswordHandle(intent)
|
||||||
|
) {
|
||||||
|
overridePendingTransition(
|
||||||
|
com.google.android.setupdesign.R.anim.sud_slide_next_in,
|
||||||
|
com.google.android.setupdesign.R.anim.sud_slide_next_out
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun launchConfirmOrChooseLock(userId: Int) {
|
||||||
|
val activity = this
|
||||||
|
lifecycleScope.launch(coroutineDispatcher) {
|
||||||
|
val intent = Intent()
|
||||||
|
val builder = ChooseLockSettingsHelper.Builder(activity)
|
||||||
|
val launched =
|
||||||
|
builder
|
||||||
|
.setRequestCode(CONFIRM_REQUEST)
|
||||||
|
.setTitle(getString(R.string.security_settings_fingerprint_preference_title))
|
||||||
|
.setRequestGatekeeperPasswordHandle(true)
|
||||||
|
.setUserId(userId)
|
||||||
|
.setForegroundOnly(true)
|
||||||
|
.setReturnCredentials(true)
|
||||||
|
.show()
|
||||||
|
if (!launched) {
|
||||||
|
intent.setClassName(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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,39 @@
|
|||||||
|
/*
|
||||||
|
* 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.enrollment.ui.fragment
|
||||||
|
|
||||||
|
import android.os.Bundle
|
||||||
|
import androidx.fragment.app.Fragment
|
||||||
|
import androidx.lifecycle.ViewModelProvider
|
||||||
|
import com.android.settings.biometrics.fingerprint2.enrollment.ui.viewmodel.FingerprintEnrollmentNavigationViewModel
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A fragment to indicate that fingerprint enrollment has been completed.
|
||||||
|
*
|
||||||
|
* This page will display basic information about what a fingerprint can be used for and acts as the
|
||||||
|
* final step of enrollment.
|
||||||
|
*/
|
||||||
|
class FingerprintEnrollConfirmationV2Fragment : Fragment() {
|
||||||
|
|
||||||
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
|
super.onCreate(savedInstanceState)
|
||||||
|
if (savedInstanceState == null) {
|
||||||
|
val navigationViewModel =
|
||||||
|
ViewModelProvider(requireActivity())[FingerprintEnrollmentNavigationViewModel::class.java]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,34 @@
|
|||||||
|
/*
|
||||||
|
* 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.enrollment.ui.fragment
|
||||||
|
|
||||||
|
import android.os.Bundle
|
||||||
|
import androidx.fragment.app.Fragment
|
||||||
|
import androidx.lifecycle.ViewModelProvider
|
||||||
|
import com.android.settings.biometrics.fingerprint2.enrollment.ui.viewmodel.FingerprintEnrollmentNavigationViewModel
|
||||||
|
|
||||||
|
/** A fragment that is responsible for enrolling a users fingerprint. */
|
||||||
|
class FingerprintEnrollEnrollingV2Fragment : Fragment() {
|
||||||
|
|
||||||
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
|
super.onCreate(savedInstanceState)
|
||||||
|
if (savedInstanceState == null) {
|
||||||
|
val navigationViewModel =
|
||||||
|
ViewModelProvider(requireActivity())[FingerprintEnrollmentNavigationViewModel::class.java]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,42 @@
|
|||||||
|
/*
|
||||||
|
* 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.enrollment.ui.fragment
|
||||||
|
|
||||||
|
import android.os.Bundle
|
||||||
|
import androidx.fragment.app.Fragment
|
||||||
|
import androidx.lifecycle.ViewModelProvider
|
||||||
|
import com.android.settings.R
|
||||||
|
import com.android.settings.biometrics.fingerprint2.enrollment.ui.viewmodel.FingerprintEnrollmentNavigationViewModel
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A fragment that is used to educate the user about the fingerprint sensor on this device.
|
||||||
|
*
|
||||||
|
* The main goals of this page are
|
||||||
|
* 1. Inform the user where the fingerprint sensor is on their device
|
||||||
|
* 2. Explain to the user how the enrollment process shown by [FingerprintEnrollEnrollingV2Fragment]
|
||||||
|
* will work.
|
||||||
|
*/
|
||||||
|
class FingerprintEnrollFindSensorV2Fragment : Fragment(R.layout.fingerprint_v2_enroll_find_sensor) {
|
||||||
|
|
||||||
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
|
super.onCreate(savedInstanceState)
|
||||||
|
if (savedInstanceState == null) {
|
||||||
|
val navigationViewModel =
|
||||||
|
ViewModelProvider(requireActivity())[FingerprintEnrollmentNavigationViewModel::class.java]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,290 @@
|
|||||||
|
/*
|
||||||
|
* 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.enrollment.ui.fragment
|
||||||
|
|
||||||
|
import android.annotation.NonNull
|
||||||
|
import android.annotation.StringRes
|
||||||
|
import android.graphics.Color
|
||||||
|
import android.graphics.PorterDuff
|
||||||
|
import android.graphics.PorterDuffColorFilter
|
||||||
|
import android.hardware.fingerprint.FingerprintSensorProperties
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.text.Html
|
||||||
|
import android.text.method.LinkMovementMethod
|
||||||
|
import android.util.Log
|
||||||
|
import android.view.View
|
||||||
|
import android.widget.ImageView
|
||||||
|
import android.widget.ScrollView
|
||||||
|
import android.widget.TextView
|
||||||
|
import androidx.fragment.app.Fragment
|
||||||
|
import androidx.lifecycle.ViewModelProvider
|
||||||
|
import androidx.lifecycle.lifecycleScope
|
||||||
|
import com.android.settings.R
|
||||||
|
import com.android.settings.biometrics.fingerprint2.enrollment.ui.viewmodel.FingerprintEnrollmentNavigationViewModel
|
||||||
|
import com.android.settings.biometrics.fingerprint2.enrollment.ui.viewmodel.FingerprintGatekeeperViewModel
|
||||||
|
import com.android.settings.biometrics.fingerprint2.enrollment.ui.viewmodel.FingerprintScrollViewModel
|
||||||
|
import com.android.settings.biometrics.fingerprint2.enrollment.ui.viewmodel.FingerprintViewModel
|
||||||
|
import com.android.settings.biometrics.fingerprint2.enrollment.ui.viewmodel.Unicorn
|
||||||
|
import com.google.android.setupcompat.template.FooterBarMixin
|
||||||
|
import com.google.android.setupcompat.template.FooterButton
|
||||||
|
import com.google.android.setupdesign.GlifLayout
|
||||||
|
import com.google.android.setupdesign.template.RequireScrollMixin
|
||||||
|
import com.google.android.setupdesign.util.DynamicColorPalette
|
||||||
|
import kotlinx.coroutines.flow.combine
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
|
private const val TAG = "FingerprintEnrollmentIntroV2Fragment"
|
||||||
|
|
||||||
|
/** This class represents the customizable text for FingerprintEnrollIntroduction. */
|
||||||
|
private data class TextModel(
|
||||||
|
@StringRes val footerMessageTwo: Int,
|
||||||
|
@StringRes val footerMessageThree: Int,
|
||||||
|
@StringRes val footerMessageFour: Int,
|
||||||
|
@StringRes val footerMessageFive: Int,
|
||||||
|
@StringRes val footerMessageSix: Int,
|
||||||
|
@StringRes val negativeButton: Int,
|
||||||
|
@StringRes val footerTitleOne: Int,
|
||||||
|
@StringRes val footerTitleTwo: Int,
|
||||||
|
@StringRes val headerText: Int,
|
||||||
|
@StringRes val descriptionText: Int,
|
||||||
|
)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The introduction fragment that is used to inform the user the basics of what a fingerprint sensor
|
||||||
|
* is and how it will be used.
|
||||||
|
*
|
||||||
|
* The main gaols of this page are
|
||||||
|
* 1. Inform the user what the fingerprint sensor is and does
|
||||||
|
* 2. How the data will be stored
|
||||||
|
* 3. How the user can access and remove their data
|
||||||
|
*/
|
||||||
|
class FingerprintEnrollmentIntroV2Fragment : Fragment(R.layout.fingerprint_v2_enroll_introduction) {
|
||||||
|
private lateinit var footerBarMixin: FooterBarMixin
|
||||||
|
private lateinit var textModel: TextModel
|
||||||
|
private lateinit var navigationViewModel: FingerprintEnrollmentNavigationViewModel
|
||||||
|
private lateinit var fingerprintStateViewModel: FingerprintViewModel
|
||||||
|
private lateinit var fingerprintScrollViewModel: FingerprintScrollViewModel
|
||||||
|
private lateinit var gateKeeperViewModel: FingerprintGatekeeperViewModel
|
||||||
|
|
||||||
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
|
super.onCreate(savedInstanceState)
|
||||||
|
navigationViewModel =
|
||||||
|
ViewModelProvider(requireActivity())[FingerprintEnrollmentNavigationViewModel::class.java]
|
||||||
|
fingerprintStateViewModel =
|
||||||
|
ViewModelProvider(requireActivity())[FingerprintViewModel::class.java]
|
||||||
|
fingerprintScrollViewModel =
|
||||||
|
ViewModelProvider(requireActivity())[FingerprintScrollViewModel::class.java]
|
||||||
|
gateKeeperViewModel =
|
||||||
|
ViewModelProvider(requireActivity())[FingerprintGatekeeperViewModel::class.java]
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
|
super.onViewCreated(view, savedInstanceState)
|
||||||
|
|
||||||
|
lifecycleScope.launch {
|
||||||
|
combine(
|
||||||
|
navigationViewModel.enrollType,
|
||||||
|
fingerprintStateViewModel.fingerprintStateViewModel,
|
||||||
|
) { enrollType, fingerprintStateViewModel ->
|
||||||
|
Pair(enrollType, fingerprintStateViewModel)
|
||||||
|
}
|
||||||
|
.collect { (enrollType, fingerprintStateViewModel) ->
|
||||||
|
val sensorProps = fingerprintStateViewModel?.sensorProps
|
||||||
|
|
||||||
|
textModel =
|
||||||
|
when (enrollType) {
|
||||||
|
Unicorn -> getUnicornTextModel()
|
||||||
|
else -> getNormalTextModel()
|
||||||
|
}
|
||||||
|
|
||||||
|
setupFooterBarAndScrollView(view)
|
||||||
|
|
||||||
|
if (savedInstanceState == null) {
|
||||||
|
getLayout()?.setHeaderText(textModel.headerText)
|
||||||
|
getLayout()?.setDescriptionText(textModel.descriptionText)
|
||||||
|
|
||||||
|
// Set color filter for the following icons.
|
||||||
|
val colorFilter = getIconColorFilter()
|
||||||
|
listOf(
|
||||||
|
R.id.icon_fingerprint,
|
||||||
|
R.id.icon_device_locked,
|
||||||
|
R.id.icon_trash_can,
|
||||||
|
R.id.icon_info,
|
||||||
|
R.id.icon_shield,
|
||||||
|
R.id.icon_link
|
||||||
|
)
|
||||||
|
.forEach { icon ->
|
||||||
|
view.findViewById<ImageView>(icon).drawable.colorFilter = colorFilter
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set the text for the footer text views.
|
||||||
|
listOf(
|
||||||
|
R.id.footer_message_2 to textModel.footerMessageTwo,
|
||||||
|
R.id.footer_message_3 to textModel.footerMessageThree,
|
||||||
|
R.id.footer_message_4 to textModel.footerMessageFour,
|
||||||
|
R.id.footer_message_5 to textModel.footerMessageFive,
|
||||||
|
R.id.footer_message_6 to textModel.footerMessageSix,
|
||||||
|
)
|
||||||
|
.forEach { pair -> view.findViewById<TextView>(pair.first).setText(pair.second) }
|
||||||
|
|
||||||
|
setFooterLink(view)
|
||||||
|
|
||||||
|
val iconShield: ImageView = view.findViewById(R.id.icon_shield)
|
||||||
|
val footerMessage6: TextView = view.findViewById(R.id.footer_message_6)
|
||||||
|
when (sensorProps?.sensorType) {
|
||||||
|
FingerprintSensorProperties.TYPE_UDFPS_ULTRASONIC,
|
||||||
|
FingerprintSensorProperties.TYPE_UDFPS_OPTICAL -> {
|
||||||
|
footerMessage6.visibility = View.VISIBLE
|
||||||
|
iconShield.visibility = View.VISIBLE
|
||||||
|
}
|
||||||
|
else -> {
|
||||||
|
footerMessage6.visibility = View.GONE
|
||||||
|
iconShield.visibility = View.GONE
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
view.findViewById<TextView?>(R.id.footer_title_1).setText(textModel.footerTitleOne)
|
||||||
|
view.findViewById<TextView?>(R.id.footer_title_2).setText(textModel.footerTitleOne)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun setFooterLink(view: View) {
|
||||||
|
val footerLink: TextView = view.findViewById(R.id.footer_learn_more)
|
||||||
|
footerLink.movementMethod = LinkMovementMethod.getInstance()
|
||||||
|
footerLink.text =
|
||||||
|
Html.fromHtml(
|
||||||
|
getString(R.string.security_settings_fingerprint_v2_enroll_introduction_message_learn_more),
|
||||||
|
Html.FROM_HTML_MODE_LEGACY
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun setupFooterBarAndScrollView(
|
||||||
|
view: View,
|
||||||
|
) {
|
||||||
|
val scrollView: ScrollView =
|
||||||
|
view.findViewById(com.google.android.setupdesign.R.id.sud_scroll_view)
|
||||||
|
scrollView.importantForAccessibility = View.IMPORTANT_FOR_ACCESSIBILITY_YES
|
||||||
|
// Next button responsible for starting the next fragment.
|
||||||
|
val onNextButtonClick: View.OnClickListener =
|
||||||
|
View.OnClickListener { Log.d(TAG, "OnNextClicked") }
|
||||||
|
|
||||||
|
val layout: GlifLayout = requireActivity().findViewById(R.id.setup_wizard_layout)
|
||||||
|
footerBarMixin = layout.getMixin(FooterBarMixin::class.java)
|
||||||
|
footerBarMixin.primaryButton =
|
||||||
|
FooterButton.Builder(requireActivity())
|
||||||
|
.setText(R.string.security_settings_face_enroll_introduction_more)
|
||||||
|
.setListener(onNextButtonClick)
|
||||||
|
.setButtonType(FooterButton.ButtonType.OPT_IN)
|
||||||
|
.setTheme(com.google.android.setupdesign.R.style.SudGlifButton_Primary)
|
||||||
|
.build()
|
||||||
|
footerBarMixin.setSecondaryButton(
|
||||||
|
FooterButton.Builder(requireActivity())
|
||||||
|
.setText(textModel.negativeButton)
|
||||||
|
.setListener({ Log.d(TAG, "prevClicked") })
|
||||||
|
.setButtonType(FooterButton.ButtonType.NEXT)
|
||||||
|
.setTheme(com.google.android.setupdesign.R.style.SudGlifButton_Primary)
|
||||||
|
.build(),
|
||||||
|
true /* usePrimaryStyle */
|
||||||
|
)
|
||||||
|
|
||||||
|
val primaryButton = footerBarMixin.primaryButton
|
||||||
|
val secondaryButton = footerBarMixin.secondaryButton
|
||||||
|
|
||||||
|
secondaryButton.visibility = View.INVISIBLE
|
||||||
|
|
||||||
|
val requireScrollMixin = layout.getMixin(RequireScrollMixin::class.java)
|
||||||
|
requireScrollMixin.requireScrollWithButton(
|
||||||
|
requireActivity(),
|
||||||
|
footerBarMixin.primaryButton,
|
||||||
|
R.string.security_settings_face_enroll_introduction_more,
|
||||||
|
onNextButtonClick
|
||||||
|
)
|
||||||
|
|
||||||
|
requireScrollMixin.setOnRequireScrollStateChangedListener { scrollNeeded: Boolean ->
|
||||||
|
// Show secondary button once scroll is completed.
|
||||||
|
if (!scrollNeeded) {
|
||||||
|
fingerprintScrollViewModel.userConsented()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
lifecycleScope.launch {
|
||||||
|
fingerprintScrollViewModel.hasReadConsentScreen.collect { consented ->
|
||||||
|
if (consented) {
|
||||||
|
primaryButton.setText(
|
||||||
|
requireContext(),
|
||||||
|
R.string.security_settings_fingerprint_enroll_introduction_agree
|
||||||
|
)
|
||||||
|
secondaryButton.visibility = View.VISIBLE
|
||||||
|
} else {
|
||||||
|
secondaryButton.visibility = View.INVISIBLE
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
footerBarMixin.getButtonContainer()?.setBackgroundColor(Color.TRANSPARENT)
|
||||||
|
|
||||||
|
// I think I should remove this, and make the challenge a pre-requisite of launching
|
||||||
|
// the flow. For instance if someone launches the activity with an invalid challenge, it
|
||||||
|
// either 1) Fails or 2) Launched confirmDeviceCredential
|
||||||
|
primaryButton.isEnabled = false
|
||||||
|
lifecycleScope.launch {
|
||||||
|
gateKeeperViewModel.hasValidGatekeeperInfo.collect { primaryButton.isEnabled = it }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getNormalTextModel() =
|
||||||
|
TextModel(
|
||||||
|
R.string.security_settings_fingerprint_v2_enroll_introduction_footer_message_2,
|
||||||
|
R.string.security_settings_fingerprint_v2_enroll_introduction_footer_message_3,
|
||||||
|
R.string.security_settings_fingerprint_v2_enroll_introduction_footer_message_4,
|
||||||
|
R.string.security_settings_fingerprint_v2_enroll_introduction_footer_message_5,
|
||||||
|
R.string.security_settings_fingerprint_v2_enroll_introduction_footer_message_6,
|
||||||
|
R.string.security_settings_fingerprint_enroll_introduction_no_thanks,
|
||||||
|
R.string.security_settings_fingerprint_enroll_introduction_footer_title_1,
|
||||||
|
R.string.security_settings_fingerprint_enroll_introduction_footer_title_2,
|
||||||
|
R.string.security_settings_fingerprint_enroll_introduction_title,
|
||||||
|
R.string.security_settings_fingerprint_enroll_introduction_v3_message,
|
||||||
|
)
|
||||||
|
|
||||||
|
private fun getUnicornTextModel() =
|
||||||
|
TextModel(
|
||||||
|
R.string.security_settings_fingerprint_v2_enroll_introduction_footer_message_consent_2,
|
||||||
|
R.string.security_settings_fingerprint_v2_enroll_introduction_footer_message_consent_3,
|
||||||
|
R.string.security_settings_fingerprint_v2_enroll_introduction_footer_message_consent_4,
|
||||||
|
R.string.security_settings_fingerprint_v2_enroll_introduction_footer_message_consent_5,
|
||||||
|
R.string.security_settings_fingerprint_v2_enroll_introduction_footer_message_consent_6,
|
||||||
|
R.string.security_settings_fingerprint_enroll_introduction_no_thanks,
|
||||||
|
R.string.security_settings_fingerprint_enroll_introduction_footer_title_consent_1,
|
||||||
|
R.string.security_settings_fingerprint_enroll_introduction_footer_title_2,
|
||||||
|
R.string.security_settings_fingerprint_enroll_consent_introduction_title,
|
||||||
|
R.string.security_settings_fingerprint_enroll_introduction_v3_message,
|
||||||
|
)
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
private fun getIconColorFilter(): PorterDuffColorFilter {
|
||||||
|
return PorterDuffColorFilter(
|
||||||
|
DynamicColorPalette.getColor(context, DynamicColorPalette.ColorType.ACCENT),
|
||||||
|
PorterDuff.Mode.SRC_IN
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getLayout(): GlifLayout? {
|
||||||
|
return requireView().findViewById(R.id.setup_wizard_layout) as GlifLayout?
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,160 @@
|
|||||||
|
/*
|
||||||
|
* 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.enrollment.ui.viewmodel
|
||||||
|
|
||||||
|
import android.util.Log
|
||||||
|
import androidx.lifecycle.ViewModel
|
||||||
|
import androidx.lifecycle.ViewModelProvider
|
||||||
|
import androidx.lifecycle.viewModelScope
|
||||||
|
import com.android.settings.biometrics.fingerprint2.domain.interactor.FingerprintManagerInteractor
|
||||||
|
import kotlinx.coroutines.CoroutineDispatcher
|
||||||
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
|
import kotlinx.coroutines.flow.asStateFlow
|
||||||
|
import kotlinx.coroutines.flow.filterNotNull
|
||||||
|
import kotlinx.coroutines.flow.update
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
|
const val TAG = "FingerprintEnrollmentNavigationViewModel"
|
||||||
|
|
||||||
|
/** Interface to validate a gatekeeper hat */
|
||||||
|
interface Validator {
|
||||||
|
fun validateGateKeeper(challenge: Long?): Boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The [EnrollType] for fingerprint enrollment indicates information on how the flow should behave.
|
||||||
|
*/
|
||||||
|
sealed class EnrollType()
|
||||||
|
|
||||||
|
/** The default enrollment experience, typically called from Settings */
|
||||||
|
object Default : EnrollType()
|
||||||
|
|
||||||
|
/** SetupWizard/Out of box experience (OOBE) enrollment type. */
|
||||||
|
object SetupWizard : EnrollType()
|
||||||
|
|
||||||
|
/** Unicorn enrollment type */
|
||||||
|
object Unicorn : EnrollType()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This class is responsible for sending a [NavigationStep] which indicates where the user is in the
|
||||||
|
* Fingerprint Enrollment flow
|
||||||
|
*/
|
||||||
|
class FingerprintEnrollmentNavigationViewModel(
|
||||||
|
private val dispatcher: CoroutineDispatcher,
|
||||||
|
private val validator: Validator,
|
||||||
|
private val fingerprintManagerInteractor: FingerprintManagerInteractor,
|
||||||
|
private val gatekeeperViewModel: FingerprintGatekeeperViewModel,
|
||||||
|
private val canSkipConfirm: Boolean
|
||||||
|
) : ViewModel() {
|
||||||
|
|
||||||
|
private class InternalNavigationStep(
|
||||||
|
lastStep: NextStepViewModel,
|
||||||
|
nextStep: NextStepViewModel,
|
||||||
|
forward: Boolean,
|
||||||
|
var canNavigate: Boolean
|
||||||
|
) : NavigationStep(lastStep, nextStep, forward)
|
||||||
|
|
||||||
|
private var _enrollType = MutableStateFlow<EnrollType?>(Default)
|
||||||
|
|
||||||
|
/** A flow that indicates the [EnrollType] */
|
||||||
|
val enrollType: Flow<EnrollType?> = _enrollType.asStateFlow()
|
||||||
|
|
||||||
|
private var navState = NavState(canSkipConfirm)
|
||||||
|
|
||||||
|
private val _navigationStep =
|
||||||
|
MutableStateFlow(
|
||||||
|
InternalNavigationStep(
|
||||||
|
PlaceHolderState,
|
||||||
|
Start.next(navState),
|
||||||
|
forward = false,
|
||||||
|
canNavigate = true
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
init {
|
||||||
|
viewModelScope.launch {
|
||||||
|
gatekeeperViewModel.credentialConfirmed.filterNotNull().collect {
|
||||||
|
if (_navigationStep.value.currStep is LaunchConfirmDeviceCredential) {
|
||||||
|
if (it) nextStep() else finish()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A flow that contains the [NavigationStep] used to indicate where in the enrollment process the
|
||||||
|
* user is.
|
||||||
|
*/
|
||||||
|
val navigationViewModel: Flow<NavigationStep> = _navigationStep.asStateFlow()
|
||||||
|
|
||||||
|
/** Used to start the next step of Fingerprint Enrollment. */
|
||||||
|
fun nextStep() {
|
||||||
|
viewModelScope.launch {
|
||||||
|
val currStep = _navigationStep.value.currStep
|
||||||
|
val nextStep = currStep.next(navState)
|
||||||
|
Log.d(TAG, "nextStep(${currStep} -> $nextStep)")
|
||||||
|
_navigationStep.update {
|
||||||
|
InternalNavigationStep(currStep, nextStep, forward = true, canNavigate = false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Go back a step of fingerprint enrollment. */
|
||||||
|
fun prevStep() {
|
||||||
|
viewModelScope.launch {
|
||||||
|
val currStep = _navigationStep.value.currStep
|
||||||
|
val nextStep = currStep.prev(navState)
|
||||||
|
_navigationStep.update {
|
||||||
|
InternalNavigationStep(currStep, nextStep, forward = false, canNavigate = false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun finish() {
|
||||||
|
_navigationStep.update {
|
||||||
|
InternalNavigationStep(Finish(null), Finish(null), forward = false, canNavigate = false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class FingerprintEnrollmentNavigationViewModelFactory(
|
||||||
|
private val backgroundDispatcher: CoroutineDispatcher,
|
||||||
|
private val fingerprintManagerInteractor: FingerprintManagerInteractor,
|
||||||
|
private val fingerprintGatekeeperViewModel: FingerprintGatekeeperViewModel,
|
||||||
|
private val canSkipConfirm: Boolean,
|
||||||
|
) : ViewModelProvider.Factory {
|
||||||
|
|
||||||
|
@Suppress("UNCHECKED_CAST")
|
||||||
|
override fun <T : ViewModel> create(
|
||||||
|
modelClass: Class<T>,
|
||||||
|
): T {
|
||||||
|
|
||||||
|
return FingerprintEnrollmentNavigationViewModel(
|
||||||
|
backgroundDispatcher,
|
||||||
|
object : Validator {
|
||||||
|
override fun validateGateKeeper(challenge: Long?): Boolean {
|
||||||
|
return challenge != null
|
||||||
|
}
|
||||||
|
},
|
||||||
|
fingerprintManagerInteractor,
|
||||||
|
fingerprintGatekeeperViewModel,
|
||||||
|
canSkipConfirm,
|
||||||
|
)
|
||||||
|
as T
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,124 @@
|
|||||||
|
/*
|
||||||
|
* 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.enrollment.ui.viewmodel
|
||||||
|
|
||||||
|
import android.os.CountDownTimer
|
||||||
|
import android.util.Log
|
||||||
|
import androidx.lifecycle.ViewModel
|
||||||
|
import androidx.lifecycle.ViewModelProvider
|
||||||
|
import androidx.lifecycle.viewModelScope
|
||||||
|
import com.android.settings.biometrics.fingerprint2.domain.interactor.FingerprintManagerInteractor
|
||||||
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
|
import kotlinx.coroutines.flow.asStateFlow
|
||||||
|
import kotlinx.coroutines.flow.map
|
||||||
|
import kotlinx.coroutines.flow.update
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
|
sealed interface GatekeeperInfo {
|
||||||
|
object Invalid : GatekeeperInfo
|
||||||
|
object Timeout : GatekeeperInfo
|
||||||
|
data class GatekeeperPasswordInfo(val token: ByteArray?, val passwordHandle: Long?) :
|
||||||
|
GatekeeperInfo
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This class is responsible for maintaining the gatekeeper information including things like
|
||||||
|
* timeouts.
|
||||||
|
*
|
||||||
|
* Please note, that this class can't fully support timeouts of the gatekeeper password handle due
|
||||||
|
* to the fact that a handle may have been generated earlier in the settings enrollment and passed
|
||||||
|
* in as a parameter to this class.
|
||||||
|
*/
|
||||||
|
class FingerprintGatekeeperViewModel(
|
||||||
|
theGatekeeperInfo: GatekeeperInfo?,
|
||||||
|
private val fingerprintManagerInteractor: FingerprintManagerInteractor,
|
||||||
|
) : ViewModel() {
|
||||||
|
|
||||||
|
private var _gatekeeperInfo: MutableStateFlow<GatekeeperInfo?> =
|
||||||
|
MutableStateFlow(theGatekeeperInfo)
|
||||||
|
|
||||||
|
/** The gatekeeper info for fingerprint enrollment. */
|
||||||
|
val gatekeeperInfo: Flow<GatekeeperInfo?> = _gatekeeperInfo.asStateFlow()
|
||||||
|
|
||||||
|
/** Indicates if the gatekeeper info is valid. */
|
||||||
|
val hasValidGatekeeperInfo: Flow<Boolean> =
|
||||||
|
gatekeeperInfo.map { it is GatekeeperInfo.GatekeeperPasswordInfo }
|
||||||
|
|
||||||
|
private var _credentialConfirmed: MutableStateFlow<Boolean?> = MutableStateFlow(null)
|
||||||
|
val credentialConfirmed: Flow<Boolean?> = _credentialConfirmed.asStateFlow()
|
||||||
|
|
||||||
|
private var countDownTimer: CountDownTimer? = null
|
||||||
|
|
||||||
|
/** Timeout of 15 minutes for a generated challenge */
|
||||||
|
private val TIMEOUT: Long = 15 * 60 * 1000
|
||||||
|
|
||||||
|
/** Called after a confirm device credential attempt has been made. */
|
||||||
|
fun onConfirmDevice(wasSuccessful: Boolean, theGatekeeperPasswordHandle: Long?) {
|
||||||
|
if (!wasSuccessful) {
|
||||||
|
Log.d(TAG, "confirmDevice failed")
|
||||||
|
_gatekeeperInfo.update { GatekeeperInfo.Invalid }
|
||||||
|
_credentialConfirmed.update { false }
|
||||||
|
} else {
|
||||||
|
viewModelScope.launch {
|
||||||
|
val res = fingerprintManagerInteractor.generateChallenge(theGatekeeperPasswordHandle!!)
|
||||||
|
_gatekeeperInfo.update { GatekeeperInfo.GatekeeperPasswordInfo(res.second, res.first) }
|
||||||
|
_credentialConfirmed.update { true }
|
||||||
|
startTimeout()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun startTimeout() {
|
||||||
|
countDownTimer?.cancel()
|
||||||
|
countDownTimer =
|
||||||
|
object : CountDownTimer(TIMEOUT, 1000) {
|
||||||
|
override fun onFinish() {
|
||||||
|
_gatekeeperInfo.update { GatekeeperInfo.Timeout }
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onTick(millisUntilFinished: Long) {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
/**
|
||||||
|
* A function that checks if the challenge and token are valid, in which case a
|
||||||
|
* [GatekeeperInfo.GatekeeperPasswordInfo] is provided, else [GatekeeperInfo.Invalid]
|
||||||
|
*/
|
||||||
|
fun toGateKeeperInfo(challenge: Long?, token: ByteArray?): GatekeeperInfo {
|
||||||
|
Log.d(TAG, "toGateKeeperInfo(${challenge == null}, ${token == null})")
|
||||||
|
if (challenge == null || token == null) {
|
||||||
|
return GatekeeperInfo.Invalid
|
||||||
|
}
|
||||||
|
return GatekeeperInfo.GatekeeperPasswordInfo(token, challenge)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class FingerprintGatekeeperViewModelFactory(
|
||||||
|
private val gatekeeperInfo: GatekeeperInfo?,
|
||||||
|
private val fingerprintManagerInteractor: FingerprintManagerInteractor,
|
||||||
|
) : ViewModelProvider.Factory {
|
||||||
|
|
||||||
|
@Suppress("UNCHECKED_CAST")
|
||||||
|
override fun <T : ViewModel> create(
|
||||||
|
modelClass: Class<T>,
|
||||||
|
): T {
|
||||||
|
return FingerprintGatekeeperViewModel(gatekeeperInfo, fingerprintManagerInteractor) as T
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,47 @@
|
|||||||
|
/*
|
||||||
|
* 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.enrollment.ui.viewmodel
|
||||||
|
|
||||||
|
import androidx.lifecycle.ViewModel
|
||||||
|
import androidx.lifecycle.ViewModelProvider
|
||||||
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
|
import kotlinx.coroutines.flow.asStateFlow
|
||||||
|
import kotlinx.coroutines.flow.update
|
||||||
|
|
||||||
|
/** This class is responsible for ensuring a users consent to use FingerprintEnrollment. */
|
||||||
|
class FingerprintScrollViewModel : ViewModel() {
|
||||||
|
|
||||||
|
private val _hasReadConsentScreen: MutableStateFlow<Boolean> = MutableStateFlow(false)
|
||||||
|
/** Indicates if a user has consented to FingerprintEnrollment */
|
||||||
|
val hasReadConsentScreen: Flow<Boolean> = _hasReadConsentScreen.asStateFlow()
|
||||||
|
|
||||||
|
/** Indicates that a user has consented to FingerprintEnrollment */
|
||||||
|
fun userConsented() {
|
||||||
|
_hasReadConsentScreen.update { true }
|
||||||
|
}
|
||||||
|
|
||||||
|
class FingerprintScrollViewModelFactory() : ViewModelProvider.Factory {
|
||||||
|
|
||||||
|
@Suppress("UNCHECKED_CAST")
|
||||||
|
override fun <T : ViewModel> create(
|
||||||
|
modelClass: Class<T>,
|
||||||
|
): T {
|
||||||
|
return FingerprintScrollViewModel() as T
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,90 @@
|
|||||||
|
/*
|
||||||
|
* 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.enrollment.ui.viewmodel
|
||||||
|
|
||||||
|
import android.hardware.fingerprint.FingerprintSensorPropertiesInternal
|
||||||
|
import androidx.lifecycle.ViewModel
|
||||||
|
import androidx.lifecycle.ViewModelProvider
|
||||||
|
import androidx.lifecycle.viewModelScope
|
||||||
|
import com.android.settings.biometrics.fingerprint2.domain.interactor.FingerprintManagerInteractor
|
||||||
|
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 the fingerprint data nad the relevant state. */
|
||||||
|
data class FingerprintStateViewModel(
|
||||||
|
val fingerprintViewModels: List<FingerEnrollmentViewModel>,
|
||||||
|
val canEnroll: Boolean,
|
||||||
|
val maxFingerprints: Int,
|
||||||
|
val sensorProps: FingerprintSensorPropertiesInternal,
|
||||||
|
)
|
||||||
|
|
||||||
|
/** Represents a fingerprint enrollment. */
|
||||||
|
data class FingerEnrollmentViewModel(
|
||||||
|
val name: String,
|
||||||
|
val fingerId: Int,
|
||||||
|
val deviceId: Long,
|
||||||
|
)
|
||||||
|
|
||||||
|
/** 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 {
|
||||||
|
FingerEnrollmentViewModel(it.name, it.fingerId, it.deviceId)
|
||||||
|
}
|
||||||
|
val sensorProps = fingerprintManagerInteractor.sensorPropertiesInternal().first()
|
||||||
|
val maxFingerprints = 5
|
||||||
|
_fingerprintViewModel.update {
|
||||||
|
FingerprintStateViewModel(
|
||||||
|
enrolledFingerprints,
|
||||||
|
enrolledFingerprints.size < maxFingerprints,
|
||||||
|
maxFingerprints,
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,104 @@
|
|||||||
|
/*
|
||||||
|
* 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.enrollment.ui.viewmodel
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A class that represents an action that the consumer should transition between lastStep and
|
||||||
|
* currStep and in what direction this transition is occurring (e.g. forward or backwards)
|
||||||
|
*/
|
||||||
|
open class NavigationStep(
|
||||||
|
val lastStep: NextStepViewModel,
|
||||||
|
val currStep: NextStepViewModel,
|
||||||
|
val forward: Boolean
|
||||||
|
) {
|
||||||
|
override fun toString(): String {
|
||||||
|
return "lastStep=$lastStep, currStep=$currStep, forward=$forward"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** The navigation state used by a [NavStep] to determine what the [NextStepViewModel] should be. */
|
||||||
|
class NavState(val confirmedDevice: Boolean)
|
||||||
|
|
||||||
|
interface NavStep<T> {
|
||||||
|
fun next(state: NavState): T
|
||||||
|
fun prev(state: NavState): T
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A class to represent a high level step (I.E. EnrollmentIntroduction) for FingerprintEnrollment.
|
||||||
|
*/
|
||||||
|
sealed class NextStepViewModel : NavStep<NextStepViewModel>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This is the initial state for the previous step, used to indicate that there have been no
|
||||||
|
* previous states.
|
||||||
|
*/
|
||||||
|
object PlaceHolderState : NextStepViewModel() {
|
||||||
|
override fun next(state: NavState): NextStepViewModel = Finish(null)
|
||||||
|
|
||||||
|
override fun prev(state: NavState): NextStepViewModel = Finish(null)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This state is the initial state for the current step, and will be used to determine if the user
|
||||||
|
* needs to [LaunchConfirmDeviceCredential] if not, it will go to [Intro]
|
||||||
|
*/
|
||||||
|
object Start : NextStepViewModel() {
|
||||||
|
override fun next(state: NavState): NextStepViewModel =
|
||||||
|
if (state.confirmedDevice) Intro else LaunchConfirmDeviceCredential
|
||||||
|
|
||||||
|
override fun prev(state: NavState): NextStepViewModel = Finish(null)
|
||||||
|
}
|
||||||
|
|
||||||
|
/** State indicating enrollment has been completed */
|
||||||
|
class Finish(val resultCode: Int?) : NextStepViewModel() {
|
||||||
|
override fun next(state: NavState): NextStepViewModel = Finish(resultCode)
|
||||||
|
override fun prev(state: NavState): NextStepViewModel = Finish(null)
|
||||||
|
}
|
||||||
|
|
||||||
|
/** State for the FingerprintEnrollment introduction */
|
||||||
|
object Intro : NextStepViewModel() {
|
||||||
|
override fun next(state: NavState): NextStepViewModel = Education
|
||||||
|
override fun prev(state: NavState): NextStepViewModel = Finish(null)
|
||||||
|
}
|
||||||
|
|
||||||
|
/** State for the FingerprintEnrollment education */
|
||||||
|
object Education : NextStepViewModel() {
|
||||||
|
override fun next(state: NavState): NextStepViewModel = Enrollment
|
||||||
|
override fun prev(state: NavState): NextStepViewModel = Intro
|
||||||
|
}
|
||||||
|
|
||||||
|
/** State for the FingerprintEnrollment enrollment */
|
||||||
|
object Enrollment : NextStepViewModel() {
|
||||||
|
override fun next(state: NavState): NextStepViewModel = Confirmation
|
||||||
|
override fun prev(state: NavState): NextStepViewModel = Education
|
||||||
|
}
|
||||||
|
|
||||||
|
/** State for the FingerprintEnrollment confirmation */
|
||||||
|
object Confirmation : NextStepViewModel() {
|
||||||
|
override fun next(state: NavState): NextStepViewModel = Finish(0)
|
||||||
|
override fun prev(state: NavState): NextStepViewModel = Intro
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* State used to send the user to the ConfirmDeviceCredential activity. This activity can either
|
||||||
|
* confirm a users device credential, or have them create one.
|
||||||
|
*/
|
||||||
|
object LaunchConfirmDeviceCredential : NextStepViewModel() {
|
||||||
|
override fun next(state: NavState): NextStepViewModel = Intro
|
||||||
|
override fun prev(state: NavState): NextStepViewModel = Finish(0)
|
||||||
|
}
|
Reference in New Issue
Block a user