Merge "Added UI tests for FingerprintEnrollIntro" into main

This commit is contained in:
Joshua Mccloskey
2023-10-11 18:53:38 +00:00
committed by Android (Google) Code Review
6 changed files with 414 additions and 215 deletions

View File

@@ -16,199 +16,210 @@
--> -->
<com.google.android.setupdesign.GlifLayout <com.google.android.setupdesign.GlifLayout
xmlns:android="http://schemas.android.com/apk/res/android" xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto" android:id="@+id/setup_wizard_layout"
style="?attr/fingerprint_layout_theme" 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_width="match_parent"
android:layout_height="wrap_content" android:layout_height="match_parent">
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 <LinearLayout
style="@style/SudContentFrame"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:clipChildren="false"
android:clipToPadding="false"
android:orientation="vertical"> android:orientation="vertical">
<!-- How it works --> <com.google.android.setupdesign.view.RichTextView
<TextView android:id="@+id/error_text"
style="@style/SudDescription.Glif"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content" />
style="@style/BiometricEnrollIntroTitle"
android:text="@string/security_settings_fingerprint_v2_enroll_introduction_footer_title_2" />
<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 <LinearLayout
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:orientation="horizontal"> android:orientation="vertical">
<ImageView <!-- How it works -->
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 <TextView
android:id="@+id/footer_message_2" style="@style/BiometricEnrollIntroTitle"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
style="@style/BiometricEnrollIntroMessage" /> android:text="@string/security_settings_fingerprint_v2_enroll_introduction_footer_title_2" />
</LinearLayout>
<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_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
style="@style/BiometricEnrollIntroMessage" /> android:orientation="horizontal">
</LinearLayout>
<!-- You're in control --> <ImageView
<TextView android:id="@+id/icon_fingerprint"
android:id="@+id/footer_title_1" android:layout_width="wrap_content"
android:layout_width="match_parent" android:layout_height="wrap_content"
android:layout_height="wrap_content" android:contentDescription="@null"
style="@style/BiometricEnrollIntroTitle" /> android:src="@drawable/ic_fingerprint_24dp" />
<LinearLayout <Space
android:layout_width="match_parent" android:layout_width="16dp"
android:layout_height="wrap_content" android:layout_height="wrap_content" />
android:orientation="horizontal">
<ImageView <TextView
android:id="@+id/icon_trash_can" android:id="@+id/footer_message_2"
android:layout_width="wrap_content" style="@style/BiometricEnrollIntroMessage"
android:layout_height="wrap_content" android:layout_width="match_parent"
android:contentDescription="@null" android:layout_height="wrap_content" />
android:src="@drawable/ic_trash_can"/> </LinearLayout>
<Space
android:layout_width="16dp" <LinearLayout
android:layout_height="wrap_content"/>
<TextView
android:id="@+id/footer_message_4"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
style="@style/BiometricEnrollIntroMessage" /> android:orientation="horizontal">
</LinearLayout>
<!-- Keep in mind --> <ImageView
<TextView android:id="@+id/icon_device_locked"
android:id="@+id/footer_title_2" android:layout_width="wrap_content"
android:layout_width="match_parent" android:layout_height="wrap_content"
android:layout_height="wrap_content" android:contentDescription="@null"
style="@style/BiometricEnrollIntroTitle" android:src="@drawable/ic_lock_24dp" />
android:text="@string/security_settings_face_enroll_introduction_info_title"/>
<LinearLayout <Space
android:layout_width="match_parent" android:layout_width="16dp"
android:layout_height="wrap_content" android:layout_height="wrap_content" />
android:orientation="horizontal">
<ImageView <TextView
android:id="@+id/icon_info" android:id="@+id/footer_message_3"
android:layout_width="wrap_content" style="@style/BiometricEnrollIntroMessage"
android:layout_height="wrap_content" android:layout_width="match_parent"
android:contentDescription="@null" android:layout_height="wrap_content" />
android:src="@drawable/ic_info_outline_24dp"/> </LinearLayout>
<Space
android:layout_width="16dp" <!-- You're in control -->
android:layout_height="wrap_content"/>
<TextView <TextView
android:id="@+id/footer_message_5" android:id="@+id/footer_title_1"
style="@style/BiometricEnrollIntroTitle"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<LinearLayout
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
style="@style/BiometricEnrollIntroMessage" /> android:orientation="horizontal">
</LinearLayout>
<LinearLayout <ImageView
android:layout_width="match_parent" android:id="@+id/icon_trash_can"
android:layout_height="wrap_content" android:layout_width="wrap_content"
android:orientation="horizontal"> android:layout_height="wrap_content"
android:contentDescription="@null"
android:src="@drawable/ic_trash_can" />
<ImageView <Space
android:id="@+id/icon_shield" android:layout_width="16dp"
android:layout_width="wrap_content" android:layout_height="wrap_content" />
android:layout_height="wrap_content"
android:contentDescription="@null" <TextView
android:src="@drawable/ic_guarantee"/> android:id="@+id/footer_message_4"
<Space style="@style/BiometricEnrollIntroMessage"
android:layout_width="16dp" android:layout_width="match_parent"
android:layout_height="wrap_content"/> android:layout_height="wrap_content" />
</LinearLayout>
<!-- Keep in mind -->
<TextView <TextView
android:id="@+id/footer_message_6" android:id="@+id/footer_title_2"
style="@style/BiometricEnrollIntroTitle"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
style="@style/BiometricEnrollIntroMessage" /> android:text="@string/security_settings_face_enroll_introduction_info_title" />
</LinearLayout>
<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_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
style="@style/BiometricEnrollIntroMessage" android:orientation="horizontal">
android:paddingBottom="0dp"
android:text="@string/security_settings_fingerprint_v2_enroll_introduction_message_learn_more" /> <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"
style="@style/BiometricEnrollIntroMessage"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</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"
style="@style/BiometricEnrollIntroMessage"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</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"
style="@style/BiometricEnrollIntroMessage"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:linksClickable="true"
android:paddingBottom="0dp"
android:text="@string/security_settings_fingerprint_v2_enroll_introduction_message_learn_more" />
</LinearLayout>
</LinearLayout> </LinearLayout>
</LinearLayout> </LinearLayout>
</LinearLayout>
</com.google.android.setupdesign.GlifLayout> </com.google.android.setupdesign.GlifLayout>

View File

@@ -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.BiometricEnrollBase.RESULT_FINISHED
import com.android.settings.biometrics.GatekeeperPasswordProvider import com.android.settings.biometrics.GatekeeperPasswordProvider
import com.android.settings.biometrics.fingerprint2.domain.interactor.FingerprintManagerInteractorImpl import com.android.settings.biometrics.fingerprint2.domain.interactor.FingerprintManagerInteractorImpl
import com.android.settings.biometrics.fingerprint2.shared.model.FingerprintViewModel
import com.android.settings.biometrics.fingerprint2.ui.enrollment.fragment.FingerprintEnrollConfirmationV2Fragment 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.FingerprintEnrollEnrollingV2Fragment
import com.android.settings.biometrics.fingerprint2.ui.enrollment.fragment.FingerprintEnrollFindSensorV2Fragment 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 accessibilityViewModel: AccessibilityViewModel
private lateinit var foldStateViewModel: FoldStateViewModel private lateinit var foldStateViewModel: FoldStateViewModel
private lateinit var orientationStateViewModel: OrientationStateViewModel private lateinit var orientationStateViewModel: OrientationStateViewModel
private lateinit var fingerprintScrollViewModel: FingerprintScrollViewModel
private val coroutineDispatcher = Dispatchers.Default private val coroutineDispatcher = Dispatchers.Default
/** Result listener for ChooseLock activity flow. */ /** Result listener for ChooseLock activity flow. */
@@ -210,8 +212,9 @@ class FingerprintEnrollmentV2Activity : FragmentActivity() {
)[FingerprintEnrollViewModel::class.java] )[FingerprintEnrollViewModel::class.java]
// Initialize scroll view model // Initialize scroll view model
ViewModelProvider(this, FingerprintScrollViewModel.FingerprintScrollViewModelFactory())[ fingerprintScrollViewModel =
FingerprintScrollViewModel::class.java] ViewModelProvider(this, FingerprintScrollViewModel.FingerprintScrollViewModelFactory())[
FingerprintScrollViewModel::class.java]
// Initialize AccessibilityViewModel // Initialize AccessibilityViewModel
accessibilityViewModel = accessibilityViewModel =

View File

@@ -25,10 +25,13 @@ import android.os.Bundle
import android.text.Html import android.text.Html
import android.text.method.LinkMovementMethod import android.text.method.LinkMovementMethod
import android.util.Log import android.util.Log
import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup
import android.widget.ImageView import android.widget.ImageView
import android.widget.ScrollView import android.widget.ScrollView
import android.widget.TextView import android.widget.TextView
import androidx.annotation.VisibleForTesting
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
@@ -72,48 +75,69 @@ private data class TextModel(
* 2. How the data will be stored * 2. How the data will be stored
* 3. How the user can access and remove their data * 3. How the user can access and remove their data
*/ */
class FingerprintEnrollIntroV2Fragment : Fragment(R.layout.fingerprint_v2_enroll_introduction) { 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
override fun onCreate(savedInstanceState: Bundle?) { /** Used for testing purposes */
super.onCreate(savedInstanceState) private var factory: ViewModelProvider.Factory? = null
navigationViewModel =
ViewModelProvider(requireActivity())[FingerprintEnrollNavigationViewModel::class.java] @VisibleForTesting
fingerprintEnrollViewModel = constructor(theFactory: ViewModelProvider.Factory) : this() {
ViewModelProvider(requireActivity())[FingerprintEnrollViewModel::class.java] factory = theFactory
fingerprintScrollViewModel =
ViewModelProvider(requireActivity())[FingerprintScrollViewModel::class.java]
gateKeeperViewModel =
ViewModelProvider(requireActivity())[FingerprintGatekeeperViewModel::class.java]
} }
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { private val viewModelProvider: ViewModelProvider by lazy {
super.onViewCreated(view, savedInstanceState) if (factory != null) {
ViewModelProvider(requireActivity(), factory!!)
} else {
ViewModelProvider(requireActivity())
}
}
lifecycleScope.launch { private lateinit var footerBarMixin: FooterBarMixin
combine( private lateinit var textModel: TextModel
navigationViewModel.enrollType,
fingerprintEnrollViewModel.sensorType,
) { enrollType, sensorType ->
Pair(enrollType, sensorType)
}
.collect { (enrollType, sensorType) ->
textModel =
when (enrollType) {
Unicorn -> getUnicornTextModel()
else -> getNormalTextModel()
}
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) { override fun onCreateView(
getLayout()?.setHeaderText(textModel.headerText) inflater: LayoutInflater,
getLayout()?.setDescriptionText(textModel.descriptionText) 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. // Set color filter for the following icons.
val colorFilter = getIconColorFilter() val colorFilter = getIconColorFilter()
@@ -158,9 +182,9 @@ class FingerprintEnrollIntroV2Fragment : Fragment(R.layout.fingerprint_v2_enroll
view.requireViewById<TextView?>(R.id.footer_title_1).setText(textModel.footerTitleOne) view.requireViewById<TextView?>(R.id.footer_title_1).setText(textModel.footerTitleOne)
view.requireViewById<TextView?>(R.id.footer_title_2).setText(textModel.footerTitleOne) view.requireViewById<TextView?>(R.id.footer_title_2).setText(textModel.footerTitleOne)
} }
} }
return view
} }
}
private fun setFooterLink(view: View) { private fun setFooterLink(view: View) {
val footerLink: TextView = view.requireViewById(R.id.footer_learn_more) val footerLink: TextView = view.requireViewById(R.id.footer_learn_more)
@@ -185,17 +209,18 @@ class FingerprintEnrollIntroV2Fragment : Fragment(R.layout.fingerprint_v2_enroll
navigationViewModel.nextStep() 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 = layout.getMixin(FooterBarMixin::class.java)
footerBarMixin.primaryButton = footerBarMixin.primaryButton =
FooterButton.Builder(requireActivity()) FooterButton.Builder(requireContext())
.setText(R.string.security_settings_face_enroll_introduction_more) .setText(R.string.security_settings_face_enroll_introduction_more)
.setListener(onNextButtonClick) .setListener(onNextButtonClick)
.setButtonType(FooterButton.ButtonType.OPT_IN) .setButtonType(FooterButton.ButtonType.OPT_IN)
.setTheme(com.google.android.setupdesign.R.style.SudGlifButton_Primary) .setTheme(com.google.android.setupdesign.R.style.SudGlifButton_Primary)
.build() .build()
footerBarMixin.setSecondaryButton( footerBarMixin.setSecondaryButton(
FooterButton.Builder(requireActivity()) FooterButton.Builder(requireContext())
.setText(textModel.negativeButton) .setText(textModel.negativeButton)
.setListener({ Log.d(TAG, "prevClicked") }) .setListener({ Log.d(TAG, "prevClicked") })
.setButtonType(FooterButton.ButtonType.NEXT) .setButtonType(FooterButton.ButtonType.NEXT)
@@ -211,8 +236,8 @@ class FingerprintEnrollIntroV2Fragment : Fragment(R.layout.fingerprint_v2_enroll
val requireScrollMixin = layout.getMixin(RequireScrollMixin::class.java) val requireScrollMixin = layout.getMixin(RequireScrollMixin::class.java)
requireScrollMixin.requireScrollWithButton( requireScrollMixin.requireScrollWithButton(
requireActivity(), requireContext(),
footerBarMixin.primaryButton, primaryButton,
R.string.security_settings_face_enroll_introduction_more, R.string.security_settings_face_enroll_introduction_more,
onNextButtonClick onNextButtonClick
) )
@@ -224,7 +249,7 @@ class FingerprintEnrollIntroV2Fragment : Fragment(R.layout.fingerprint_v2_enroll
} }
} }
lifecycleScope.launch { viewLifecycleOwner.lifecycleScope.launch {
fingerprintScrollViewModel.hasReadConsentScreen.collect { consented -> fingerprintScrollViewModel.hasReadConsentScreen.collect { consented ->
if (consented) { if (consented) {
primaryButton.setText( 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 // the flow. For instance if someone launches the activity with an invalid challenge, it
// either 1) Fails or 2) Launched confirmDeviceCredential // either 1) Fails or 2) Launched confirmDeviceCredential
primaryButton.isEnabled = false primaryButton.isEnabled = false
lifecycleScope.launch { viewLifecycleOwner.lifecycleScope.launch {
gateKeeperViewModel.hasValidGatekeeperInfo.collect { primaryButton.isEnabled = it } gateKeeperViewModel.hasValidGatekeeperInfo.collect { primaryButton.isEnabled = it }
} }
} }
@@ -284,8 +309,4 @@ class FingerprintEnrollIntroV2Fragment : Fragment(R.layout.fingerprint_v2_enroll
PorterDuff.Mode.SRC_IN PorterDuff.Mode.SRC_IN
) )
} }
private fun getLayout(): GlifLayout? {
return requireView().findViewById(R.id.setup_wizard_layout) as GlifLayout?
}
} }

View File

@@ -31,11 +31,6 @@ import kotlinx.coroutines.launch
private const val TAG = "FingerprintEnrollNavigationViewModel" 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. * The [EnrollType] for fingerprint enrollment indicates information on how the flow should behave.
*/ */
@@ -56,7 +51,6 @@ object Unicorn : EnrollType()
*/ */
class FingerprintEnrollNavigationViewModel( class FingerprintEnrollNavigationViewModel(
private val dispatcher: CoroutineDispatcher, private val dispatcher: CoroutineDispatcher,
private val validator: Validator,
private val fingerprintManagerInteractor: FingerprintManagerInteractor, private val fingerprintManagerInteractor: FingerprintManagerInteractor,
private val gatekeeperViewModel: FingerprintGatekeeperViewModel, private val gatekeeperViewModel: FingerprintGatekeeperViewModel,
private val canSkipConfirm: Boolean private val canSkipConfirm: Boolean
@@ -145,11 +139,6 @@ class FingerprintEnrollNavigationViewModel(
return FingerprintEnrollNavigationViewModel( return FingerprintEnrollNavigationViewModel(
backgroundDispatcher, backgroundDispatcher,
object : Validator {
override fun validateGateKeeper(challenge: Long?): Boolean {
return challenge != null
}
},
fingerprintManagerInteractor, fingerprintManagerInteractor,
fingerprintGatekeeperViewModel, fingerprintGatekeeperViewModel,
canSkipConfirm, canSkipConfirm,

View File

@@ -25,6 +25,8 @@ android_app {
"androidx.fragment_fragment-testing", "androidx.fragment_fragment-testing",
"frameworks-base-testutils", "frameworks-base-testutils",
"androidx.fragment_fragment", "androidx.fragment_fragment",
"androidx.lifecycle_lifecycle-runtime-testing",
"kotlinx_coroutines_test",
], ],
aaptflags: ["--extra-packages com.android.settings"], aaptflags: ["--extra-packages com.android.settings"],

View File

@@ -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<FingerprintEnrollIntroV2Fragment>
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 <T : ViewModel> create(
modelClass: Class<T>,
): 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<GlifLayout>(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)
)
)
)
)
}
}