diff --git a/res/layout/fingerprint_v2_enroll_introduction.xml b/res/layout/fingerprint_v2_enroll_introduction.xml
index e9dd08ad7ca..2fd1f9c84ee 100644
--- a/res/layout/fingerprint_v2_enroll_introduction.xml
+++ b/res/layout/fingerprint_v2_enroll_introduction.xml
@@ -16,199 +16,210 @@
-->
-
-
+ android:layout_height="match_parent">
-
-
-
-
-
-
-
-
-
-
-
+ android:layout_height="wrap_content" />
+
+
+
+
+
+
+
+ android:orientation="vertical">
-
-
+
-
+ android:text="@string/security_settings_fingerprint_v2_enroll_introduction_footer_title_2" />
-
-
-
-
-
-
+ android:orientation="horizontal">
-
-
+
-
+
-
-
-
+
+
+
-
+ android:orientation="horizontal">
-
-
+
-
+
-
-
+
+
+
+
+
+
-
+ android:orientation="horizontal">
-
+
-
-
+
+
+
+
+
+
-
+ android:text="@string/security_settings_face_enroll_introduction_info_title" />
-
-
-
-
-
+ android:orientation="horizontal">
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
\ No newline at end of file
diff --git a/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/activity/FingerprintEnrollmentV2Activity.kt b/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/activity/FingerprintEnrollmentV2Activity.kt
index bbd67b58de7..58fcea6961a 100644
--- a/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/activity/FingerprintEnrollmentV2Activity.kt
+++ b/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/activity/FingerprintEnrollmentV2Activity.kt
@@ -42,6 +42,7 @@ 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.shared.model.FingerprintViewModel
import com.android.settings.biometrics.fingerprint2.ui.enrollment.fragment.FingerprintEnrollConfirmationV2Fragment
import com.android.settings.biometrics.fingerprint2.ui.enrollment.fragment.FingerprintEnrollEnrollingV2Fragment
import com.android.settings.biometrics.fingerprint2.ui.enrollment.fragment.FingerprintEnrollFindSensorV2Fragment
@@ -82,6 +83,7 @@ class FingerprintEnrollmentV2Activity : FragmentActivity() {
private lateinit var accessibilityViewModel: AccessibilityViewModel
private lateinit var foldStateViewModel: FoldStateViewModel
private lateinit var orientationStateViewModel: OrientationStateViewModel
+ private lateinit var fingerprintScrollViewModel: FingerprintScrollViewModel
private val coroutineDispatcher = Dispatchers.Default
/** Result listener for ChooseLock activity flow. */
@@ -210,8 +212,9 @@ class FingerprintEnrollmentV2Activity : FragmentActivity() {
)[FingerprintEnrollViewModel::class.java]
// Initialize scroll view model
- ViewModelProvider(this, FingerprintScrollViewModel.FingerprintScrollViewModelFactory())[
- FingerprintScrollViewModel::class.java]
+ fingerprintScrollViewModel =
+ ViewModelProvider(this, FingerprintScrollViewModel.FingerprintScrollViewModelFactory())[
+ FingerprintScrollViewModel::class.java]
// Initialize AccessibilityViewModel
accessibilityViewModel =
diff --git a/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/fragment/FingerprintEnrollIntroV2Fragment.kt b/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/fragment/FingerprintEnrollIntroV2Fragment.kt
index dbf6d128c0d..898b158aa25 100644
--- a/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/fragment/FingerprintEnrollIntroV2Fragment.kt
+++ b/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/fragment/FingerprintEnrollIntroV2Fragment.kt
@@ -25,10 +25,13 @@ import android.os.Bundle
import android.text.Html
import android.text.method.LinkMovementMethod
import android.util.Log
+import android.view.LayoutInflater
import android.view.View
+import android.view.ViewGroup
import android.widget.ImageView
import android.widget.ScrollView
import android.widget.TextView
+import androidx.annotation.VisibleForTesting
import androidx.fragment.app.Fragment
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.lifecycleScope
@@ -72,48 +75,69 @@ private data class TextModel(
* 2. How the data will be stored
* 3. How the user can access and remove their data
*/
-class FingerprintEnrollIntroV2Fragment : Fragment(R.layout.fingerprint_v2_enroll_introduction) {
- private lateinit var footerBarMixin: FooterBarMixin
- private lateinit var textModel: TextModel
- private lateinit var navigationViewModel: FingerprintEnrollNavigationViewModel
- private lateinit var fingerprintEnrollViewModel: FingerprintEnrollViewModel
- private lateinit var fingerprintScrollViewModel: FingerprintScrollViewModel
- private lateinit var gateKeeperViewModel: FingerprintGatekeeperViewModel
+class FingerprintEnrollIntroV2Fragment() : Fragment(R.layout.fingerprint_v2_enroll_introduction) {
- override fun onCreate(savedInstanceState: Bundle?) {
- super.onCreate(savedInstanceState)
- navigationViewModel =
- ViewModelProvider(requireActivity())[FingerprintEnrollNavigationViewModel::class.java]
- fingerprintEnrollViewModel =
- ViewModelProvider(requireActivity())[FingerprintEnrollViewModel::class.java]
- fingerprintScrollViewModel =
- ViewModelProvider(requireActivity())[FingerprintScrollViewModel::class.java]
- gateKeeperViewModel =
- ViewModelProvider(requireActivity())[FingerprintGatekeeperViewModel::class.java]
+ /** Used for testing purposes */
+ private var factory: ViewModelProvider.Factory? = null
+
+ @VisibleForTesting
+ constructor(theFactory: ViewModelProvider.Factory) : this() {
+ factory = theFactory
}
- override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
- super.onViewCreated(view, savedInstanceState)
+ private val viewModelProvider: ViewModelProvider by lazy {
+ if (factory != null) {
+ ViewModelProvider(requireActivity(), factory!!)
+ } else {
+ ViewModelProvider(requireActivity())
+ }
+ }
- lifecycleScope.launch {
- combine(
- navigationViewModel.enrollType,
- fingerprintEnrollViewModel.sensorType,
- ) { enrollType, sensorType ->
- Pair(enrollType, sensorType)
- }
- .collect { (enrollType, sensorType) ->
- textModel =
- when (enrollType) {
- Unicorn -> getUnicornTextModel()
- else -> getNormalTextModel()
- }
+ private lateinit var footerBarMixin: FooterBarMixin
+ private lateinit var textModel: TextModel
- setupFooterBarAndScrollView(view)
+ // Note that the ViewModels cannot be requested before the onCreate call
+ private val navigationViewModel: FingerprintEnrollNavigationViewModel by lazy {
+ viewModelProvider[FingerprintEnrollNavigationViewModel::class.java]
+ }
+ private val fingerprintViewModel: FingerprintEnrollViewModel by lazy {
+ viewModelProvider[FingerprintEnrollViewModel::class.java]
+ }
+ private val fingerprintScrollViewModel: FingerprintScrollViewModel by lazy {
+ viewModelProvider[FingerprintScrollViewModel::class.java]
+ }
+ private val gateKeeperViewModel: FingerprintGatekeeperViewModel by lazy {
+ viewModelProvider[FingerprintGatekeeperViewModel::class.java]
+ }
- if (savedInstanceState == null) {
- getLayout()?.setHeaderText(textModel.headerText)
- getLayout()?.setDescriptionText(textModel.descriptionText)
+ override fun onCreateView(
+ inflater: LayoutInflater,
+ container: ViewGroup?,
+ savedInstanceState: Bundle?
+ ): View? =
+ super.onCreateView(inflater, container, savedInstanceState).also { theView ->
+ val view = theView!!
+
+ viewLifecycleOwner.lifecycleScope.launch {
+ combine(
+ navigationViewModel.enrollType,
+ fingerprintViewModel.sensorType,
+ ) { enrollType, sensorType ->
+ Pair(enrollType, sensorType)
+ }
+ .collect { (enrollType, sensorType) ->
+ textModel =
+ when (enrollType) {
+ Unicorn -> getUnicornTextModel()
+ else -> getNormalTextModel()
+ }
+
+ setupFooterBarAndScrollView(view)
+
+ val layout = view as GlifLayout
+
+ layout.setHeaderText(textModel.headerText)
+ layout.setDescriptionText(textModel.descriptionText)
// Set color filter for the following icons.
val colorFilter = getIconColorFilter()
@@ -158,9 +182,9 @@ class FingerprintEnrollIntroV2Fragment : Fragment(R.layout.fingerprint_v2_enroll
view.requireViewById(R.id.footer_title_1).setText(textModel.footerTitleOne)
view.requireViewById(R.id.footer_title_2).setText(textModel.footerTitleOne)
}
- }
+ }
+ return view
}
- }
private fun setFooterLink(view: View) {
val footerLink: TextView = view.requireViewById(R.id.footer_learn_more)
@@ -185,17 +209,18 @@ class FingerprintEnrollIntroV2Fragment : Fragment(R.layout.fingerprint_v2_enroll
navigationViewModel.nextStep()
}
- val layout: GlifLayout = requireActivity().requireViewById(R.id.setup_wizard_layout)
+ val layout: GlifLayout = view.findViewById(R.id.setup_wizard_layout)!!
footerBarMixin = layout.getMixin(FooterBarMixin::class.java)
footerBarMixin.primaryButton =
- FooterButton.Builder(requireActivity())
+ FooterButton.Builder(requireContext())
.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())
+ FooterButton.Builder(requireContext())
.setText(textModel.negativeButton)
.setListener({ Log.d(TAG, "prevClicked") })
.setButtonType(FooterButton.ButtonType.NEXT)
@@ -211,8 +236,8 @@ class FingerprintEnrollIntroV2Fragment : Fragment(R.layout.fingerprint_v2_enroll
val requireScrollMixin = layout.getMixin(RequireScrollMixin::class.java)
requireScrollMixin.requireScrollWithButton(
- requireActivity(),
- footerBarMixin.primaryButton,
+ requireContext(),
+ primaryButton,
R.string.security_settings_face_enroll_introduction_more,
onNextButtonClick
)
@@ -224,7 +249,7 @@ class FingerprintEnrollIntroV2Fragment : Fragment(R.layout.fingerprint_v2_enroll
}
}
- lifecycleScope.launch {
+ viewLifecycleOwner.lifecycleScope.launch {
fingerprintScrollViewModel.hasReadConsentScreen.collect { consented ->
if (consented) {
primaryButton.setText(
@@ -244,7 +269,7 @@ class FingerprintEnrollIntroV2Fragment : Fragment(R.layout.fingerprint_v2_enroll
// 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 {
+ viewLifecycleOwner.lifecycleScope.launch {
gateKeeperViewModel.hasValidGatekeeperInfo.collect { primaryButton.isEnabled = it }
}
}
@@ -284,8 +309,4 @@ class FingerprintEnrollIntroV2Fragment : Fragment(R.layout.fingerprint_v2_enroll
PorterDuff.Mode.SRC_IN
)
}
-
- private fun getLayout(): GlifLayout? {
- return requireView().findViewById(R.id.setup_wizard_layout) as GlifLayout?
- }
}
diff --git a/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/viewmodel/FingerprintEnrolllNavigationViewModel.kt b/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/viewmodel/FingerprintEnrolllNavigationViewModel.kt
index d2bb3212122..97c8271a73d 100644
--- a/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/viewmodel/FingerprintEnrolllNavigationViewModel.kt
+++ b/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/viewmodel/FingerprintEnrolllNavigationViewModel.kt
@@ -31,11 +31,6 @@ import kotlinx.coroutines.launch
private const val TAG = "FingerprintEnrollNavigationViewModel"
-/** 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.
*/
@@ -56,7 +51,6 @@ object Unicorn : EnrollType()
*/
class FingerprintEnrollNavigationViewModel(
private val dispatcher: CoroutineDispatcher,
- private val validator: Validator,
private val fingerprintManagerInteractor: FingerprintManagerInteractor,
private val gatekeeperViewModel: FingerprintGatekeeperViewModel,
private val canSkipConfirm: Boolean
@@ -145,11 +139,6 @@ class FingerprintEnrollNavigationViewModel(
return FingerprintEnrollNavigationViewModel(
backgroundDispatcher,
- object : Validator {
- override fun validateGateKeeper(challenge: Long?): Boolean {
- return challenge != null
- }
- },
fingerprintManagerInteractor,
fingerprintGatekeeperViewModel,
canSkipConfirm,
diff --git a/tests/robotests/Android.bp b/tests/robotests/Android.bp
index f1c9cb8bd29..9b12dfaae8d 100644
--- a/tests/robotests/Android.bp
+++ b/tests/robotests/Android.bp
@@ -25,6 +25,8 @@ android_app {
"androidx.fragment_fragment-testing",
"frameworks-base-testutils",
"androidx.fragment_fragment",
+ "androidx.lifecycle_lifecycle-runtime-testing",
+ "kotlinx_coroutines_test",
],
aaptflags: ["--extra-packages com.android.settings"],
diff --git a/tests/robotests/src/com/android/settings/biometrics/fingerprint2/fragment/FingerprintEnrollIntroFragmentTest.kt b/tests/robotests/src/com/android/settings/biometrics/fingerprint2/fragment/FingerprintEnrollIntroFragmentTest.kt
new file mode 100644
index 00000000000..cea6676bc20
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/biometrics/fingerprint2/fragment/FingerprintEnrollIntroFragmentTest.kt
@@ -0,0 +1,173 @@
+/*
+ * 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.fragment
+
+import android.content.Context
+import android.os.Bundle
+import androidx.fragment.app.testing.FragmentScenario
+import androidx.fragment.app.testing.launchFragmentInContainer
+import androidx.lifecycle.ViewModel
+import androidx.lifecycle.ViewModelProvider
+import androidx.test.core.app.ApplicationProvider
+import androidx.test.espresso.Espresso.onView
+import androidx.test.espresso.assertion.ViewAssertions.doesNotExist
+import androidx.test.espresso.assertion.ViewAssertions.matches
+import androidx.test.espresso.matcher.ViewMatchers.Visibility
+import androidx.test.espresso.matcher.ViewMatchers.isDisplayed
+import androidx.test.espresso.matcher.ViewMatchers.withEffectiveVisibility
+import androidx.test.espresso.matcher.ViewMatchers.withId
+import androidx.test.espresso.matcher.ViewMatchers.withText
+import androidx.test.runner.AndroidJUnit4
+import com.android.settings.R
+import com.android.settings.biometrics.fingerprint2.ui.enrollment.fragment.FingerprintEnrollIntroV2Fragment
+import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.FingerprintEnrollNavigationViewModel
+import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.FingerprintEnrollViewModel
+import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.FingerprintGatekeeperViewModel
+import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.FingerprintScrollViewModel
+import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.GatekeeperInfo
+import com.android.settings.testutils2.FakeFingerprintManagerInteractor
+import com.google.android.setupdesign.GlifLayout
+import com.google.android.setupdesign.template.RequireScrollMixin
+import kotlinx.coroutines.test.StandardTestDispatcher
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+class FingerprintEnrollIntroFragmentTest {
+ private var context: Context = ApplicationProvider.getApplicationContext()
+ private var interactor = FakeFingerprintManagerInteractor()
+
+ private val gatekeeperViewModel =
+ FingerprintGatekeeperViewModel(
+ GatekeeperInfo.GatekeeperPasswordInfo(byteArrayOf(1, 2, 3), 100L),
+ interactor
+ )
+ private val backgroundDispatcher = StandardTestDispatcher()
+ private lateinit var fragmentScenario: FragmentScenario
+
+ private val navigationViewModel =
+ FingerprintEnrollNavigationViewModel(
+ backgroundDispatcher,
+ interactor,
+ gatekeeperViewModel,
+ canSkipConfirm = true,
+ )
+ private var fingerprintViewModel = FingerprintEnrollViewModel(interactor, backgroundDispatcher)
+ private var fingerprintScrollViewModel = FingerprintScrollViewModel()
+
+ @Before
+ fun setup() {
+ val factory =
+ object : ViewModelProvider.Factory {
+ @Suppress("UNCHECKED_CAST")
+ override fun create(
+ modelClass: Class,
+ ): T {
+ return when (modelClass) {
+ FingerprintEnrollViewModel::class.java -> fingerprintViewModel
+ FingerprintScrollViewModel::class.java -> fingerprintScrollViewModel
+ FingerprintEnrollNavigationViewModel::class.java -> navigationViewModel
+ FingerprintGatekeeperViewModel::class.java -> gatekeeperViewModel
+ else -> null
+ }
+ as T
+ }
+ }
+
+ fragmentScenario =
+ launchFragmentInContainer(Bundle(), R.style.SudThemeGlif) {
+ FingerprintEnrollIntroV2Fragment(factory)
+ }
+ }
+
+ @Test
+ fun testScrollToBottomButtonChangesText() {
+ fragmentScenario.onFragment { fragment ->
+ onView(withText("I agree")).check(doesNotExist())
+ val someView = (fragment.requireView().findViewById(R.id.setup_wizard_layout))!!
+ val scrollMixin = someView.getMixin(RequireScrollMixin::class.java)!!
+ val listener = scrollMixin.onRequireScrollStateChangedListener
+ // This actually changes the button text
+ listener.onRequireScrollStateChanged(false)
+
+ onView(withText("I agree")).check(matches(isDisplayed()))
+ }
+ }
+
+ @Test
+ fun testBasicTitle() {
+ onView(withText(R.string.security_settings_fingerprint_enroll_introduction_title))
+ .check(matches(withEffectiveVisibility(Visibility.VISIBLE)))
+ }
+
+ @Test
+ fun testFooterMessageTwo() {
+ onView(withId(R.id.footer_message_2))
+ .check(
+ matches(
+ withText(
+ context.getString(
+ (R.string.security_settings_fingerprint_v2_enroll_introduction_footer_message_2)
+ )
+ )
+ )
+ )
+ }
+
+ @Test
+ fun testFooterMessageThree() {
+ onView(withId(R.id.footer_message_3))
+ .check(
+ matches(
+ withText(
+ context.getString(
+ (R.string.security_settings_fingerprint_v2_enroll_introduction_footer_message_3)
+ )
+ )
+ )
+ )
+ }
+
+ @Test
+ fun testFooterMessageFour() {
+ onView(withId(R.id.footer_message_4))
+ .check(
+ matches(
+ withText(
+ context.getString(
+ (R.string.security_settings_fingerprint_v2_enroll_introduction_footer_message_4)
+ )
+ )
+ )
+ )
+ }
+
+ @Test
+ fun testFooterMessageFive() {
+ onView(withId(R.id.footer_message_5))
+ .check(
+ matches(
+ withText(
+ context.getString(
+ (R.string.security_settings_fingerprint_v2_enroll_introduction_footer_message_5)
+ )
+ )
+ )
+ )
+ }
+}