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:
10
res/drawable/ic_lock_open_24dp.xml
Normal file
10
res/drawable/ic_lock_open_24dp.xml
Normal 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>
|
142
res/layout/remote_auth_enroll_introduction.xml
Normal file
142
res/layout/remote_auth_enroll_introduction.xml
Normal 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>
|
78
res/layout/remote_auth_introduction_image_carousel.xml
Normal file
78
res/layout/remote_auth_introduction_image_carousel.xml
Normal 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>
|
40
res/layout/remote_auth_introduction_image_carousel_item.xml
Normal file
40
res/layout/remote_auth_introduction_image_carousel_item.xml
Normal 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>
|
@@ -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
1
res/raw/remoteauth_explanation_swipe_animation.json
Normal file
1
res/raw/remoteauth_explanation_swipe_animation.json
Normal file
File diff suppressed because one or more lines are too long
@@ -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>
|
||||
|
@@ -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 didn’t 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 & Face Unlock</string>
|
||||
|
@@ -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"
|
||||
}
|
||||
}
|
@@ -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"
|
||||
}
|
||||
}
|
@@ -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))
|
||||
}
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user