Remote authenticator enrollment introduction layout.

This flow will be included in Device Unlock settings with the
Fingerprint and Face Unlock.

Bug: b/293908278
Test: atest RemoteAuthEnrollIntroductionTest
Change-Id: Ia20662e897f925d82547550e25b79a2e61d2dc34
This commit is contained in:
Justin McClain
2023-08-08 19:03:57 +00:00
parent 524643a6c8
commit f71909220b
12 changed files with 634 additions and 7 deletions

View File

@@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="960"
android:viewportHeight="960"
android:tint="?attr/colorControlNormal">
<path
android:fillColor="@android:color/white"
android:pathData="M240,320h360v-80q0,-50 -35,-85t-85,-35q-50,0 -85,35t-35,85h-80q0,-83 58.5,-141.5T480,40q83,0 141.5,58.5T680,240v80h40q33,0 56.5,23.5T800,400v400q0,33 -23.5,56.5T720,880L240,880q-33,0 -56.5,-23.5T160,800v-400q0,-33 23.5,-56.5T240,320ZM240,800h480v-400L240,400v400ZM480,680q33,0 56.5,-23.5T560,600q0,-33 -23.5,-56.5T480,520q-33,0 -56.5,23.5T400,600q0,33 23.5,56.5T480,680ZM240,800v-400,400Z"/>
</vector>

View File

@@ -0,0 +1,142 @@
<?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"
android:id="@+id/setup_wizard_layout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:icon="@drawable/ic_lock"
app:sucUsePartnerResource="false"
app:sucHeaderText="@string/security_settings_remoteauth_enroll_introduction_title"
app:sudDescriptionText="@string/security_settings_remoteauth_enroll_introduction_message">
<androidx.constraintlayout.widget.ConstraintLayout
style="@style/SudContentFrame"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingHorizontal="@dimen/remoteauth_fragment_padding_horizontal"
app:layout_optimizationLevel="barrier">
<com.android.settings.remoteauth.introduction.IntroductionImageCarousel
android:id="@+id/image_carousel_view"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<!-- How it works -->
<TextView
android:id="@+id/explanation_subheading_how_it_works"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
style="@style/BiometricEnrollIntroTitle"
android:text="@string/security_settings_remoteauth_enroll_introduction_how_title"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/image_carousel_view" />
<ImageView
android:id="@+id/explanation_icon_lock_open"
android:layout_width="@dimen/remoteauth_icon_small_size"
android:layout_height="@dimen/remoteauth_icon_small_size"
android:importantForAccessibility="no"
android:background="@drawable/ic_lock_open_24dp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/explanation_subheading_how_it_works" />
<TextView
android:id="@+id/explanation_lock_open_description"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/remoteauth_introduction_description_start_margin"
style="@style/BiometricEnrollIntroMessage"
android:text="@string/security_settings_remoteauth_enroll_introduction_info_lock_open"
app:layout_constrainedWidth="true"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@id/explanation_icon_lock_open"
app:layout_constraintTop_toTopOf="@id/explanation_icon_lock_open" />
<androidx.constraintlayout.widget.Barrier
android:id="@+id/explanation_lock_open_description_barrier"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:barrierDirection="bottom"
app:constraint_referenced_ids="explanation_icon_lock_open, explanation_lock_open_description" />
<ImageView
android:id="@+id/explanation_icon_notifications"
android:layout_width="@dimen/remoteauth_icon_small_size"
android:layout_height="@dimen/remoteauth_icon_small_size"
android:importantForAccessibility="no"
android:background="@drawable/ic_notifications"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/explanation_lock_open_description_barrier"/>
<TextView
android:id="@+id/explanation_notifications_description"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/remoteauth_introduction_description_start_margin"
style="@style/BiometricEnrollIntroMessage"
android:text="@string/security_settings_remoteauth_enroll_introduction_info_notifications"
app:layout_constrainedWidth="true"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@id/explanation_icon_notifications"
app:layout_constraintTop_toTopOf="@id/explanation_icon_notifications" />
<androidx.constraintlayout.widget.Barrier
android:id="@+id/explanation_notifications_description_barrier"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:barrierDirection="bottom"
app:constraint_referenced_ids="explanation_icon_notifications,explanation_notifications_description" />
<!-- You're in control -->
<TextView
android:id="@+id/explanation_subheading_youre_in_control"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
style="@style/BiometricEnrollIntroTitle"
android:text="@string/security_settings_remoteauth_enroll_introduction_youre_in_control_title"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/explanation_notifications_description_barrier" />
<ImageView
android:id="@+id/explanation_icon_remove_watch"
android:layout_width="@dimen/remoteauth_icon_small_size"
android:layout_height="@dimen/remoteauth_icon_small_size"
android:importantForAccessibility="no"
android:background="@drawable/ic_delete"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/explanation_subheading_youre_in_control" />
<TextView
android:id="@+id/explanation_remove_watch_description"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/remoteauth_introduction_description_start_margin"
style="@style/BiometricEnrollIntroMessage"
android:text="@string/security_settings_remoteauth_enroll_introduction_info_remove_watch"
app:layout_constrainedWidth="true"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@id/explanation_icon_remove_watch"
app:layout_constraintTop_toTopOf="@id/explanation_icon_remove_watch" />
</androidx.constraintlayout.widget.ConstraintLayout>
</com.google.android.setupdesign.GlifLayout>

View File

@@ -0,0 +1,78 @@
<?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.
-->
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingHorizontal="@dimen/remoteauth_introduction_fragment_padding_horizontal"
app:layout_optimizationLevel="barrier">
<androidx.viewpager2.widget.ViewPager2
android:id="@+id/image_carousel"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<ImageView
android:id="@+id/carousel_back_arrow"
app:layout_constraintStart_toStartOf="parent"
android:contentDescription="@string/wizard_back"
android:layout_width="@dimen/remoteauth_touchable_area_minimum_span"
android:layout_height="@dimen/remoteauth_touchable_area_minimum_span"
android:scaleType="center"
android:tintMode="src_in"
app:layout_constraintEnd_toStartOf="@id/carousel_progress_indicator"
app:layout_constraintHorizontal_chainStyle="spread_inside"
app:layout_constraintTop_toTopOf="@id/carousel_progress_indicator"
app:layout_constraintBottom_toBottomOf="@id/carousel_progress_indicator"
android:background="@drawable/ic_arrow_back" />
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/carousel_progress_indicator"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:importantForAccessibility="no"
app:layout_constraintStart_toEndOf="@id/carousel_back_arrow"
app:layout_constraintEnd_toStartOf="@id/carousel_forward_arrow"
app:layout_constraintTop_toBottomOf="@id/image_carousel"
android:layout_marginTop="@dimen/remoteauth_carousel_progress_margin" />
<ImageView
android:id="@+id/carousel_forward_arrow"
android:contentDescription="@string/wizard_next"
android:layout_width="@dimen/remoteauth_touchable_area_minimum_span"
android:layout_height="@dimen/remoteauth_touchable_area_minimum_span"
android:scaleType="center"
android:tintMode="src_in"
app:layout_constraintStart_toEndOf="@id/carousel_progress_indicator"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="@id/carousel_progress_indicator"
app:layout_constraintBottom_toBottomOf="@id/carousel_progress_indicator"
android:background="@drawable/ic_arrow_forward" />
<androidx.constraintlayout.widget.Barrier
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:barrierMargin="@dimen/remoteauth_carousel_progress_margin"
app:barrierDirection="bottom"
app:constraint_referenced_ids="carousel_back_arrow,carousel_forward_arrow" />
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@@ -0,0 +1,40 @@
<?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.
-->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/explanation_animation_container"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:gravity="center_horizontal">
<com.airbnb.lottie.LottieAnimationView
android:id="@+id/explanation_animation"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:importantForAccessibility="no"
app:lottie_loop="true"
app:lottie_autoPlay="true" />
<TextView
android:id="@+id/carousel_text"
android:textSize="@dimen/remoteauth_introduction_subheading_text_size"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
</LinearLayout>

View File

@@ -0,0 +1,23 @@
<?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.
-->
<ImageView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="@dimen/remoteauth_carousel_progress_circle_diameter"
android:layout_height="@dimen/remoteauth_carousel_progress_circle_diameter"
android:layout_marginHorizontal="@dimen/remoteauth_carousel_progress_circle_margin"
android:background="@drawable/ring_progress" />

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -171,6 +171,15 @@
<!-- RemoteAuth-->
<dimen name="remoteauth_fragment_padding_horizontal">40dp</dimen>
<dimen name="remoteauth_icon_small_size">24dp</dimen>
<dimen name="remoteauth_touchable_area_minimum_span">48dp</dimen>
<dimen name="remoteauth_introduction_fragment_padding_horizontal">30dp</dimen>
<dimen name="remoteauth_introduction_description_start_margin">8dp</dimen>
<dimen name="remoteauth_introduction_subheading_text_size">18sp</dimen>
<dimen name="remoteauth_carousel_progress_margin">28dp</dimen>
<dimen name="remoteauth_carousel_progress_circle_diameter">8dp</dimen>
<dimen name="remoteauth_carousel_progress_circle_margin">4dp</dimen>
<!-- Lock pattern view size, align sysui biometric_auth_pattern_view_size -->
<dimen name="biometric_auth_pattern_view_size">348dp</dimen>

View File

@@ -885,19 +885,39 @@
<string name="security_settings_fingerprint_multiple_face_watch_preference_summary">Face, fingerprints, and <xliff:g id="watch" example="Dani's Watch">%s</xliff:g> added</string>
<!-- RemoteAuth unlock enrollment and settings --><skip />
<!-- Strings for RemoteAuth enroll introduction page -->
<!-- Introduction title shown in remote enrollment to introduce the remote feature [CHAR LIMIT=29] -->
<string name="security_settings_remoteauth_enroll_introduction_title">Set up your watch</string>
<!-- Summary of the Watch Unlock feature that allows users to unlock the phone with paired watches [CHAR_LIMIT=NONE]-->
<string name="security_settings_remoteauth_enroll_introduction_message">Watch Unlock is another convenient way to unlock this phone, for example, when your fingers are wet or face isn\u2019t recognized.\n\nYou can use your watch to unlock this phone when you:</string>
<!-- Button text to cancel enrollment [CHAR LIMIT=30] -->
<string name="security_settings_remoteauth_enroll_introduction_disagree">Not now</string>
<!-- Button text to start enrollment [CHAR LIMIT=30] -->
<string name="security_settings_remoteauth_enroll_introduction_agree">Continue</string>
<!-- Button text to scroll to the end of a scrollview. [CHAR LIMIT=30] -->
<string name="security_settings_remoteauth_enroll_introduction_more">More</string>
<!-- Heading of the paragraph that explains how the Watch Unlock feature works [CHAR_LIMIT=NONE] -->
<string name="security_settings_remoteauth_enroll_introduction_how_title">How it works</string>
<!-- Explains that paired watches must be unlocked, on-body, and nearby, in order to unlock the phone [CHAR_LIMIT=NONE] -->
<string name="security_settings_remoteauth_enroll_introduction_info_lock_open">Your watch must be unlocked, on your wrist, and within reach of this phone. You won\u2019t need to unlock your watch again while it\u2019s on your wrist.</string>
<!-- Explains that users will be notified on the watch when the phone is unlocked by the watch, and they can tap on the notification to lock the phone from the watch [CHAR_LIMIT=NONE] -->
<string name="security_settings_remoteauth_enroll_introduction_info_notifications">When this phone is unlocked, you\u2019ll get notified on your watch. If it was unlocked when you didnt intend to, tap the notification to lock the phone again.</string>
<!-- Heading of the paragraph that explains how to opt out of the Watch Unlock feature [CHAR_LIMIT=NONE] -->
<string name="security_settings_remoteauth_enroll_introduction_youre_in_control_title">You\u2019re in control</string>
<!-- Explains that users can prevent their watches from unlocking their phone by removing them in the Watch Unlock settings [CHAR_LIMIT=NONE] -->
<string name="security_settings_remoteauth_enroll_introduction_info_remove_watch">You can remove your watch from Watch Unlock at any time in Settings</string>
<!-- Subtitle for animation that explains users can unlock by tapping a notification [CHAR_LIMIT=NONE] -->
<string name="security_settings_remoteauth_enroll_introduction_animation_tap_notification">Tap a notification</string>
<!-- Subtitle for animation that explains users can unlock by swiping up on the lock screen [CHAR_LIMIT=NONE] -->
<string name="security_settings_remoteauth_enroll_introduction_animation_swipe_up">Swipe up on the lock screen</string>
<!-- Strings for RemoteAuth enroll finish page -->
<!-- Title of the dialog that shows when a paired watch has been set up successfully and can be used to unlock the phone [CHAR_LIMIT=45] -->
<string name="security_settings_remoteauth_enroll_finish_title">
You\'re all set!
</string>
<string name="security_settings_remoteauth_enroll_finish_title">You\u2019re all set!</string>
<!-- Explains when a paired watch can be used to unlock the phone after it has been set up successfully [CHAR_LIMIT=NONE] -->
<string name="security_settings_remoteauth_enroll_finish_description">
You can now use your watch to unlock this phone when you swipe up on the lock screen or tap a notification
</string>
<string name="security_settings_remoteauth_enroll_finish_description">You can now use your watch to unlock this phone when you swipe up on the lock screen or tap a notification</string>
<!-- Button text to finish enrollment [CHAR LIMIT=30] -->
<string name="security_settings_remoteauth_enroll_finish_btn_next">Done</string>
<!-- Biometric settings --><skip />
<!-- Title shown for menu item that launches biometric settings. [CHAR LIMIT=66] -->
<string name="security_settings_biometric_preference_title">Fingerprint &amp; Face Unlock</string>

View File

@@ -0,0 +1,159 @@
/*
* 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.remoteauth.introduction
import android.content.Context
import android.util.AttributeSet
import android.util.Log
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.ImageView
import android.widget.TextView
import androidx.annotation.VisibleForTesting
import androidx.constraintlayout.widget.ConstraintLayout
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import androidx.viewpager2.widget.MarginPageTransformer
import androidx.viewpager2.widget.ViewPager2
import com.airbnb.lottie.LottieAnimationView
import com.android.settings.R
import com.android.settingslib.widget.LottieColorUtils
class IntroductionImageCarousel : ConstraintLayout {
private val carousel: ViewPager2 by lazy { findViewById<ViewPager2>(R.id.image_carousel) }
private val progressIndicator: RecyclerView by lazy {
findViewById<RecyclerView>(R.id.carousel_progress_indicator)
}
private val backArrow: ImageView by lazy { findViewById<ImageView>(R.id.carousel_back_arrow) }
private val forwardArrow: ImageView by lazy {
findViewById<ImageView>(R.id.carousel_forward_arrow)
}
private val progressIndicatorAdapter = ProgressIndicatorAdapter()
// The index of the current animation we are on
private var currentPage = 0
set(value) {
val pageRange = 0..(ANIMATION_LIST.size - 1)
field = value.coerceIn(pageRange)
backArrow.isEnabled = field > pageRange.start
forwardArrow.isEnabled = field < pageRange.endInclusive
carousel.setCurrentItem(field)
progressIndicatorAdapter.currentIndex = field
}
private val onPageChangeCallback =
object : ViewPager2.OnPageChangeCallback() {
override fun onPageSelected(position: Int) {
currentPage = position
}
}
constructor(context: Context) : super(context)
constructor(context: Context, attrSet: AttributeSet?) : super(context, attrSet)
init {
LayoutInflater.from(context).inflate(R.layout.remote_auth_introduction_image_carousel, this)
with(carousel) {
setPageTransformer(
MarginPageTransformer(
context.resources.getDimension(R.dimen.remoteauth_introduction_fragment_padding_horizontal).toInt()
)
)
adapter = ImageCarouselAdapter()
registerOnPageChangeCallback(onPageChangeCallback)
}
with(progressIndicator) {
layoutManager = LinearLayoutManager(context, LinearLayoutManager.HORIZONTAL, false)
adapter = progressIndicatorAdapter
}
backArrow.setOnClickListener { currentPage-- }
forwardArrow.setOnClickListener { currentPage++ }
}
fun unregister() {
carousel.unregisterOnPageChangeCallback(onPageChangeCallback)
}
private class AnimationViewHolder(val context: Context, itemView: View) : RecyclerView.ViewHolder(itemView) {
val animationView = itemView.requireViewById<LottieAnimationView>(R.id.explanation_animation)
val descriptionText = itemView.requireViewById<TextView>(R.id.carousel_text)
}
/** Adapter for the onboarding animations. */
private class ImageCarouselAdapter : RecyclerView.Adapter<AnimationViewHolder>() {
override fun getItemCount() = ANIMATION_LIST.size
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) =
AnimationViewHolder(parent.context, LayoutInflater.from(parent.context).inflate(R.layout.remote_auth_introduction_image_carousel_item, parent, false))
override fun onBindViewHolder(holder: AnimationViewHolder, position: Int) {
with(holder.animationView) {
setAnimation(ANIMATION_LIST[position].first)
LottieColorUtils.applyDynamicColors(holder.context, this)
}
holder.descriptionText.setText(ANIMATION_LIST[position].second)
with(holder.itemView) {
// This makes sure that the proper description text instead of a generic "Page" label is
// verbalized by Talkback when switching to a new page on the ViewPager2.
contentDescription = context.getString(ANIMATION_LIST[position].second)
}
}
}
/** Adapter for icons indicating carousel progress. */
private class ProgressIndicatorAdapter : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
var currentIndex: Int = 0
set(value) {
val previousIndex = field
field = value.coerceIn(0, getItemCount() - 1)
notifyItemChanged(previousIndex)
notifyItemChanged(field)
}
override fun getItemCount() = ANIMATION_LIST.size
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) =
object :
RecyclerView.ViewHolder(LayoutInflater.from(parent.context).inflate(R.layout.remote_auth_introduction_image_carousel_progress_icon, parent, false)) {}
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
holder.itemView.isSelected = position == currentIndex
}
}
companion object {
@VisibleForTesting
val ANIMATION_LIST =
listOf(
Pair(
R.raw.remoteauth_explanation_swipe_animation,
R.string.security_settings_remoteauth_enroll_introduction_animation_swipe_up
),
Pair(
R.raw.remoteauth_explanation_notification_animation,
R.string.security_settings_remoteauth_enroll_introduction_animation_tap_notification
),
)
const val TAG = "RemoteAuthCarousel"
}
}

View File

@@ -0,0 +1,92 @@
/*
* 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.remoteauth.introduction
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import com.android.settings.R
import com.android.settings.remoteauth.RemoteAuthEnrollBase
import com.google.android.setupcompat.template.FooterButton
import com.google.android.setupdesign.template.RequireScrollMixin
/**
* Provides introductory info about remote authenticator unlock.
*/
class RemoteAuthEnrollIntroduction :
RemoteAuthEnrollBase(
layoutResId = R.layout.remote_auth_enroll_introduction,
glifLayoutId = R.id.setup_wizard_layout,
) {
override fun onCreateView(
inflater: LayoutInflater,
viewGroup: ViewGroup?,
savedInstanceArgs: Bundle?
) =
super.onCreateView(inflater, viewGroup, savedInstanceArgs).also {
initializeRequireScrollMixin(it)
}
override fun initializePrimaryFooterButton() : FooterButton {
return FooterButton.Builder(context!!)
.setText(R.string.security_settings_remoteauth_enroll_introduction_agree)
.setListener(::onPrimaryFooterButtonClick)
.setButtonType(FooterButton.ButtonType.OPT_IN)
.setTheme(R.style.SudGlifButton_Primary)
.build()
}
override fun initializeSecondaryFooterButton() : FooterButton {
return FooterButton.Builder(context!!)
.setText(R.string.security_settings_remoteauth_enroll_introduction_disagree)
.setListener(::onSecondaryFooterButtonClick)
.setButtonType(FooterButton.ButtonType.NEXT)
.setTheme(R.style.SudGlifButton_Primary)
.build()
}
private fun onPrimaryFooterButtonClick(view: View) {
// TODO(b/293906345): Wire up navigation
}
private fun onSecondaryFooterButtonClick(view: View) {
// TODO(b/293906345): Wire up navigation
}
private fun initializeRequireScrollMixin(view: View) {
val layout = getGlifLayout(view)
secondaryFooterButton?.visibility = View.INVISIBLE
val requireScrollMixin = layout.getMixin(RequireScrollMixin::class.java)
requireScrollMixin.requireScrollWithButton(requireContext(), primaryFooterButton,
R.string.security_settings_remoteauth_enroll_introduction_more, ::onPrimaryFooterButtonClick)
requireScrollMixin.setOnRequireScrollStateChangedListener { scrollNeeded ->
if (scrollNeeded) {
primaryFooterButton.setText(requireContext(), R.string.security_settings_remoteauth_enroll_introduction_more)
} else {
primaryFooterButton.setText(requireContext(), R.string.security_settings_remoteauth_enroll_introduction_agree)
secondaryFooterButton?.visibility = View.VISIBLE
}
}
}
private companion object {
const val TAG = "RemoteAuthEnrollIntro"
}
}

View File

@@ -0,0 +1,52 @@
/*
* 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.remoteauth.introduction
import android.content.Context
import android.os.Bundle
import androidx.fragment.app.testing.launchFragmentInContainer
import androidx.test.core.app.ApplicationProvider
import com.android.settings.R
import com.google.android.setupdesign.GlifLayout
import com.google.common.truth.Truth.assertThat
import org.junit.Test
import org.junit.runner.RunWith
import org.robolectric.RobolectricTestRunner
@RunWith(RobolectricTestRunner::class)
class RemoteAuthEnrollIntroductionTest {
private var mContext : Context = ApplicationProvider.getApplicationContext()
@Test
fun testRemoteAuthenticatorEnrollIntroduction_hasHeader() {
launchFragmentInContainer<RemoteAuthEnrollIntroduction>(Bundle(), R.style.SudThemeGlif)
.onFragment {
assertThat((it.view as GlifLayout).headerText)
.isEqualTo(mContext.getString(R.string.security_settings_remoteauth_enroll_introduction_title))
}
}
@Test
fun testRemoteAuthenticatorEnrollIntroduction_hasDescription() {
launchFragmentInContainer<RemoteAuthEnrollIntroduction>(Bundle(), R.style.SudThemeGlif)
.onFragment {
assertThat((it.view as GlifLayout).descriptionText)
.isEqualTo(mContext.getString(R.string.security_settings_remoteauth_enroll_introduction_message))
}
}
}