2/n: Add default implementation for multi-biometric enroll

1) Adds a layout for multi-biometric selection in BiometricEnrollActivity
2) Adds widgets for checkboxes
3) Shows ConfirmLock*/ChooseLock* for multi-biometric devices in
   BiometricEnrollActivity
4) finish()'s when loses foreground
5) Adds default string for ChooseLock* and multi-biometrics, e.g.
   "Set up Password + Biometrics", as well as associated plumbing
   to bring the user back to BiometricEnrollActivity once the
   credential is enrolled
6) When max templates enrolled, checkbox becomes disabled and
   description string is updated

Bug: 162341940
Bug: 152242790
Fixes: 161742393

No effect on existing devices with the following:
Test: adb shell am start -a android.settings.BIOMETRIC_ENROLL
Test: SUW
Test: make -j RunSettingsRoboTests

Exempt-From-Owner-Approval: Biometric-related change
to EncryptionInterstitial

Change-Id: I855460d50228ace24d4ec5fbe330f02ab406cc02
This commit is contained in:
Kevin Chyn
2020-09-09 13:28:28 -07:00
parent eb8c0f14ea
commit 87bb772e16
35 changed files with 1281 additions and 270 deletions

View File

@@ -14,13 +14,12 @@
~ See the License for the specific language governing permissions and ~ See the License for the specific language governing permissions and
~ limitations under the License ~ limitations under the License
--> -->
<vector xmlns:android="http://schemas.android.com/apk/res/android" <vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp" android:width="24dp"
android:height="24dp" android:height="24dp"
android:viewportWidth="24" android:viewportWidth="24"
android:viewportHeight="24"> android:viewportHeight="24"
android:tint="?android:attr/colorControlNormal">
<path <path
android:fillColor="#000000" android:fillColor="#000000"
android:pathData="M10.25,13c0,0.69-0.56,1.25-1.25,1.25S7.75,13.69,7.75,13S8.31,11.75,9,11.75S10.25,12.31,10.25,13z M15,11.75 c-0.69,0-1.25,0.56-1.25,1.25s0.56,1.25,1.25,1.25s1.25-0.56,1.25-1.25S15.69,11.75,15,11.75z M22,12c0,5.52-4.48,10-10,10 S2,17.52,2,12S6.48,2,12,2S22,6.48,22,12z M20,12c0-0.78-0.12-1.53-0.33-2.24C18.97,9.91,18.25,10,17.5,10 c-3.13,0-5.92-1.44-7.76-3.69c-1.05,2.56-3.14,4.57-5.74,5.55C4.01,11.9,4,11.95,4,12c0,4.41,3.59,8,8,8S20,16.41,20,12z" /> android:pathData="M10.25,13c0,0.69-0.56,1.25-1.25,1.25S7.75,13.69,7.75,13S8.31,11.75,9,11.75S10.25,12.31,10.25,13z M15,11.75 c-0.69,0-1.25,0.56-1.25,1.25s0.56,1.25,1.25,1.25s1.25-0.56,1.25-1.25S15.69,11.75,15,11.75z M22,12c0,5.52-4.48,10-10,10 S2,17.52,2,12S6.48,2,12,2S22,6.48,22,12z M20,12c0-0.78-0.12-1.53-0.33-2.24C18.97,9.91,18.25,10,17.5,10 c-3.13,0-5.92-1.44-7.76-3.69c-1.05,2.56-3.14,4.57-5.74,5.55C4.01,11.9,4,11.95,4,12c0,4.41,3.59,8,8,8S20,16.41,20,12z" />

View File

@@ -0,0 +1,64 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2018 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.
-->
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<!-- Icon -->
<ImageView
android:id="@+id/icon"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerVertical="true"
android:layout_alignParentStart="true" />
<!-- Title -->
<com.google.android.setupdesign.view.RichTextView
style="@style/SudDescription.Glif"
android:id="@+id/title"
android:paddingHorizontal="8dp"
android:paddingTop="16dp"
android:layout_marginBottom="4dp"
android:gravity="start"
android:layout_toEndOf="@+id/icon"
android:layout_toStartOf="@+id/checkbox"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
<!-- Description -->
<TextView
android:id="@+id/description"
android:paddingHorizontal="8dp"
android:paddingBottom="16dp"
android:layout_toEndOf="@+id/icon"
android:layout_toStartOf="@+id/checkbox"
android:layout_below="@+id/title"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
<!-- Check Box -->
<CheckBox
android:layout_alignParentEnd="true"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/checkbox"
android:layout_centerVertical="true"
android:checked="true"/>
</RelativeLayout>

View File

@@ -0,0 +1,80 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
~ Copyright (C) 2020 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"
xmlns:BiometricEnrollCheckbox="http://schemas.android.com/apk/res/com.android.settings"
style="?attr/face_layout_theme"
app:sucHeaderText="@string/multi_biometric_enroll_title"
android:id="@+id/setup_wizard_layout"
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout
style="@style/SudContentFrame"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<com.google.android.setupdesign.view.RichTextView
android:id="@+id/sud_layout_description"
style="@style/SudDescription.Glif"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/multi_biometric_enroll_subtitle"/>
<com.google.android.setupdesign.view.IllustrationVideoView
android:id="@+id/illustration_normal"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
style="@style/SudContentIllustration"
app:sudVideo="@raw/face_settings"/>
<TextView
style="@style/SudDescription.Glif"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/setup_with"
android:text="@string/multi_biometric_enroll_setup_with"
android:gravity="start"/>
<include layout="@layout/horizontal_divider"/>
<com.android.settings.biometrics.BiometricEnrollCheckbox
android:id="@+id/checkbox_face"
android:layout_width="match_parent"
android:layout_height="wrap_content"
BiometricEnrollCheckbox:title="@string/multi_biometric_enroll_face_unlock_title"
BiometricEnrollCheckbox:description="@string/multi_biometric_enroll_face_unlock_description"
BiometricEnrollCheckbox:icon="@drawable/ic_face_24dp"/>
<include layout="@layout/horizontal_divider"/>
<com.android.settings.biometrics.BiometricEnrollCheckbox
android:id="@+id/checkbox_fingerprint"
android:layout_width="match_parent"
android:layout_height="wrap_content"
BiometricEnrollCheckbox:title="@string/multi_biometric_enroll_fingerprint_unlock_title"
BiometricEnrollCheckbox:description="@string/multi_biometric_enroll_fingerprint_unlock_description"
BiometricEnrollCheckbox:icon="@drawable/ic_fingerprint_24dp"/>
<include layout="@layout/horizontal_divider"/>
</LinearLayout>
</com.google.android.setupdesign.GlifLayout>

View File

@@ -16,12 +16,12 @@
--> -->
<TextView xmlns:android="http://schemas.android.com/apk/res/android" <TextView xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/face_header_description" android:id="@+id/biometric_header_description"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:gravity="center_vertical" android:gravity="center_vertical"
android:minHeight="56dp" android:minHeight="56dp"
android:paddingStart="?android:attr/listPreferredItemPaddingStart" android:paddingStart="?android:attr/listPreferredItemPaddingStart"
android:paddingEnd="?android:attr/listPreferredItemPaddingEnd" android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"
android:text="@string/lock_settings_picker_face_message" android:text="@string/lock_settings_picker_biometric_message"
style="@style/FaceHeaderStyle" /> style="@style/BiometricHeaderStyle" />

View File

@@ -1,26 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2015 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.
-->
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/fingerprint_header_description"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center_vertical"
android:minHeight="56dp"
android:paddingStart="?android:attr/listPreferredItemPaddingStart"
android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"
android:text="@string/lock_settings_picker_fingerprint_message"
style="@style/FingerprintHeaderStyle" />

View File

@@ -148,6 +148,13 @@
<attr name="thickness" format="dimension" /> <attr name="thickness" format="dimension" />
</declare-styleable> </declare-styleable>
<!-- For biometric enroll checkboxes -->
<declare-styleable name="BiometricEnrollCheckbox">
<attr name="icon" format="reference" />
<attr name="title" format="reference" />
<attr name="description" format="reference" />
</declare-styleable>
<!-- For Face enroll accessibility toggle --> <!-- For Face enroll accessibility toggle -->
<declare-styleable name="FaceEnrollAccessibilityToggle"> <declare-styleable name="FaceEnrollAccessibilityToggle">
<attr name="messageText" format="reference" /> <attr name="messageText" format="reference" />

View File

@@ -711,6 +711,26 @@
<!-- Summary for Security settings when fingerprint is not supported [CHAR LIMIT=NONE]--> <!-- Summary for Security settings when fingerprint is not supported [CHAR LIMIT=NONE]-->
<string name="security_dashboard_summary_no_fingerprint">Screen lock</string> <string name="security_dashboard_summary_no_fingerprint">Screen lock</string>
<!-- Biometric enrollment for devices with multiple sensors --><skip />
<!-- Introduction title shown in biometric enrollment screen for devices that have both fingerprint and face. [CHAR LIMIT=60] -->
<string name="multi_biometric_enroll_title">Unlock your phone securely with your face and fingerprint</string>
<!-- Introduction subtitle shown in the biometric enrollment screen. [CHAR LIMIT=60] -->
<string name="multi_biometric_enroll_subtitle">You can also sign in to apps and confirm payment</string>
<!-- Section description preceding the biometric sensors available for the user to select for enrollment. [CHAR LIMIT=40] -->
<string name="multi_biometric_enroll_setup_with">Set up unlock with:</string>
<!-- Title for a preference containing a checkbox which if selected will allow the user to proceed to set up face unlock. [CHAR LIMIT=32] -->
<string name="multi_biometric_enroll_face_unlock_title">Face unlock</string>
<!-- Description for a preference containing a checkbox which if selected will allow the user to set up face unlock. [CHAR LIMIT=40] -->
<string name="multi_biometric_enroll_face_unlock_description">Make unlocking your phone a breeze</string>
<!-- Title for a preference containing a checkbox which if selected will allow the user to proceed to set up fingerprint unlock. [CHAR LIMIT=32] -->
<string name="multi_biometric_enroll_fingerprint_unlock_title">Fingerprint</string>
<!-- Description for a preference containing a checkbox which if selected will allow the user to set up fingerprint unlock. [CHAR LIMIT=40] -->
<string name="multi_biometric_enroll_fingerprint_unlock_description">Save time unlocking phone in your pocket</string>
<!-- Button for skipping biometric enrollment. [CHAR LIMIT=30] -->
<string name="multi_biometric_enroll_skip">Skip</string>
<!-- Button for starting biometric enrollment. [CHAR LIMIT=30] -->
<string name="multi_biometric_enroll_next">Next</string>
<!-- Face enrollment and settings --><skip /> <!-- Face enrollment and settings --><skip />
<!-- Message shown in summary field when face unlock is set up. [CHAR LIMIT=40] --> <!-- Message shown in summary field when face unlock is set up. [CHAR LIMIT=40] -->
<string name="security_settings_face_preference_summary">Face added</string> <string name="security_settings_face_preference_summary">Face added</string>
@@ -865,6 +885,8 @@
<string name="security_settings_fingerprint_enroll_introduction_message_setup">Use your fingerprint to unlock your phone or approve purchases.\n\nNote: Your fingerprint may be less secure than a strong pattern or PIN.</string> <string name="security_settings_fingerprint_enroll_introduction_message_setup">Use your fingerprint to unlock your phone or approve purchases.\n\nNote: Your fingerprint may be less secure than a strong pattern or PIN.</string>
<!-- Button text to cancel enrollment from the introduction [CHAR LIMIT=22] --> <!-- Button text to cancel enrollment from the introduction [CHAR LIMIT=22] -->
<string name="security_settings_fingerprint_enroll_introduction_cancel">Cancel</string> <string name="security_settings_fingerprint_enroll_introduction_cancel">Cancel</string>
<!-- Button text to cancel enrollment [CHAR LIMIT=30] -->
<string name="security_settings_fingerprint_enroll_introduction_no_thanks">No thanks</string>
<!-- Button text to continue to the next screen from the introduction [CHAR LIMIT=22] --> <!-- Button text to continue to the next screen from the introduction [CHAR LIMIT=22] -->
<string name="security_settings_fingerprint_enroll_introduction_continue">Continue</string> <string name="security_settings_fingerprint_enroll_introduction_continue">Continue</string>
<!-- Button text to cancel enrollment from the introduction (this string variant is used while in setup wizard) [CHAR LIMIT=22] --> <!-- Button text to cancel enrollment from the introduction (this string variant is used while in setup wizard) [CHAR LIMIT=22] -->
@@ -939,6 +961,12 @@
<string name="face_lock_screen_setup_skip_dialog_text" product="device">By protecting your device with a screen lock option, no one will be able to use it if it is lost or stolen. You also need a screen lock option to set up face unlock. To go back, tap Cancel. </string> <string name="face_lock_screen_setup_skip_dialog_text" product="device">By protecting your device with a screen lock option, no one will be able to use it if it is lost or stolen. You also need a screen lock option to set up face unlock. To go back, tap Cancel. </string>
<!-- Dialog text shown when the user tries to skip setting up a screen lock, warning that they can't continue to set face unlock. (default) [CHAR LIMIT=NONE] --> <!-- Dialog text shown when the user tries to skip setting up a screen lock, warning that they can't continue to set face unlock. (default) [CHAR LIMIT=NONE] -->
<string name="face_lock_screen_setup_skip_dialog_text" product="default">By protecting your phone with a screen lock option, no one will be able to use it if it is lost or stolen. You also need a screen lock option to set up face unlock. To go back, tap Cancel.</string> <string name="face_lock_screen_setup_skip_dialog_text" product="default">By protecting your phone with a screen lock option, no one will be able to use it if it is lost or stolen. You also need a screen lock option to set up face unlock. To go back, tap Cancel.</string>
<!-- Dialog text shown when the user tries to skip setting up a screen lock, warning that they can't continue to set biometric unlock. (tablet) [CHAR LIMIT=NONE] -->
<string name="biometrics_lock_screen_setup_skip_dialog_text" product="tablet">By protecting your tablet with a screen lock option, no one will be able to use it if it is lost or stolen. You also need a screen lock option to set up biometrics. To go back, tap Cancel. </string>
<!-- Dialog text shown when the user tries to skip setting up a screen lock, warning that they can't continue to set biometric unlock. (device) [CHAR LIMIT=NONE] -->
<string name="biometrics_lock_screen_setup_skip_dialog_text" product="device">By protecting your device with a screen lock option, no one will be able to use it if it is lost or stolen. You also need a screen lock option to set up biometrics. To go back, tap Cancel. </string>
<!-- Dialog text shown when the user tries to skip setting up a screen lock, warning that they can't continue to set biometric unlock. (default) [CHAR LIMIT=NONE] -->
<string name="biometrics_lock_screen_setup_skip_dialog_text" product="default">By protecting your phone with a screen lock option, no one will be able to use it if it is lost or stolen. You also need a screen lock option to set up biometrics. To go back, tap Cancel.</string>
<!-- Title of dialog shown when the user tries to skip setting up a PIN, warning them of potential consequences of not doing so [CHAR LIMIT=48]--> <!-- Title of dialog shown when the user tries to skip setting up a PIN, warning them of potential consequences of not doing so [CHAR LIMIT=48]-->
<string name="lock_screen_pin_skip_title">Skip PIN setup?</string> <string name="lock_screen_pin_skip_title">Skip PIN setup?</string>
<!-- Title of dialog shown when the user tries to skip setting up a password, warning them of potential consequences of not doing so [CHAR LIMIT=48]--> <!-- Title of dialog shown when the user tries to skip setting up a password, warning them of potential consequences of not doing so [CHAR LIMIT=48]-->
@@ -1179,11 +1207,8 @@
<!-- Description text for screen in setup wizard asking user to set up screen lock, explaining to the user how setting up a screen lock protect them from losing data. (phone) [CHAR LIMIT=NONE] --> <!-- Description text for screen in setup wizard asking user to set up screen lock, explaining to the user how setting up a screen lock protect them from losing data. (phone) [CHAR LIMIT=NONE] -->
<string name="setup_lock_settings_picker_message" product="default">Prevent others from using this phone without your permission by activating device protection features. Choose the screen lock you want to use.</string> <string name="setup_lock_settings_picker_message" product="default">Prevent others from using this phone without your permission by activating device protection features. Choose the screen lock you want to use.</string>
<!-- Message shown in screen lock picker while setting up the backup/fallback screen lock method for fingerprint. Users can choose to use this method to unlock the screen instead of fingerprint, or when fingerprint is not accepted. [CHAR LIMIT=80] [BACKUP_MESSAGE_ID=2799884038398627882] --> <!-- Message shown in screen lock picker while setting up the backup/fallback screen lock method for biometrics. Users can choose to use this method to unlock the screen instead of biometrics, or when biometrics are not accepted. [CHAR LIMIT=80] [BACKUP_MESSAGE_ID=2799884038398627882] -->
<string name="lock_settings_picker_fingerprint_message">Choose your backup screen lock method</string> <string name="lock_settings_picker_biometric_message">Choose your backup screen lock method</string>
<!-- Message shown in screen lock picker while setting up the backup/fallback screen lock method for face unlock. Users can choose to use this method to unlock the screen instead of face unlock, or when face unlock is not accepted. [CHAR LIMIT=80] -->
<string name="lock_settings_picker_face_message">Choose your backup screen lock method</string>
<!-- Label for button in screen lock settings, allowing users to choose other types of screen locks. [CHAR LIMIT=40] --> <!-- Label for button in screen lock settings, allowing users to choose other types of screen locks. [CHAR LIMIT=40] -->
<string name="setup_lock_settings_options_button_label">Screen lock options</string> <string name="setup_lock_settings_options_button_label">Screen lock options</string>
@@ -1247,34 +1272,37 @@
<!-- Title for preference that guides the user through creating a backup unlock pattern for fingerprint [CHAR LIMIT=45]--> <!-- Title for preference that guides the user through creating a backup unlock pattern for fingerprint [CHAR LIMIT=45]-->
<string name="fingerprint_unlock_set_unlock_pattern">Fingerprint + Pattern</string> <string name="fingerprint_unlock_set_unlock_pattern">Fingerprint + Pattern</string>
<!-- Title for preference that guides the user through creating a backup unlock PIN for fingerprint [CHAR LIMIT=45]--> <!-- Title for preference that guides the user through creating a backup unlock PIN for fingerprint [CHAR LIMIT=45]-->
<string name="fingerprint_unlock_set_unlock_pin">Fingerprint + PIN</string> <string name="fingerprint_unlock_set_unlock_pin">Fingerprint + PIN</string>
<!-- Title for preference that guides the user through creating a backup unlock password for fingerprint [CHAR LIMIT=45]--> <!-- Title for preference that guides the user through creating a backup unlock password for fingerprint [CHAR LIMIT=45]-->
<string name="fingerprint_unlock_set_unlock_password">Fingerprint + Password</string> <string name="fingerprint_unlock_set_unlock_password">Fingerprint + Password</string>
<!-- Title for preference that guides the user to skip fingerprint setup [CHAR LIMIT=60]--> <!-- Title for preference that guides the user to skip fingerprint setup [CHAR LIMIT=60]-->
<string name="fingerprint_unlock_skip_fingerprint">Continue without fingerprint</string> <string name="fingerprint_unlock_skip_fingerprint">Continue without fingerprint</string>
<!-- Message shown in screen lock picker while setting up the new screen lock with fingerprint option. [CHAR LIMIT=NONE]--> <!-- Message shown in screen lock picker while setting up the new screen lock with fingerprint option. [CHAR LIMIT=NONE]-->
<string name="fingerprint_unlock_title">You can unlock your phone using your fingerprint. For security, this option requires a backup screen lock.</string> <string name="fingerprint_unlock_title">You can unlock your phone using your fingerprint. For security, this option requires a backup screen lock.</string>
<!-- Title for preference that guides the user through creating a backup unlock pattern for face unlock [CHAR LIMIT=45]--> <!-- Title for preference that guides the user through creating a backup unlock pattern for face unlock [CHAR LIMIT=45]-->
<string name="face_unlock_set_unlock_pattern">Face unlock + Pattern</string> <string name="face_unlock_set_unlock_pattern">Face unlock + Pattern</string>
<!-- Title for preference that guides the user through creating a backup unlock PIN for face unlock [CHAR LIMIT=45]--> <!-- Title for preference that guides the user through creating a backup unlock PIN for face unlock [CHAR LIMIT=45]-->
<string name="face_unlock_set_unlock_pin">Face unlock + PIN</string> <string name="face_unlock_set_unlock_pin">Face unlock + PIN</string>
<!-- Title for preference that guides the user through creating a backup unlock password for face unlock [CHAR LIMIT=45]--> <!-- Title for preference that guides the user through creating a backup unlock password for face unlock [CHAR LIMIT=45]-->
<string name="face_unlock_set_unlock_password">Face unlock + Password</string> <string name="face_unlock_set_unlock_password">Face unlock + Password</string>
<!-- Title for preference that guides the user to skip face unlock setup [CHAR LIMIT=60]--> <!-- Title for preference that guides the user to skip face unlock setup [CHAR LIMIT=60]-->
<string name="face_unlock_skip_face">Continue without face unlock</string> <string name="face_unlock_skip_face">Continue without face unlock</string>
<!-- Message shown in screen lock picker while setting up the new screen lock with face unlock option. [CHAR LIMIT=NONE] --> <!-- Message shown in screen lock picker while setting up the new screen lock with face unlock option. [CHAR LIMIT=NONE] -->
<string name="face_unlock_title">You can unlock your phone using your face. For security, this option requires a backup screen lock.</string> <string name="face_unlock_title">You can unlock your phone using your face. For security, this option requires a backup screen lock.</string>
<!-- Title for preference that guides the user through creating a backup unlock pattern for biometrics unlock [CHAR LIMIT=45]-->
<string name="biometrics_unlock_set_unlock_pattern">Biometrics + Pattern</string>
<!-- Title for preference that guides the user through creating a backup unlock PIN for biometrics unlock [CHAR LIMIT=45]-->
<string name="biometrics_unlock_set_unlock_pin">Biometrics + PIN</string>
<!-- Title for preference that guides the user through creating a backup unlock password for biometrics unlock [CHAR LIMIT=45]-->
<string name="biometrics_unlock_set_unlock_password">Biometrics + Password</string>
<!-- Title for preference that guides the user to skip face unlock setup [CHAR LIMIT=60]-->
<string name="biometrics_unlock_skip_biometrics">Continue without biometrics</string>
<!-- Message shown in screen lock picker while setting up the new screen lock with biometrics option. [CHAR LIMIT=NONE] -->
<string name="biometrics_unlock_title">You can unlock your phone using your biometrics. For security, this option requires a backup screen lock.</string>
<!-- Summary for preference that has been disabled by because of the DevicePolicyAdmin, or because device encryption is enabled, or because there are credentials in the credential storage [CHAR LIMIT=50] --> <!-- Summary for preference that has been disabled by because of the DevicePolicyAdmin, or because device encryption is enabled, or because there are credentials in the credential storage [CHAR LIMIT=50] -->
<string name="unlock_set_unlock_disabled_summary">Disabled by admin, encryption policy, or <string name="unlock_set_unlock_disabled_summary">Disabled by admin, encryption policy, or
credential storage</string> credential storage</string>
@@ -4110,6 +4138,15 @@
<string name="lockpassword_choose_your_pattern_header_for_face">To use face unlock, set pattern</string> <string name="lockpassword_choose_your_pattern_header_for_face">To use face unlock, set pattern</string>
<!-- Header on first screen of choose password/PIN as backup for face unlock flow. If this string cannot be translated in under 40 characters, please translate "Set face unlock backup" [CHAR LIMIT=40] --> <!-- Header on first screen of choose password/PIN as backup for face unlock flow. If this string cannot be translated in under 40 characters, please translate "Set face unlock backup" [CHAR LIMIT=40] -->
<string name="lockpassword_choose_your_pin_header_for_face">To use face unlock, set PIN</string> <string name="lockpassword_choose_your_pin_header_for_face">To use face unlock, set PIN</string>
<!-- Header on first screen of choose password/PIN as backup for biometric unlock flow. If this string cannot be translated in under 40 characters, please translate "Set biometric unlock backup" [CHAR LIMIT=40] -->
<string name="lockpassword_choose_your_password_header_for_biometrics">To use biometrics, set password</string>
<!-- Header on first screen of choose pattern as backup for biometric unlock flow. If this string cannot be translated in under 40 characters, please translate "Set biometric unlock backup" [CHAR LIMIT=40] -->
<string name="lockpassword_choose_your_pattern_header_for_biometrics">To use biometrics, set pattern</string>
<!-- Header on first screen of choose password/PIN as backup for biometric unlock flow. If this string cannot be translated in under 40 characters, please translate "Set biometric unlock backup" [CHAR LIMIT=40] -->
<string name="lockpassword_choose_your_pin_header_for_biometrics">To use biometrics, set PIN</string>
<!-- Text for button that the user should tap when they forgot their work profile password [CHAR LIMIT=40] --> <!-- Text for button that the user should tap when they forgot their work profile password [CHAR LIMIT=40] -->
<string name="lockpassword_forgot_password">Forgot your password?</string> <string name="lockpassword_forgot_password">Forgot your password?</string>
<!-- Text for button that the user should tap when they forgot their work profile pattern [CHAR LIMIT=40] --> <!-- Text for button that the user should tap when they forgot their work profile pattern [CHAR LIMIT=40] -->
@@ -9400,6 +9437,14 @@
<!-- Message shown on encryption interstitial to ask the user whether or not they want to use a password to encrypt the device while setting up face unlock. [CHAR LIMIT=NONE] --> <!-- Message shown on encryption interstitial to ask the user whether or not they want to use a password to encrypt the device while setting up face unlock. [CHAR LIMIT=NONE] -->
<string name="encryption_interstitial_message_password_for_face">In addition to using your face to unlock your device, you can further protect this device by requiring your password before it starts up. Until the device starts up, it can\u2019t receive calls, messages, or notifications, including alarms.\n\nThis helps protect data on lost or stolen devices. Require password to start your device?</string> <string name="encryption_interstitial_message_password_for_face">In addition to using your face to unlock your device, you can further protect this device by requiring your password before it starts up. Until the device starts up, it can\u2019t receive calls, messages, or notifications, including alarms.\n\nThis helps protect data on lost or stolen devices. Require password to start your device?</string>
<!-- Message shown on encryption interstitial to ask the user whether or not they want to use a PIN to encrypt the device while setting up biometric unlock. [CHAR LIMIT=NONE] -->
<string name="encryption_interstitial_message_pin_for_biometrics">In addition to using your biometrics to unlock your device, you can further protect this device by requiring your PIN before it starts up. Until the device starts up, it can\u2019t receive calls, messages, or notifications, including alarms.\n\nThis helps protect data on lost or stolen devices. Require PIN to start your device?</string>
<!-- Message shown on encryption interstitial to ask the user whether or not they want to use a pattern to encrypt the device while setting up biometric unlock. [CHAR LIMIT=NONE] -->
<string name="encryption_interstitial_message_pattern_for_biometrics">In addition to using your biometrics to unlock your device, you can further protect this device by requiring your pattern before it starts up. Until the device starts up, it can\u2019t receive calls, messages, or notifications, including alarms.\n\nThis helps protect data on lost or stolen devices. Require pattern to start your device?</string>
<!-- Message shown on encryption interstitial to ask the user whether or not they want to use a password to encrypt the device while setting up biometric unlock. [CHAR LIMIT=NONE] -->
<string name="encryption_interstitial_message_password_for_biometrics">In addition to using your biometrics to unlock your device, you can further protect this device by requiring your password before it starts up. Until the device starts up, it can\u2019t receive calls, messages, or notifications, including alarms.\n\nThis helps protect data on lost or stolen devices. Require password to start your device?</string>
<!-- Button label to say yes to the question of whether to require PIN/password/pattern to start your device. [CHAR LIMIT=20] --> <!-- Button label to say yes to the question of whether to require PIN/password/pattern to start your device. [CHAR LIMIT=20] -->
<string name="encryption_interstitial_yes">Yes</string> <string name="encryption_interstitial_yes">Yes</string>
<!-- Button label to say no to the question of whether to require PIN/password/pattern to start your device. [CHAR LIMIT=20] --> <!-- Button label to say no to the question of whether to require PIN/password/pattern to start your device. [CHAR LIMIT=20] -->

View File

@@ -396,13 +396,7 @@
<item name="android:gravity">center_vertical</item> <item name="android:gravity">center_vertical</item>
</style> </style>
<style name="FingerprintHeaderStyle" parent="@*android:style/TextAppearance.DeviceDefault.Subhead"> <style name="BiometricHeaderStyle" parent="@*android:style/TextAppearance.DeviceDefault.Subhead">
<item name="android:paddingTop">16dp</item>
<item name="android:textColor">@color/primary_dark_material_light</item>
<item name="android:lineSpacingMultiplier">1.2</item>
</style>
<style name="FaceHeaderStyle" parent="@*android:style/TextAppearance.DeviceDefault.Subhead">
<item name="android:paddingTop">16dp</item> <item name="android:paddingTop">16dp</item>
<item name="android:textColor">@color/primary_dark_material_light</item> <item name="android:textColor">@color/primary_dark_material_light</item>
<item name="android:lineSpacingMultiplier">1.2</item> <item name="android:lineSpacingMultiplier">1.2</item>

View File

@@ -58,6 +58,11 @@
android:title="@string/face_unlock_skip_face" android:title="@string/face_unlock_skip_face"
android:persistent="false"/> android:persistent="false"/>
<com.android.settingslib.RestrictedPreference
android:key="unlock_skip_biometrics"
android:title="@string/biometrics_unlock_skip_biometrics"
android:persistent="false"/>
<com.android.settingslib.widget.FooterPreference <com.android.settingslib.widget.FooterPreference
android:key="lock_settings_footer" android:key="lock_settings_footer"
android:selectable="false" android:selectable="false"

View File

@@ -111,6 +111,8 @@ public class EncryptionInterstitial extends SettingsActivity {
ChooseLockSettingsHelper.EXTRA_KEY_FOR_FINGERPRINT, false); ChooseLockSettingsHelper.EXTRA_KEY_FOR_FINGERPRINT, false);
final boolean forFace = getActivity().getIntent() final boolean forFace = getActivity().getIntent()
.getBooleanExtra(ChooseLockSettingsHelper.EXTRA_KEY_FOR_FACE, false); .getBooleanExtra(ChooseLockSettingsHelper.EXTRA_KEY_FOR_FACE, false);
final boolean forBiometrics = getActivity().getIntent()
.getBooleanExtra(ChooseLockSettingsHelper.EXTRA_KEY_FOR_BIOMETRICS, false);
Intent intent = getActivity().getIntent(); Intent intent = getActivity().getIntent();
mRequestedPasswordQuality = intent.getIntExtra(EXTRA_PASSWORD_QUALITY, 0); mRequestedPasswordQuality = intent.getIntExtra(EXTRA_PASSWORD_QUALITY, 0);
mUnlockMethodIntent = intent.getParcelableExtra(EXTRA_UNLOCK_METHOD_INTENT); mUnlockMethodIntent = intent.getParcelableExtra(EXTRA_UNLOCK_METHOD_INTENT);
@@ -121,6 +123,8 @@ public class EncryptionInterstitial extends SettingsActivity {
R.string.encryption_interstitial_message_pattern_for_fingerprint : R.string.encryption_interstitial_message_pattern_for_fingerprint :
forFace ? forFace ?
R.string.encryption_interstitial_message_pattern_for_face : R.string.encryption_interstitial_message_pattern_for_face :
forBiometrics ?
R.string.encryption_interstitial_message_pattern_for_biometrics :
R.string.encryption_interstitial_message_pattern; R.string.encryption_interstitial_message_pattern;
break; break;
case DevicePolicyManager.PASSWORD_QUALITY_NUMERIC: case DevicePolicyManager.PASSWORD_QUALITY_NUMERIC:
@@ -129,6 +133,8 @@ public class EncryptionInterstitial extends SettingsActivity {
R.string.encryption_interstitial_message_pin_for_fingerprint : R.string.encryption_interstitial_message_pin_for_fingerprint :
forFace ? forFace ?
R.string.encryption_interstitial_message_pin_for_face : R.string.encryption_interstitial_message_pin_for_face :
forBiometrics ?
R.string.encryption_interstitial_message_pin_for_biometrics :
R.string.encryption_interstitial_message_pin; R.string.encryption_interstitial_message_pin;
break; break;
default: default:
@@ -136,6 +142,8 @@ public class EncryptionInterstitial extends SettingsActivity {
R.string.encryption_interstitial_message_password_for_fingerprint : R.string.encryption_interstitial_message_password_for_fingerprint :
forFace ? forFace ?
R.string.encryption_interstitial_message_password_for_face : R.string.encryption_interstitial_message_password_for_face :
forBiometrics ?
R.string.encryption_interstitial_message_password_for_biometrics :
R.string.encryption_interstitial_message_password; R.string.encryption_interstitial_message_password;
break; break;
} }

View File

@@ -21,23 +21,36 @@ import android.app.admin.DevicePolicyManager;
import android.app.settings.SettingsEnums; import android.app.settings.SettingsEnums;
import android.content.Intent; import android.content.Intent;
import android.content.pm.PackageManager; import android.content.pm.PackageManager;
import android.content.res.Resources;
import android.hardware.biometrics.BiometricManager; import android.hardware.biometrics.BiometricManager;
import android.hardware.biometrics.BiometricManager.Authenticators; import android.hardware.biometrics.BiometricManager.Authenticators;
import android.hardware.face.FaceManager;
import android.hardware.face.FaceSensorProperties;
import android.hardware.fingerprint.FingerprintManager;
import android.hardware.fingerprint.FingerprintSensorProperties;
import android.os.Bundle; import android.os.Bundle;
import android.os.UserHandle; import android.os.UserHandle;
import android.os.UserManager;
import android.provider.Settings; import android.provider.Settings;
import android.util.Log; import android.util.Log;
import android.view.View;
import androidx.annotation.Nullable;
import com.android.internal.widget.LockPatternUtils;
import com.android.settings.R;
import com.android.settings.SetupWizardUtils; import com.android.settings.SetupWizardUtils;
import com.android.settings.biometrics.face.FaceEnrollIntroduction;
import com.android.settings.biometrics.fingerprint.FingerprintEnrollFindSensor;
import com.android.settings.biometrics.fingerprint.FingerprintEnrollIntroduction;
import com.android.settings.biometrics.fingerprint.SetupFingerprintEnrollIntroduction;
import com.android.settings.core.InstrumentedActivity; import com.android.settings.core.InstrumentedActivity;
import com.android.settings.password.ChooseLockGeneric; import com.android.settings.password.ChooseLockGeneric;
import com.android.settings.password.ChooseLockPattern;
import com.android.settings.password.ChooseLockSettingsHelper; import com.android.settings.password.ChooseLockSettingsHelper;
import com.google.android.setupcompat.template.FooterBarMixin;
import com.google.android.setupcompat.template.FooterButton;
import com.google.android.setupcompat.util.WizardManagerHelper; import com.google.android.setupcompat.util.WizardManagerHelper;
import com.google.android.setupdesign.GlifLayout;
import java.util.List;
/** /**
* Trampoline activity launched by the {@code android.settings.BIOMETRIC_ENROLL} action which * Trampoline activity launched by the {@code android.settings.BIOMETRIC_ENROLL} action which
@@ -49,18 +62,53 @@ public class BiometricEnrollActivity extends InstrumentedActivity {
private static final String TAG = "BiometricEnrollActivity"; private static final String TAG = "BiometricEnrollActivity";
private static final int REQUEST_CHOOSE_LOCK = 1;
private static final int REQUEST_CONFIRM_LOCK = 2;
public static final int RESULT_SKIP = BiometricEnrollBase.RESULT_SKIP;
// Intent extra. If true, biometric enrollment should skip introductory screens. Currently // Intent extra. If true, biometric enrollment should skip introductory screens. Currently
// this only applies to fingerprint. // this only applies to fingerprint.
public static final String EXTRA_SKIP_INTRO = "skip_intro"; public static final String EXTRA_SKIP_INTRO = "skip_intro";
private static final String SAVED_STATE_CONFIRMING_CREDENTIALS = "confirming_credentials";
private static final String SAVED_STATE_GK_PW_HANDLE = "gk_pw_handle";
public static final class InternalActivity extends BiometricEnrollActivity {} public static final class InternalActivity extends BiometricEnrollActivity {}
private int mUserId = UserHandle.myUserId();
private boolean mConfirmingCredentials;
@Nullable private Long mGkPwHandle;
private BiometricEnrollCheckbox mCheckboxFace;
private BiometricEnrollCheckbox mCheckboxFingerprint;
@Nullable private MultiBiometricEnrollHelper mMultiBiometricEnrollHelper;
@Override @Override
public void onCreate(Bundle savedInstanceState) { public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
if (this instanceof InternalActivity) {
mUserId = getIntent().getIntExtra(Intent.EXTRA_USER_ID, UserHandle.myUserId());
}
if (savedInstanceState != null) {
mConfirmingCredentials = savedInstanceState
.getBoolean(SAVED_STATE_CONFIRMING_CREDENTIALS, false);
if (savedInstanceState.containsKey(SAVED_STATE_GK_PW_HANDLE)) {
mGkPwHandle = savedInstanceState.getLong(SAVED_STATE_GK_PW_HANDLE);
}
}
// Put the theme in the intent so it gets propagated to other activities in the flow
final Intent intent = getIntent();
if (intent.getStringExtra(WizardManagerHelper.EXTRA_THEME) == null) {
intent.putExtra(
WizardManagerHelper.EXTRA_THEME,
SetupWizardUtils.getThemeString(intent));
}
// Default behavior is to enroll BIOMETRIC_WEAK or above. See ACTION_BIOMETRIC_ENROLL. // Default behavior is to enroll BIOMETRIC_WEAK or above. See ACTION_BIOMETRIC_ENROLL.
final int authenticators = getIntent().getIntExtra( final int authenticators = intent.getIntExtra(
Settings.EXTRA_BIOMETRIC_AUTHENTICATORS_ALLOWED, Authenticators.BIOMETRIC_WEAK); Settings.EXTRA_BIOMETRIC_AUTHENTICATORS_ALLOWED, Authenticators.BIOMETRIC_WEAK);
Log.d(TAG, "Authenticators: " + authenticators); Log.d(TAG, "Authenticators: " + authenticators);
@@ -73,9 +121,7 @@ public class BiometricEnrollActivity extends InstrumentedActivity {
if (isSetupWizard) { if (isSetupWizard) {
if (hasFeatureFace && hasFeatureFingerprint) { if (hasFeatureFace && hasFeatureFingerprint) {
// TODO(b/162341940, b/152242790) this should show a multi-biometric selection setupForMultiBiometricEnroll();
// screen
launchFingerprintOnlyEnroll();
} else if (hasFeatureFace) { } else if (hasFeatureFace) {
launchFaceOnlyEnroll(); launchFaceOnlyEnroll();
} else if (hasFeatureFingerprint) { } else if (hasFeatureFingerprint) {
@@ -98,9 +144,7 @@ public class BiometricEnrollActivity extends InstrumentedActivity {
if (authenticators == BiometricManager.Authenticators.DEVICE_CREDENTIAL) { if (authenticators == BiometricManager.Authenticators.DEVICE_CREDENTIAL) {
launchCredentialOnlyEnroll(); launchCredentialOnlyEnroll();
} else if (hasFeatureFace && hasFeatureFingerprint) { } else if (hasFeatureFace && hasFeatureFingerprint) {
// TODO(b/162341940, b/152242790) this should show a multi-biometric selection setupForMultiBiometricEnroll();
// screen
launchFingerprintOnlyEnroll();
} else if (hasFeatureFingerprint) { } else if (hasFeatureFingerprint) {
launchFingerprintOnlyEnroll(); launchFingerprintOnlyEnroll();
} else if (hasFeatureFace) { } else if (hasFeatureFace) {
@@ -112,30 +156,216 @@ public class BiometricEnrollActivity extends InstrumentedActivity {
} }
} }
@Override
protected void onSaveInstanceState(@NonNull Bundle outState) {
super.onSaveInstanceState(outState);
outState.putBoolean(SAVED_STATE_CONFIRMING_CREDENTIALS, mConfirmingCredentials);
if (mGkPwHandle != null) {
outState.putLong(SAVED_STATE_GK_PW_HANDLE, mGkPwHandle);
}
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (mMultiBiometricEnrollHelper == null) {
overridePendingTransition(R.anim.sud_slide_next_in, R.anim.sud_slide_next_out);
switch (requestCode) {
case REQUEST_CHOOSE_LOCK:
mConfirmingCredentials = false;
if (resultCode == ChooseLockPattern.RESULT_FINISHED) {
mGkPwHandle = BiometricUtils.getGatekeeperPasswordHandle(data);
} else {
Log.d(TAG, "Unknown result for chooseLock: " + resultCode);
setResult(resultCode);
finish();
}
break;
case REQUEST_CONFIRM_LOCK:
mConfirmingCredentials = false;
if (resultCode == RESULT_OK) {
mGkPwHandle = BiometricUtils.getGatekeeperPasswordHandle(data);
} else {
Log.d(TAG, "Unknown result for confirmLock: " + resultCode);
finish();
}
break;
default:
Log.d(TAG, "Unknown requestCode: " + requestCode + ", finishing");
finish();
}
} else {
mMultiBiometricEnrollHelper.onActivityResult(requestCode, resultCode, data);
}
}
@Override
protected void onApplyThemeResource(Resources.Theme theme, int resid, boolean first) {
resid = SetupWizardUtils.getTheme(getIntent());
theme.applyStyle(R.style.SetupWizardPartnerResource, true);
super.onApplyThemeResource(theme, resid, first);
}
@Override
protected void onStop() {
super.onStop();
if (mConfirmingCredentials || mMultiBiometricEnrollHelper != null) {
return;
}
if (!isChangingConfigurations()) {
Log.d(TAG, "Finishing in onStop");
finish();
}
}
private void setupForMultiBiometricEnroll() {
setContentView(R.layout.biometric_enroll_layout);
mCheckboxFace = findViewById(R.id.checkbox_face);
mCheckboxFingerprint = findViewById(R.id.checkbox_fingerprint);
mCheckboxFace.setListener(this::updateNextButton);
mCheckboxFingerprint.setListener(this::updateNextButton);
final FingerprintManager fingerprintManager = getSystemService(FingerprintManager.class);
final FaceManager faceManager = getSystemService(FaceManager.class);
final List<FingerprintSensorProperties> fpProperties =
fingerprintManager.getSensorProperties();
final List<FaceSensorProperties> faceProperties = faceManager.getSensorProperties();
// This would need to be updated for devices with multiple sensors of the same modality
final boolean maxFacesEnrolled = faceManager.getEnrolledFaces(mUserId).size()
>= faceProperties.get(0).maxTemplatesAllowed;
final boolean maxFingerprintsEnrolled = fingerprintManager.getEnrolledFingerprints(mUserId)
.size() >= fpProperties.get(0).maxTemplatesAllowed;
if (maxFacesEnrolled) {
mCheckboxFace.setEnabled(false);
mCheckboxFace.setDescription(R.string.face_intro_error_max);
}
if (maxFingerprintsEnrolled) {
mCheckboxFingerprint.setEnabled(false);
mCheckboxFingerprint.setDescription(R.string.fingerprint_intro_error_max);
}
final FooterBarMixin footerBarMixin = ((GlifLayout) findViewById(R.id.setup_wizard_layout))
.getMixin(FooterBarMixin.class);
footerBarMixin.setSecondaryButton(new FooterButton.Builder(this)
.setText(R.string.multi_biometric_enroll_skip)
.setListener(this::onButtonNegative)
.setButtonType(FooterButton.ButtonType.SKIP)
.setTheme(R.style.SudGlifButton_Secondary)
.build());
footerBarMixin.setPrimaryButton(new FooterButton.Builder(this)
.setText(R.string.multi_biometric_enroll_next)
.setListener(this::onButtonPositive)
.setButtonType(FooterButton.ButtonType.NEXT)
.setTheme(R.style.SudGlifButton_Primary)
.build());
footerBarMixin.getSecondaryButton().setVisibility(View.VISIBLE);
footerBarMixin.getPrimaryButton().setVisibility(View.VISIBLE);
if (!mConfirmingCredentials && mGkPwHandle == null) {
mConfirmingCredentials = true;
if (!userHasPassword(mUserId)) {
launchChooseLock();
} else {
launchConfirmLock();
}
}
}
private void updateNextButton(View view) {
final boolean canEnrollAny = canEnrollFace() || canEnrollFingerprint();
final FooterBarMixin footerBarMixin = ((GlifLayout) findViewById(R.id.setup_wizard_layout))
.getMixin(FooterBarMixin.class);
footerBarMixin.getPrimaryButton().setEnabled(canEnrollAny);
}
private void onButtonPositive(View view) {
// Start the state machine according to checkboxes, taking max enrolled into account
mMultiBiometricEnrollHelper = new MultiBiometricEnrollHelper(this, mUserId,
canEnrollFace(), canEnrollFingerprint(), mGkPwHandle);
mMultiBiometricEnrollHelper.startNextStep();
}
private void onButtonNegative(View view) {
setResult(RESULT_SKIP);
finish();
}
private boolean canEnrollFace() {
return mCheckboxFace.isEnabled() && mCheckboxFace.isChecked();
}
private boolean canEnrollFingerprint() {
return mCheckboxFingerprint.isEnabled() && mCheckboxFingerprint.isChecked();
}
private boolean userHasPassword(int userId) {
final UserManager userManager = getSystemService(UserManager.class);
final int passwordQuality = new LockPatternUtils(this)
.getActivePasswordQuality(userManager.getCredentialOwnerProfile(userId));
return passwordQuality != DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED;
}
private void launchChooseLock() {
Log.d(TAG, "launchChooseLock");
Intent intent = BiometricUtils.getChooseLockIntent(this, getIntent());
intent.putExtra(ChooseLockGeneric.ChooseLockGenericFragment.MINIMUM_QUALITY_KEY,
DevicePolicyManager.PASSWORD_QUALITY_SOMETHING);
intent.putExtra(ChooseLockGeneric.ChooseLockGenericFragment.HIDE_DISABLED_PREFS, true);
intent.putExtra(ChooseLockSettingsHelper.EXTRA_KEY_REQUEST_GK_PW_HANDLE, true);
intent.putExtra(ChooseLockSettingsHelper.EXTRA_KEY_FOR_BIOMETRICS, true);
if (mUserId != UserHandle.USER_NULL) {
intent.putExtra(Intent.EXTRA_USER_ID, mUserId);
}
startActivityForResult(intent, REQUEST_CHOOSE_LOCK);
}
private void launchConfirmLock() {
Log.d(TAG, "launchConfirmLock");
final ChooseLockSettingsHelper.Builder builder = new ChooseLockSettingsHelper.Builder(this);
builder.setRequestCode(REQUEST_CONFIRM_LOCK)
.setRequestGatekeeperPasswordHandle(true)
.setForegroundOnly(true)
.setReturnCredentials(true);
if (mUserId != UserHandle.USER_NULL) {
builder.setUserId(mUserId);
}
final boolean launched = builder.show();
if (!launched) {
// This shouldn't happen, as we should only end up at this step if a lock thingy is
// already set.
finish();
}
}
/** /**
* This should only be used to launch enrollment for single-sensor devices, which use
* FLAG_ACTIVITY_FORWARD_RESULT path.
*
* @param intent Enrollment activity that should be started (e.g. FaceEnrollIntroduction.class, * @param intent Enrollment activity that should be started (e.g. FaceEnrollIntroduction.class,
* etc). * etc).
*/ */
private void launchEnrollActivity(@NonNull Intent intent) { private void launchEnrollActivity(@NonNull Intent intent) {
intent.setFlags(Intent.FLAG_ACTIVITY_FORWARD_RESULT); intent.setFlags(Intent.FLAG_ACTIVITY_FORWARD_RESULT);
byte[] hardwareAuthToken = null;
if (this instanceof InternalActivity) { if (this instanceof InternalActivity) {
// Propagate challenge and user Id from ChooseLockGeneric. hardwareAuthToken = getIntent().getByteArrayExtra(
final byte[] token = getIntent() ChooseLockSettingsHelper.EXTRA_KEY_CHALLENGE_TOKEN);
.getByteArrayExtra(ChooseLockSettingsHelper.EXTRA_KEY_CHALLENGE_TOKEN);
final int userId = getIntent()
.getIntExtra(Intent.EXTRA_USER_ID, UserHandle.USER_NULL);
final long gkPwHandle = getIntent().getLongExtra(
ChooseLockSettingsHelper.EXTRA_KEY_GK_PW_HANDLE, 0L);
intent.putExtra(ChooseLockSettingsHelper.EXTRA_KEY_CHALLENGE_TOKEN, token);
intent.putExtra(Intent.EXTRA_USER_ID, userId);
if (gkPwHandle != 0L) {
intent.putExtra(ChooseLockSettingsHelper.EXTRA_KEY_GK_PW_HANDLE, gkPwHandle);
}
} }
BiometricUtils.launchEnrollForResult(this, intent, 0 /* requestCode */, hardwareAuthToken,
startActivity(intent); null /* gkPwHandle */, mUserId);
finish();
} }
private void launchCredentialOnlyEnroll() { private void launchCredentialOnlyEnroll() {
@@ -152,40 +382,18 @@ public class BiometricEnrollActivity extends InstrumentedActivity {
// ChooseLockGeneric can request to start fingerprint enroll bypassing the intro screen. // ChooseLockGeneric can request to start fingerprint enroll bypassing the intro screen.
if (getIntent().getBooleanExtra(EXTRA_SKIP_INTRO, false) if (getIntent().getBooleanExtra(EXTRA_SKIP_INTRO, false)
&& this instanceof InternalActivity) { && this instanceof InternalActivity) {
intent = getFingerprintFindSensorIntent(); intent = BiometricUtils.getFingerprintFindSensorIntent(this, getIntent());
} else { } else {
intent = getFingerprintIntroIntent(); intent = BiometricUtils.getFingerprintIntroIntent(this, getIntent());
} }
launchEnrollActivity(intent); launchEnrollActivity(intent);
} }
private void launchFaceOnlyEnroll() { private void launchFaceOnlyEnroll() {
final Intent intent = getFaceIntroIntent(); final Intent intent = BiometricUtils.getFaceIntroIntent(this, getIntent());
launchEnrollActivity(intent); launchEnrollActivity(intent);
} }
private Intent getFingerprintFindSensorIntent() {
Intent intent = new Intent(this, FingerprintEnrollFindSensor.class);
SetupWizardUtils.copySetupExtras(getIntent(), intent);
return intent;
}
private Intent getFingerprintIntroIntent() {
if (WizardManagerHelper.isAnySetupWizard(getIntent())) {
Intent intent = new Intent(this, SetupFingerprintEnrollIntroduction.class);
WizardManagerHelper.copyWizardManagerExtras(getIntent(), intent);
return intent;
} else {
return new Intent(this, FingerprintEnrollIntroduction.class);
}
}
private Intent getFaceIntroIntent() {
Intent intent = new Intent(this, FaceEnrollIntroduction.class);
WizardManagerHelper.copyWizardManagerExtras(getIntent(), intent);
return intent;
}
@Override @Override
public int getMetricsCategory() { public int getMetricsCategory() {
return SettingsEnums.BIOMETRIC_ENROLL_ACTIVITY; return SettingsEnums.BIOMETRIC_ENROLL_ACTIVITY;

View File

@@ -57,6 +57,9 @@ public abstract class BiometricEnrollBase extends InstrumentedActivity {
* starting the next activity. However, this leads to broken 'Back' * starting the next activity. However, this leads to broken 'Back'
* behavior. So, now an activity does not finish itself until it gets this * behavior. So, now an activity does not finish itself until it gets this
* result. * result.
*
* This must be the same as
* {@link com.android.settings.password.ChooseLockPattern#RESULT_FINISHED}
*/ */
public static final int RESULT_FINISHED = RESULT_FIRST_USER; public static final int RESULT_FINISHED = RESULT_FIRST_USER;
@@ -78,6 +81,11 @@ public abstract class BiometricEnrollBase extends InstrumentedActivity {
public static final int LEARN_MORE_REQUEST = 3; public static final int LEARN_MORE_REQUEST = 3;
public static final int CONFIRM_REQUEST = 4; public static final int CONFIRM_REQUEST = 4;
public static final int ENROLL_REQUEST = 5; public static final int ENROLL_REQUEST = 5;
/**
* Request code when starting another biometric enrollment from within a biometric flow. For
* example, when starting fingerprint enroll after face enroll.
*/
public static final int ENROLL_NEXT_BIOMETRIC_REQUEST = 6;
protected boolean mLaunchedConfirmLock; protected boolean mLaunchedConfirmLock;
protected byte[] mToken; protected byte[] mToken;

View File

@@ -0,0 +1,115 @@
/*
* Copyright (C) 2020 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;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.CheckBox;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.StringRes;
import com.android.settings.R;
/**
* Widget contain space for an icon, title, description, and checkbox. On devices with multiple
* biometric sensors, allows users to choose sensors during {@link BiometricEnrollActivity}.
*/
public class BiometricEnrollCheckbox extends LinearLayout {
@NonNull private final CheckBox mCheckBox;
@NonNull private final TextView mDescriptionView;
@Nullable private View.OnClickListener mListener;
public BiometricEnrollCheckbox(Context context) {
this(context, null /* attrs */);
}
public BiometricEnrollCheckbox(Context context,
@Nullable AttributeSet attrs) {
this(context, attrs, 0 /* defStyleAttr */);
}
public BiometricEnrollCheckbox(Context context, @Nullable AttributeSet attrs,
int defStyleAttr) {
this(context, attrs, defStyleAttr, 0 /* defStyleRes */);
}
public BiometricEnrollCheckbox(Context context, AttributeSet attrs, int defStyleAttr,
int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
LayoutInflater.from(context).inflate(R.layout.biometric_enroll_checkbox,
this, true /* attachToRoot */);
mCheckBox = findViewById(R.id.checkbox);
final ImageView iconView = findViewById(R.id.icon);
final TextView titleView = findViewById(R.id.title);
mDescriptionView = findViewById(R.id.description);
setOnClickListener(view -> {
if (isEnabled()) {
mCheckBox.toggle();
}
if (mListener != null) {
mListener.onClick(view);
}
});
final TypedArray a = context
.obtainStyledAttributes(attrs, R.styleable.BiometricEnrollCheckbox);
try {
final Drawable icon =
a.getDrawable(R.styleable.BiometricEnrollCheckbox_icon);
final CharSequence title =
a.getText(R.styleable.BiometricEnrollCheckbox_title);
final CharSequence description =
a.getText(R.styleable.BiometricEnrollCheckbox_description);
iconView.setImageDrawable(icon);
titleView.setText(title);
mDescriptionView.setText(description);
} finally {
a.recycle();
}
}
public void setListener(View.OnClickListener listener) {
mListener = listener;
}
@Override
public void setEnabled(boolean enabled) {
super.setEnabled(enabled);
mCheckBox.setEnabled(enabled);
}
public boolean isChecked() {
return mCheckBox.isChecked();
}
public void setDescription(@StringRes int resId) {
mDescriptionView.setText(resId);
}
}

View File

@@ -21,7 +21,7 @@ import android.content.Intent;
import android.os.Bundle; import android.os.Bundle;
import android.os.UserHandle; import android.os.UserHandle;
import android.os.UserManager; import android.os.UserManager;
import android.os.storage.StorageManager; import android.util.Log;
import android.view.View; import android.view.View;
import android.widget.TextView; import android.widget.TextView;
@@ -29,9 +29,7 @@ import com.android.internal.widget.LockPatternUtils;
import com.android.settings.R; import com.android.settings.R;
import com.android.settings.SetupWizardUtils; import com.android.settings.SetupWizardUtils;
import com.android.settings.password.ChooseLockGeneric; import com.android.settings.password.ChooseLockGeneric;
import com.android.settings.password.ChooseLockGeneric.ChooseLockGenericFragment;
import com.android.settings.password.ChooseLockSettingsHelper; import com.android.settings.password.ChooseLockSettingsHelper;
import com.android.settings.password.SetupChooseLockGeneric;
import com.google.android.setupcompat.template.FooterButton; import com.google.android.setupcompat.template.FooterButton;
import com.google.android.setupcompat.util.WizardManagerHelper; import com.google.android.setupcompat.util.WizardManagerHelper;
@@ -43,6 +41,8 @@ import com.google.android.setupdesign.span.LinkSpan;
public abstract class BiometricEnrollIntroduction extends BiometricEnrollBase public abstract class BiometricEnrollIntroduction extends BiometricEnrollBase
implements LinkSpan.OnClickListener { implements LinkSpan.OnClickListener {
private static final String TAG = "BiometricEnrollIntroduction";
private static final String KEY_CONFIRMING_CREDENTIALS = "confirming_credentials"; private static final String KEY_CONFIRMING_CREDENTIALS = "confirming_credentials";
private UserManager mUserManager; private UserManager mUserManager;
@@ -164,7 +164,8 @@ public abstract class BiometricEnrollIntroduction extends BiometricEnrollBase
// No password registered, launch into enrollment wizard. // No password registered, launch into enrollment wizard.
mConfirmingCredentials = true; mConfirmingCredentials = true;
launchChooseLock(); launchChooseLock();
} else if (!BiometricUtils.containsGatekeeperPassword(getIntent()) && mToken == null) { } else if (!BiometricUtils.containsGatekeeperPasswordHandle(getIntent())
&& mToken == null) {
// It's possible to have a token but mLaunchedConfirmLock == false, since // It's possible to have a token but mLaunchedConfirmLock == false, since
// ChooseLockGeneric can pass us a token. // ChooseLockGeneric can pass us a token.
mConfirmingCredentials = true; mConfirmingCredentials = true;
@@ -220,7 +221,7 @@ public abstract class BiometricEnrollIntroduction extends BiometricEnrollBase
} }
private void launchChooseLock() { private void launchChooseLock() {
Intent intent = getChooseLockIntent(); Intent intent = BiometricUtils.getChooseLockIntent(this, getIntent());
intent.putExtra(ChooseLockGeneric.ChooseLockGenericFragment.MINIMUM_QUALITY_KEY, intent.putExtra(ChooseLockGeneric.ChooseLockGenericFragment.MINIMUM_QUALITY_KEY,
DevicePolicyManager.PASSWORD_QUALITY_SOMETHING); DevicePolicyManager.PASSWORD_QUALITY_SOMETHING);
intent.putExtra(ChooseLockGeneric.ChooseLockGenericFragment.HIDE_DISABLED_PREFS, true); intent.putExtra(ChooseLockGeneric.ChooseLockGenericFragment.HIDE_DISABLED_PREFS, true);
@@ -240,27 +241,11 @@ public abstract class BiometricEnrollIntroduction extends BiometricEnrollBase
if (mUserId != UserHandle.USER_NULL) { if (mUserId != UserHandle.USER_NULL) {
intent.putExtra(Intent.EXTRA_USER_ID, mUserId); intent.putExtra(Intent.EXTRA_USER_ID, mUserId);
} }
BiometricUtils.copyMultiBiometricExtras(getIntent(), intent);
intent.putExtra(EXTRA_FROM_SETTINGS_SUMMARY, mFromSettingsSummary); intent.putExtra(EXTRA_FROM_SETTINGS_SUMMARY, mFromSettingsSummary);
startActivityForResult(intent, BIOMETRIC_FIND_SENSOR_REQUEST); startActivityForResult(intent, BIOMETRIC_FIND_SENSOR_REQUEST);
} }
protected Intent getChooseLockIntent() {
if (WizardManagerHelper.isAnySetupWizard(getIntent())) {
// Default to PIN lock in setup wizard
Intent intent = new Intent(this, SetupChooseLockGeneric.class);
if (StorageManager.isFileEncryptedNativeOrEmulated()) {
intent.putExtra(
LockPatternUtils.PASSWORD_TYPE_KEY,
DevicePolicyManager.PASSWORD_QUALITY_NUMERIC);
intent.putExtra(ChooseLockGenericFragment.EXTRA_SHOW_OPTIONS_BUTTON, true);
}
WizardManagerHelper.copyWizardManagerExtras(getIntent(), intent);
return intent;
} else {
return new Intent(this, ChooseLockGeneric.class);
}
}
@Override @Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) { protected void onActivityResult(int requestCode, int resultCode, Intent data) {
if (requestCode == BIOMETRIC_FIND_SENSOR_REQUEST) { if (requestCode == BIOMETRIC_FIND_SENSOR_REQUEST) {
@@ -301,6 +286,12 @@ public abstract class BiometricEnrollIntroduction extends BiometricEnrollBase
} }
} else if (requestCode == LEARN_MORE_REQUEST) { } else if (requestCode == LEARN_MORE_REQUEST) {
overridePendingTransition(R.anim.sud_slide_back_in, R.anim.sud_slide_back_out); overridePendingTransition(R.anim.sud_slide_back_in, R.anim.sud_slide_back_out);
} else if (requestCode == ENROLL_NEXT_BIOMETRIC_REQUEST) {
Log.d(TAG, "ENROLL_NEXT_BIOMETRIC_REQUEST, result: " + resultCode);
if (resultCode != RESULT_CANCELED) {
setResult(resultCode, data);
finish();
}
} }
super.onActivityResult(requestCode, resultCode, data); super.onActivityResult(requestCode, resultCode, data);
} }

View File

@@ -16,16 +16,35 @@
package com.android.settings.biometrics; package com.android.settings.biometrics;
import android.app.Activity;
import android.app.PendingIntent;
import android.app.admin.DevicePolicyManager;
import android.content.Context; import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.content.IntentSender;
import android.os.storage.StorageManager;
import android.util.Log;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.android.internal.widget.LockPatternUtils; import com.android.internal.widget.LockPatternUtils;
import com.android.settings.SetupWizardUtils;
import com.android.settings.biometrics.face.FaceEnrollIntroduction;
import com.android.settings.biometrics.fingerprint.FingerprintEnrollFindSensor;
import com.android.settings.biometrics.fingerprint.FingerprintEnrollIntroduction;
import com.android.settings.biometrics.fingerprint.SetupFingerprintEnrollIntroduction;
import com.android.settings.password.ChooseLockGeneric;
import com.android.settings.password.ChooseLockSettingsHelper; import com.android.settings.password.ChooseLockSettingsHelper;
import com.android.settings.password.SetupChooseLockGeneric;
import com.google.android.setupcompat.util.WizardManagerHelper;
/** /**
* Common biometric utilities. * Common biometric utilities.
*/ */
public class BiometricUtils { public class BiometricUtils {
private static final String TAG = "BiometricUtils";
/** /**
* Given the result from confirming or choosing a credential, request Gatekeeper to generate * Given the result from confirming or choosing a credential, request Gatekeeper to generate
* a HardwareAuthToken with the Gatekeeper Password together with a biometric challenge. * a HardwareAuthToken with the Gatekeeper Password together with a biometric challenge.
@@ -36,24 +55,29 @@ public class BiometricUtils {
* @param challenge Unique biometric challenge from FingerprintManager/FaceManager * @param challenge Unique biometric challenge from FingerprintManager/FaceManager
* @return * @return
*/ */
public static byte[] requestGatekeeperHat(Context context, Intent result, int userId, public static byte[] requestGatekeeperHat(@NonNull Context context, @NonNull Intent result,
long challenge) { int userId, long challenge) {
final long gatekeeperPasswordHandle = result.getLongExtra( if (!containsGatekeeperPasswordHandle(result)) {
ChooseLockSettingsHelper.EXTRA_KEY_GK_PW_HANDLE, 0L);
if (gatekeeperPasswordHandle == 0L) {
throw new IllegalStateException("Gatekeeper Password is missing!!"); throw new IllegalStateException("Gatekeeper Password is missing!!");
} }
final long gatekeeperPasswordHandle = result.getLongExtra(
ChooseLockSettingsHelper.EXTRA_KEY_GK_PW_HANDLE, 0L);
return requestGatekeeperHat(context, gatekeeperPasswordHandle, userId, challenge);
}
public static byte[] requestGatekeeperHat(@NonNull Context context, long gkPwHandle, int userId,
long challenge) {
final LockPatternUtils utils = new LockPatternUtils(context); final LockPatternUtils utils = new LockPatternUtils(context);
return utils.verifyGatekeeperPasswordHandle(gatekeeperPasswordHandle, challenge, userId) return utils.verifyGatekeeperPasswordHandle(gkPwHandle, challenge, userId)
.getGatekeeperHAT(); .getGatekeeperHAT();
} }
public static boolean containsGatekeeperPassword(Intent data) { public static boolean containsGatekeeperPasswordHandle(@Nullable Intent data) {
if (data == null) { return data != null && data.hasExtra(ChooseLockSettingsHelper.EXTRA_KEY_GK_PW_HANDLE);
return false; }
}
return data.getLongExtra(ChooseLockSettingsHelper.EXTRA_KEY_GK_PW_HANDLE, 0L) != 0L; public static long getGatekeeperPasswordHandle(@NonNull Intent data) {
return data.getLongExtra(ChooseLockSettingsHelper.EXTRA_KEY_GK_PW_HANDLE, 0L);
} }
/** /**
@@ -64,16 +88,157 @@ public class BiometricUtils {
* @param context Caller's context * @param context Caller's context
* @param data The onActivityResult intent from ChooseLock* or ConfirmLock* * @param data The onActivityResult intent from ChooseLock* or ConfirmLock*
*/ */
public static void removeGatekeeperPasswordHandle(Context context, Intent data) { public static void removeGatekeeperPasswordHandle(@NonNull Context context,
@Nullable Intent data) {
if (data == null) { if (data == null) {
return; return;
} }
final long gatekeeperPasswordsHandle = data.getLongExtra( if (!containsGatekeeperPasswordHandle(data)) {
ChooseLockSettingsHelper.EXTRA_KEY_GK_PW_HANDLE, 0L);
if (gatekeeperPasswordsHandle == 0L) {
return; return;
} }
removeGatekeeperPasswordHandle(context, getGatekeeperPasswordHandle(data));
}
public static void removeGatekeeperPasswordHandle(@NonNull Context context, long handle) {
final LockPatternUtils utils = new LockPatternUtils(context); final LockPatternUtils utils = new LockPatternUtils(context);
utils.removeGatekeeperPasswordHandle(gatekeeperPasswordsHandle); utils.removeGatekeeperPasswordHandle(handle);
Log.d(TAG, "Removed handle");
}
/**
* @param context caller's context
* @param activityIntent The intent that started the caller's activity
* @return Intent for starting ChooseLock*
*/
public static Intent getChooseLockIntent(@NonNull Context context,
@NonNull Intent activityIntent) {
if (WizardManagerHelper.isAnySetupWizard(activityIntent)) {
// Default to PIN lock in setup wizard
Intent intent = new Intent(context, SetupChooseLockGeneric.class);
if (StorageManager.isFileEncryptedNativeOrEmulated()) {
intent.putExtra(
LockPatternUtils.PASSWORD_TYPE_KEY,
DevicePolicyManager.PASSWORD_QUALITY_NUMERIC);
intent.putExtra(ChooseLockGeneric.ChooseLockGenericFragment
.EXTRA_SHOW_OPTIONS_BUTTON, true);
}
WizardManagerHelper.copyWizardManagerExtras(activityIntent, intent);
return intent;
} else {
return new Intent(context, ChooseLockGeneric.class);
}
}
/**
* @param context caller's context
* @param activityIntent The intent that started the caller's activity
* @return Intent for starting FingerprintEnrollFindSensor
*/
public static Intent getFingerprintFindSensorIntent(@NonNull Context context,
@NonNull Intent activityIntent) {
Intent intent = new Intent(context, FingerprintEnrollFindSensor.class);
SetupWizardUtils.copySetupExtras(activityIntent, intent);
return intent;
}
/**
* @param context caller's context
* @param activityIntent The intent that started the caller's activity
* @return Intent for starting FingerprintEnrollIntroduction
*/
public static Intent getFingerprintIntroIntent(@NonNull Context context,
@NonNull Intent activityIntent) {
if (WizardManagerHelper.isAnySetupWizard(activityIntent)) {
Intent intent = new Intent(context, SetupFingerprintEnrollIntroduction.class);
WizardManagerHelper.copyWizardManagerExtras(activityIntent, intent);
return intent;
} else {
return new Intent(context, FingerprintEnrollIntroduction.class);
}
}
/**
* @param context caller's context
* @param activityIntent The intent that started the caller's activity
* @return Intent for starting FaceEnrollIntroduction
*/
public static Intent getFaceIntroIntent(@NonNull Context context,
@NonNull Intent activityIntent) {
Intent intent = new Intent(context, FaceEnrollIntroduction.class);
WizardManagerHelper.copyWizardManagerExtras(activityIntent, intent);
return intent;
}
/**
* @param activity Reference to the calling activity, used to startActivity
* @param intent Intent pointing to the enrollment activity
* @param requestCode If non-zero, will invoke startActivityForResult instead of startActivity
* @param hardwareAuthToken HardwareAuthToken from Gatekeeper
* @param userId User to request enrollment for
*/
public static void launchEnrollForResult(@NonNull BiometricEnrollActivity activity,
@NonNull Intent intent, int requestCode,
@Nullable byte[] hardwareAuthToken, @Nullable Long gkPwHandle, int userId) {
if (hardwareAuthToken != null) {
intent.putExtra(ChooseLockSettingsHelper.EXTRA_KEY_CHALLENGE_TOKEN,
hardwareAuthToken);
}
if (gkPwHandle != null) {
intent.putExtra(ChooseLockSettingsHelper.EXTRA_KEY_GK_PW_HANDLE, gkPwHandle);
}
if (activity instanceof BiometricEnrollActivity.InternalActivity) {
intent.putExtra(Intent.EXTRA_USER_ID, userId);
}
if (requestCode != 0) {
activity.startActivityForResult(intent, requestCode);
} else {
activity.startActivity(intent);
activity.finish();
}
}
/**
* @param activity Activity that we want to check
* @return True if the activity is going through a multi-biometric enrollment flow.
*/
public static boolean isMultiBiometricEnrollmentFlow(@NonNull Activity activity) {
return activity.getIntent().hasExtra(MultiBiometricEnrollHelper.EXTRA_ENROLL_AFTER_FACE);
}
public static void copyMultiBiometricExtras(@NonNull Intent fromIntent,
@NonNull Intent toIntent) {
final PendingIntent pendingIntent = (PendingIntent) fromIntent.getExtra(
MultiBiometricEnrollHelper.EXTRA_ENROLL_AFTER_FACE, null);
if (pendingIntent != null) {
toIntent.putExtra(MultiBiometricEnrollHelper.EXTRA_ENROLL_AFTER_FACE, pendingIntent);
}
}
/**
* If the current biometric enrollment (e.g. face) should be followed by another one (e.g.
* fingerprint) (see {@link #isMultiBiometricEnrollmentFlow(Activity)}), retrieves the
* PendingIntent pointing to the next enrollment and starts it. The caller will receive the
* result in onActivityResult.
* @return true if the next enrollment was started
*/
public static boolean tryStartingNextBiometricEnroll(@NonNull Activity activity,
int requestCode) {
final PendingIntent pendingIntent = (PendingIntent) activity.getIntent()
.getExtra(MultiBiometricEnrollHelper.EXTRA_ENROLL_AFTER_FACE);
if (pendingIntent != null) {
try {
Log.d(TAG, "Starting pendingIntent: " + pendingIntent);
IntentSender intentSender = pendingIntent.getIntentSender();
activity.startIntentSenderForResult(intentSender, requestCode,
null /* fillInIntent */, 0 /* flagMask */, 0 /* flagValues */,
0 /* extraFlags */);
return true;
} catch (IntentSender.SendIntentException e) {
Log.e(TAG, "Pending intent canceled: " + e);
}
}
return false;
} }
} }

View File

@@ -0,0 +1,113 @@
/*
* Copyright (C) 2020 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;
import android.app.Activity;
import android.app.PendingIntent;
import android.content.Intent;
import android.hardware.face.FaceManager;
import android.hardware.fingerprint.FingerprintManager;
import android.util.Log;
import androidx.annotation.NonNull;
import com.android.settings.password.ChooseLockSettingsHelper;
/**
* Helper for {@link BiometricEnrollActivity} when multiple sensors exist on a device.
*/
public class MultiBiometricEnrollHelper {
private static final String TAG = "MultiBiometricEnrollHelper";
private static final int REQUEST_FACE_ENROLL = 3000;
private static final int REQUEST_FINGERPRINT_ENROLL = 3001;
public static final String EXTRA_ENROLL_AFTER_FACE = "enroll_after_face";
@NonNull private final BiometricEnrollActivity mActivity;
private final long mGkPwHandle;
private final int mUserId;
private final boolean mRequestEnrollFace;
private final boolean mRequestEnrollFingerprint;
MultiBiometricEnrollHelper(@NonNull BiometricEnrollActivity activity, int userId,
boolean enrollFace, boolean enrollFingerprint, long gkPwHandle) {
mActivity = activity;
mUserId = userId;
mGkPwHandle = gkPwHandle;
mRequestEnrollFace = enrollFace;
mRequestEnrollFingerprint = enrollFingerprint;
}
void startNextStep() {
if (mRequestEnrollFace) {
launchFaceEnroll();
} else if (mRequestEnrollFingerprint) {
launchFingerprintEnroll();
} else {
mActivity.setResult(BiometricEnrollIntroduction.RESULT_SKIP);
mActivity.finish();
}
}
private void launchFaceEnroll() {
final FaceManager faceManager = mActivity.getSystemService(FaceManager.class);
faceManager.generateChallenge((sensorId, challenge) -> {
final byte[] hardwareAuthToken = BiometricUtils.requestGatekeeperHat(mActivity,
mGkPwHandle, mUserId, challenge);
final Intent faceIntent = BiometricUtils.getFaceIntroIntent(mActivity,
mActivity.getIntent());
if (mRequestEnrollFingerprint) {
// Give FaceEnroll a pendingIntent pointing to fingerprint enrollment, so that it
// can be started when user skips or finishes face enrollment.
final Intent fpIntent = BiometricUtils.getFingerprintIntroIntent(mActivity,
mActivity.getIntent());
fpIntent.putExtra(ChooseLockSettingsHelper.EXTRA_KEY_GK_PW_HANDLE, mGkPwHandle);
final PendingIntent fpAfterFaceIntent = PendingIntent.getActivity(mActivity,
0 /* requestCode */, fpIntent, 0 /* flags */);
faceIntent.putExtra(EXTRA_ENROLL_AFTER_FACE, fpAfterFaceIntent);
}
BiometricUtils.launchEnrollForResult(mActivity, faceIntent, REQUEST_FACE_ENROLL,
hardwareAuthToken, mGkPwHandle, mUserId);
});
}
private void launchFingerprintEnroll() {
final FingerprintManager fingerprintManager = mActivity
.getSystemService(FingerprintManager.class);
fingerprintManager.generateChallenge(((sensorId, challenge) -> {
final byte[] hardwareAuthToken = BiometricUtils.requestGatekeeperHat(mActivity,
mGkPwHandle, mUserId, challenge);
final Intent intent = BiometricUtils.getFingerprintIntroIntent(mActivity,
mActivity.getIntent());
BiometricUtils.launchEnrollForResult(mActivity, intent, REQUEST_FINGERPRINT_ENROLL,
hardwareAuthToken, mGkPwHandle, mUserId);
}));
}
void onActivityResult(int requestCode, int resultCode, Intent data) {
Log.d(TAG, "RequestCode: " + requestCode + " resultCode: " + resultCode);
if (resultCode != Activity.RESULT_CANCELED) {
BiometricUtils.removeGatekeeperPasswordHandle(mActivity, mGkPwHandle);
mActivity.setResult(resultCode);
mActivity.finish();
}
}
}

View File

@@ -33,6 +33,7 @@ import android.widget.TextView;
import com.android.settings.R; import com.android.settings.R;
import com.android.settings.Utils; import com.android.settings.Utils;
import com.android.settings.biometrics.BiometricEnrollBase; import com.android.settings.biometrics.BiometricEnrollBase;
import com.android.settings.biometrics.BiometricUtils;
import com.android.settings.password.ChooseLockSettingsHelper; import com.android.settings.password.ChooseLockSettingsHelper;
import com.google.android.setupcompat.template.FooterBarMixin; import com.google.android.setupcompat.template.FooterBarMixin;
@@ -183,6 +184,7 @@ public class FaceEnrollEducation extends BiometricEnrollBase {
intent.putExtra(Intent.EXTRA_USER_ID, mUserId); intent.putExtra(Intent.EXTRA_USER_ID, mUserId);
} }
intent.putExtra(EXTRA_FROM_SETTINGS_SUMMARY, mFromSettingsSummary); intent.putExtra(EXTRA_FROM_SETTINGS_SUMMARY, mFromSettingsSummary);
BiometricUtils.copyMultiBiometricExtras(getIntent(), intent);
final String flattenedString = getString(R.string.config_face_enroll); final String flattenedString = getString(R.string.config_face_enroll);
if (!TextUtils.isEmpty(flattenedString)) { if (!TextUtils.isEmpty(flattenedString)) {
ComponentName componentName = ComponentName.unflattenFromString(flattenedString); ComponentName componentName = ComponentName.unflattenFromString(flattenedString);

View File

@@ -16,17 +16,23 @@
package com.android.settings.biometrics.face; package com.android.settings.biometrics.face;
import android.app.PendingIntent;
import android.app.admin.DevicePolicyManager; import android.app.admin.DevicePolicyManager;
import android.app.settings.SettingsEnums; import android.app.settings.SettingsEnums;
import android.content.Intent; import android.content.Intent;
import android.content.IntentSender;
import android.hardware.face.FaceManager; import android.hardware.face.FaceManager;
import android.hardware.face.FaceSensorProperties;
import android.os.Bundle; import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.TextView; import android.widget.TextView;
import com.android.settings.R; import com.android.settings.R;
import com.android.settings.Utils; import com.android.settings.Utils;
import com.android.settings.biometrics.BiometricEnrollIntroduction; import com.android.settings.biometrics.BiometricEnrollIntroduction;
import com.android.settings.biometrics.BiometricUtils; import com.android.settings.biometrics.BiometricUtils;
import com.android.settings.biometrics.MultiBiometricEnrollHelper;
import com.android.settings.overlay.FeatureFactory; import com.android.settings.overlay.FeatureFactory;
import com.android.settings.password.ChooseLockSettingsHelper; import com.android.settings.password.ChooseLockSettingsHelper;
import com.android.settingslib.RestrictedLockUtilsInternal; import com.android.settingslib.RestrictedLockUtilsInternal;
@@ -37,6 +43,8 @@ import com.google.android.setupcompat.util.WizardManagerHelper;
import com.google.android.setupdesign.span.LinkSpan; import com.google.android.setupdesign.span.LinkSpan;
import com.google.android.setupdesign.template.RequireScrollMixin; import com.google.android.setupdesign.template.RequireScrollMixin;
import java.util.List;
public class FaceEnrollIntroduction extends BiometricEnrollIntroduction { public class FaceEnrollIntroduction extends BiometricEnrollIntroduction {
private static final String TAG = "FaceEnrollIntroduction"; private static final String TAG = "FaceEnrollIntroduction";
@@ -44,6 +52,20 @@ public class FaceEnrollIntroduction extends BiometricEnrollIntroduction {
private FaceManager mFaceManager; private FaceManager mFaceManager;
private FaceFeatureProvider mFaceFeatureProvider; private FaceFeatureProvider mFaceFeatureProvider;
@Override
protected void onCancelButtonClick(View view) {
if (!BiometricUtils.tryStartingNextBiometricEnroll(this, ENROLL_NEXT_BIOMETRIC_REQUEST)) {
super.onCancelButtonClick(view);
}
}
@Override
protected void onSkipButtonClick(View view) {
if (!BiometricUtils.tryStartingNextBiometricEnroll(this, ENROLL_NEXT_BIOMETRIC_REQUEST)) {
super.onSkipButtonClick(view);
}
}
@Override @Override
protected void onCreate(Bundle savedInstanceState) { protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
@@ -53,25 +75,14 @@ public class FaceEnrollIntroduction extends BiometricEnrollIntroduction {
.getFaceFeatureProvider(); .getFaceFeatureProvider();
mFooterBarMixin = getLayout().getMixin(FooterBarMixin.class); mFooterBarMixin = getLayout().getMixin(FooterBarMixin.class);
if (WizardManagerHelper.isAnySetupWizard(getIntent())) { mFooterBarMixin.setSecondaryButton(
mFooterBarMixin.setSecondaryButton( new FooterButton.Builder(this)
new FooterButton.Builder(this) .setText(R.string.security_settings_face_enroll_introduction_no_thanks)
.setText(R.string.security_settings_face_enroll_introduction_no_thanks) .setListener(this::onSkipButtonClick)
.setListener(this::onSkipButtonClick) .setButtonType(FooterButton.ButtonType.SKIP)
.setButtonType(FooterButton.ButtonType.SKIP) .setTheme(R.style.SudGlifButton_Secondary)
.setTheme(R.style.SudGlifButton_Secondary) .build()
.build() );
);
} else {
mFooterBarMixin.setSecondaryButton(
new FooterButton.Builder(this)
.setText(R.string.security_settings_face_enroll_introduction_no_thanks)
.setListener(this::onCancelButtonClick)
.setButtonType(FooterButton.ButtonType.CANCEL)
.setTheme(R.style.SudGlifButton_Secondary)
.build()
);
}
FooterButton.Builder nextButtonBuilder = new FooterButton.Builder(this) FooterButton.Builder nextButtonBuilder = new FooterButton.Builder(this)
.setText(R.string.security_settings_face_enroll_introduction_agree) .setText(R.string.security_settings_face_enroll_introduction_agree)
@@ -99,13 +110,15 @@ public class FaceEnrollIntroduction extends BiometricEnrollIntroduction {
// This path is an entry point for SetNewPasswordController, e.g. // This path is an entry point for SetNewPasswordController, e.g.
// adb shell am start -a android.app.action.SET_NEW_PASSWORD // adb shell am start -a android.app.action.SET_NEW_PASSWORD
if (mToken == null && BiometricUtils.containsGatekeeperPassword(getIntent())) { if (mToken == null && BiometricUtils.containsGatekeeperPasswordHandle(getIntent())) {
mFooterBarMixin.getPrimaryButton().setEnabled(false); mFooterBarMixin.getPrimaryButton().setEnabled(false);
// We either block on generateChallenge, or need to gray out the "next" button until // We either block on generateChallenge, or need to gray out the "next" button until
// the challenge is ready. Let's just do this for now. // the challenge is ready. Let's just do this for now.
mFaceManager.generateChallenge((sensorId, challenge) -> { mFaceManager.generateChallenge((sensorId, challenge) -> {
mToken = BiometricUtils.requestGatekeeperHat(this, getIntent(), mUserId, challenge); mToken = BiometricUtils.requestGatekeeperHat(this, getIntent(), mUserId, challenge);
BiometricUtils.removeGatekeeperPasswordHandle(this, getIntent()); if (BiometricUtils.isMultiBiometricEnrollmentFlow(this)) {
BiometricUtils.removeGatekeeperPasswordHandle(this, getIntent());
}
mFooterBarMixin.getPrimaryButton().setEnabled(true); mFooterBarMixin.getPrimaryButton().setEnabled(true);
}); });
} }
@@ -160,8 +173,9 @@ public class FaceEnrollIntroduction extends BiometricEnrollIntroduction {
private boolean maxFacesEnrolled() { private boolean maxFacesEnrolled() {
if (mFaceManager != null) { if (mFaceManager != null) {
final int max = getResources().getInteger( final List<FaceSensorProperties> props = mFaceManager.getSensorProperties();
com.android.internal.R.integer.config_faceMaxTemplatesPerUser); // This will need to be updated for devices with multiple face sensors.
final int max = props.get(0).maxTemplatesAllowed;
final int numEnrolledFaces = mFaceManager.getEnrolledFaces(mUserId).size(); final int numEnrolledFaces = mFaceManager.getEnrolledFaces(mUserId).size();
return numEnrolledFaces >= max; return numEnrolledFaces >= max;
} else { } else {

View File

@@ -236,7 +236,7 @@ public class FaceSettings extends DashboardFragment {
public void onActivityResult(int requestCode, int resultCode, Intent data) { public void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data); super.onActivityResult(requestCode, resultCode, data);
if (mToken == null && !BiometricUtils.containsGatekeeperPassword(data)) { if (mToken == null && !BiometricUtils.containsGatekeeperPasswordHandle(data)) {
Log.e(TAG, "No credential"); Log.e(TAG, "No credential");
finish(); finish();
} }

View File

@@ -63,7 +63,7 @@ public class FingerprintEnrollFindSensor extends BiometricEnrollBase {
// This is an entry point for SetNewPasswordController, e.g. // This is an entry point for SetNewPasswordController, e.g.
// adb shell am start -a android.app.action.SET_NEW_PASSWORD // adb shell am start -a android.app.action.SET_NEW_PASSWORD
if (mToken == null && BiometricUtils.containsGatekeeperPassword(getIntent())) { if (mToken == null && BiometricUtils.containsGatekeeperPasswordHandle(getIntent())) {
final FingerprintManager fpm = getSystemService(FingerprintManager.class); final FingerprintManager fpm = getSystemService(FingerprintManager.class);
fpm.generateChallenge((sensorId, challenge) -> { fpm.generateChallenge((sensorId, challenge) -> {
mToken = BiometricUtils.requestGatekeeperHat(this, getIntent(), mUserId, challenge); mToken = BiometricUtils.requestGatekeeperHat(this, getIntent(), mUserId, challenge);
@@ -79,6 +79,9 @@ public class FingerprintEnrollFindSensor extends BiometricEnrollBase {
} else if (mToken != null) { } else if (mToken != null) {
// HAT passed in from somewhere else, such as FingerprintEnrollIntroduction // HAT passed in from somewhere else, such as FingerprintEnrollIntroduction
startLookingForFingerprint(); startLookingForFingerprint();
} else {
// There's something wrong with the enrollment flow, this should never happen.
throw new IllegalStateException("HAT and GkPwHandle both missing...");
} }
View animationView = findViewById(R.id.fingerprint_sensor_location_animation); View animationView = findViewById(R.id.fingerprint_sensor_location_animation);

View File

@@ -21,6 +21,7 @@ import android.app.settings.SettingsEnums;
import android.content.ActivityNotFoundException; import android.content.ActivityNotFoundException;
import android.content.Intent; import android.content.Intent;
import android.hardware.fingerprint.FingerprintManager; import android.hardware.fingerprint.FingerprintManager;
import android.hardware.fingerprint.FingerprintSensorProperties;
import android.os.Bundle; import android.os.Bundle;
import android.util.Log; import android.util.Log;
import android.widget.TextView; import android.widget.TextView;
@@ -28,6 +29,7 @@ import android.widget.TextView;
import com.android.settings.R; import com.android.settings.R;
import com.android.settings.Utils; import com.android.settings.Utils;
import com.android.settings.biometrics.BiometricEnrollIntroduction; import com.android.settings.biometrics.BiometricEnrollIntroduction;
import com.android.settings.biometrics.BiometricUtils;
import com.android.settings.password.ChooseLockSettingsHelper; import com.android.settings.password.ChooseLockSettingsHelper;
import com.android.settingslib.HelpUtils; import com.android.settingslib.HelpUtils;
import com.android.settingslib.RestrictedLockUtilsInternal; import com.android.settingslib.RestrictedLockUtilsInternal;
@@ -36,6 +38,8 @@ import com.google.android.setupcompat.template.FooterBarMixin;
import com.google.android.setupcompat.template.FooterButton; import com.google.android.setupcompat.template.FooterButton;
import com.google.android.setupdesign.span.LinkSpan; import com.google.android.setupdesign.span.LinkSpan;
import java.util.List;
public class FingerprintEnrollIntroduction extends BiometricEnrollIntroduction { public class FingerprintEnrollIntroduction extends BiometricEnrollIntroduction {
private static final String TAG = "FingerprintIntro"; private static final String TAG = "FingerprintIntro";
@@ -45,13 +49,14 @@ public class FingerprintEnrollIntroduction extends BiometricEnrollIntroduction {
@Override @Override
protected void onCreate(Bundle savedInstanceState) { protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
mFingerprintManager = Utils.getFingerprintManagerOrNull(this); mFingerprintManager = Utils.getFingerprintManagerOrNull(this);
mFooterBarMixin = getLayout().getMixin(FooterBarMixin.class); mFooterBarMixin = getLayout().getMixin(FooterBarMixin.class);
mFooterBarMixin.setSecondaryButton( mFooterBarMixin.setSecondaryButton(
new FooterButton.Builder(this) new FooterButton.Builder(this)
.setText(R.string.security_settings_face_enroll_introduction_cancel) .setText(getNegativeButtonTextId())
.setListener(this::onCancelButtonClick) .setListener(this::onSkipButtonClick)
.setButtonType(FooterButton.ButtonType.SKIP) .setButtonType(FooterButton.ButtonType.SKIP)
.setTheme(R.style.SudGlifButton_Secondary) .setTheme(R.style.SudGlifButton_Secondary)
.build() .build()
@@ -67,6 +72,10 @@ public class FingerprintEnrollIntroduction extends BiometricEnrollIntroduction {
); );
} }
int getNegativeButtonTextId() {
return R.string.security_settings_fingerprint_enroll_introduction_no_thanks;
}
@Override @Override
protected boolean isDisabledByAdmin() { protected boolean isDisabledByAdmin() {
return RestrictedLockUtilsInternal.checkIfKeyguardFeaturesDisabled( return RestrictedLockUtilsInternal.checkIfKeyguardFeaturesDisabled(
@@ -117,8 +126,10 @@ public class FingerprintEnrollIntroduction extends BiometricEnrollIntroduction {
@Override @Override
protected int checkMaxEnrolled() { protected int checkMaxEnrolled() {
if (mFingerprintManager != null) { if (mFingerprintManager != null) {
final int max = getResources().getInteger( final List<FingerprintSensorProperties> props =
com.android.internal.R.integer.config_fingerprintMaxTemplatesPerUser); mFingerprintManager.getSensorProperties();
// This will need to be updated for devices with multiple fingerprint sensors
final int max = props.get(0).maxTemplatesAllowed;
final int numEnrolledFingerprints = final int numEnrolledFingerprints =
mFingerprintManager.getEnrolledFingerprints(mUserId).size(); mFingerprintManager.getEnrolledFingerprints(mUserId).size();
if (numEnrolledFingerprints >= max) { if (numEnrolledFingerprints >= max) {
@@ -147,7 +158,12 @@ public class FingerprintEnrollIntroduction extends BiometricEnrollIntroduction {
@Override @Override
protected Intent getEnrollingIntent() { protected Intent getEnrollingIntent() {
return new Intent(this, FingerprintEnrollFindSensor.class); final Intent intent = new Intent(this, FingerprintEnrollFindSensor.class);
if (BiometricUtils.containsGatekeeperPasswordHandle(getIntent())) {
intent.putExtra(ChooseLockSettingsHelper.EXTRA_KEY_GK_PW_HANDLE,
BiometricUtils.getGatekeeperPasswordHandle(getIntent()));
}
return intent;
} }
@Override @Override

View File

@@ -570,7 +570,7 @@ public class FingerprintSettings extends SubSettings {
if (requestCode == CONFIRM_REQUEST || requestCode == CHOOSE_LOCK_GENERIC_REQUEST) { if (requestCode == CONFIRM_REQUEST || requestCode == CHOOSE_LOCK_GENERIC_REQUEST) {
mLaunchedConfirm = false; mLaunchedConfirm = false;
if (resultCode == RESULT_FINISHED || resultCode == RESULT_OK) { if (resultCode == RESULT_FINISHED || resultCode == RESULT_OK) {
if (data != null && BiometricUtils.containsGatekeeperPassword(data)) { if (data != null && BiometricUtils.containsGatekeeperPasswordHandle(data)) {
mFingerprintManager.generateChallenge((sensorId, challenge) -> { mFingerprintManager.generateChallenge((sensorId, challenge) -> {
mToken = BiometricUtils.requestGatekeeperHat(getActivity(), data, mToken = BiometricUtils.requestGatekeeperHat(getActivity(), data,
mUserId, challenge); mUserId, challenge);

View File

@@ -32,7 +32,9 @@ import com.android.internal.widget.LockPatternUtils;
import com.android.settings.R; import com.android.settings.R;
import com.android.settings.SetupWizardUtils; import com.android.settings.SetupWizardUtils;
import com.android.settings.Utils; import com.android.settings.Utils;
import com.android.settings.biometrics.BiometricUtils;
import com.android.settings.password.ChooseLockGeneric.ChooseLockGenericFragment; import com.android.settings.password.ChooseLockGeneric.ChooseLockGenericFragment;
import com.android.settings.password.ChooseLockSettingsHelper;
import com.android.settings.password.SetupChooseLockGeneric; import com.android.settings.password.SetupChooseLockGeneric;
import com.android.settings.password.SetupSkipDialog; import com.android.settings.password.SetupSkipDialog;
@@ -58,29 +60,24 @@ public class SetupFingerprintEnrollIntroduction extends FingerprintEnrollIntrodu
} }
} }
@Override
int getNegativeButtonTextId() {
return R.string.security_settings_face_enroll_introduction_cancel;
}
@Override @Override
protected void onSaveInstanceState(Bundle outState) { protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState); super.onSaveInstanceState(outState);
outState.putBoolean(KEY_LOCK_SCREEN_PRESENT, mAlreadyHadLockScreenSetup); outState.putBoolean(KEY_LOCK_SCREEN_PRESENT, mAlreadyHadLockScreenSetup);
} }
@Override
protected Intent getChooseLockIntent() {
Intent intent = new Intent(this, SetupChooseLockGeneric.class);
if (StorageManager.isFileEncryptedNativeOrEmulated()) {
intent.putExtra(
LockPatternUtils.PASSWORD_TYPE_KEY,
DevicePolicyManager.PASSWORD_QUALITY_NUMERIC);
intent.putExtra(ChooseLockGenericFragment.EXTRA_SHOW_OPTIONS_BUTTON, true);
}
SetupWizardUtils.copySetupExtras(getIntent(), intent);
return intent;
}
@Override @Override
protected Intent getEnrollingIntent() { protected Intent getEnrollingIntent() {
final Intent intent = new Intent(this, SetupFingerprintEnrollFindSensor.class); final Intent intent = new Intent(this, SetupFingerprintEnrollFindSensor.class);
if (BiometricUtils.containsGatekeeperPasswordHandle(getIntent())) {
intent.putExtra(ChooseLockSettingsHelper.EXTRA_KEY_GK_PW_HANDLE,
BiometricUtils.getGatekeeperPasswordHandle(getIntent()));
}
SetupWizardUtils.copySetupExtras(getIntent(), intent); SetupWizardUtils.copySetupExtras(getIntent(), intent);
return intent; return intent;
} }
@@ -157,6 +154,11 @@ public class SetupFingerprintEnrollIntroduction extends FingerprintEnrollIntrodu
} }
} }
@Override
protected void onSkipButtonClick(View view) {
onCancelButtonClick(view);
}
/** /**
* Propagate lock screen metrics if the user goes back from the fingerprint setup screen * Propagate lock screen metrics if the user goes back from the fingerprint setup screen
* after having added lock screen to his device. * after having added lock screen to his device.

View File

@@ -103,6 +103,7 @@ public class ChooseLockGeneric extends SettingsActivity {
private static final String TAG = "ChooseLockGenericFragment"; private static final String TAG = "ChooseLockGenericFragment";
private static final String KEY_SKIP_FINGERPRINT = "unlock_skip_fingerprint"; private static final String KEY_SKIP_FINGERPRINT = "unlock_skip_fingerprint";
private static final String KEY_SKIP_FACE = "unlock_skip_face"; private static final String KEY_SKIP_FACE = "unlock_skip_face";
private static final String KEY_SKIP_BIOMETRICS = "unlock_skip_biometrics";
private static final String PASSWORD_CONFIRMED = "password_confirmed"; private static final String PASSWORD_CONFIRMED = "password_confirmed";
private static final String WAITING_FOR_CONFIRMATION = "waiting_for_confirmation"; private static final String WAITING_FOR_CONFIRMATION = "waiting_for_confirmation";
public static final String MINIMUM_QUALITY_KEY = "minimum_quality"; public static final String MINIMUM_QUALITY_KEY = "minimum_quality";
@@ -175,6 +176,7 @@ public class ChooseLockGeneric extends SettingsActivity {
protected boolean mForFingerprint = false; protected boolean mForFingerprint = false;
protected boolean mForFace = false; protected boolean mForFace = false;
protected boolean mForBiometrics = false;
@Override @Override
public int getMetricsCategory() { public int getMetricsCategory() {
@@ -216,6 +218,9 @@ public class ChooseLockGeneric extends SettingsActivity {
ChooseLockSettingsHelper.EXTRA_KEY_FOR_FINGERPRINT, false); ChooseLockSettingsHelper.EXTRA_KEY_FOR_FINGERPRINT, false);
mForFace = intent.getBooleanExtra( mForFace = intent.getBooleanExtra(
ChooseLockSettingsHelper.EXTRA_KEY_FOR_FACE, false); ChooseLockSettingsHelper.EXTRA_KEY_FOR_FACE, false);
mForBiometrics = intent.getBooleanExtra(
ChooseLockSettingsHelper.EXTRA_KEY_FOR_BIOMETRICS, false);
mRequestedMinComplexity = intent mRequestedMinComplexity = intent
.getIntExtra(EXTRA_KEY_REQUESTED_MIN_COMPLEXITY, PASSWORD_COMPLEXITY_NONE); .getIntExtra(EXTRA_KEY_REQUESTED_MIN_COMPLEXITY, PASSWORD_COMPLEXITY_NONE);
mCallerAppName = mCallerAppName =
@@ -303,17 +308,20 @@ public class ChooseLockGeneric extends SettingsActivity {
} }
protected void addHeaderView() { protected void addHeaderView() {
setHeaderView(R.layout.choose_lock_generic_biometric_header);
TextView textView = getHeaderView().findViewById(R.id.biometric_header_description);
if (mForFingerprint) { if (mForFingerprint) {
setHeaderView(R.layout.choose_lock_generic_fingerprint_header);
if (mIsSetNewPassword) { if (mIsSetNewPassword) {
((TextView) getHeaderView().findViewById(R.id.fingerprint_header_description)) textView.setText(R.string.fingerprint_unlock_title);
.setText(R.string.fingerprint_unlock_title);
} }
} else if (mForFace) { } else if (mForFace) {
setHeaderView(R.layout.choose_lock_generic_face_header);
if (mIsSetNewPassword) { if (mIsSetNewPassword) {
((TextView) getHeaderView().findViewById(R.id.face_header_description)) textView.setText(R.string.face_unlock_title);
.setText(R.string.face_unlock_title); }
} else if (mForBiometrics) {
if (mIsSetNewPassword) {
textView.setText(R.string.biometrics_unlock_title);
} }
} }
} }
@@ -328,7 +336,8 @@ public class ChooseLockGeneric extends SettingsActivity {
// unlock method to an insecure one // unlock method to an insecure one
showFactoryResetProtectionWarningDialog(key); showFactoryResetProtectionWarningDialog(key);
return true; return true;
} else if (KEY_SKIP_FINGERPRINT.equals(key) || KEY_SKIP_FACE.equals(key)) { } else if (KEY_SKIP_FINGERPRINT.equals(key) || KEY_SKIP_FACE.equals(key)
|| KEY_SKIP_BIOMETRICS.equals(key)) {
Intent chooseLockGenericIntent = new Intent(getActivity(), Intent chooseLockGenericIntent = new Intent(getActivity(),
getInternalActivityClass()); getInternalActivityClass());
chooseLockGenericIntent.setAction(getIntent().getAction()); chooseLockGenericIntent.setAction(getIntent().getAction());
@@ -357,7 +366,7 @@ public class ChooseLockGeneric extends SettingsActivity {
* @param disabled * @param disabled
*/ */
// TODO: why does this take disabled, its always called with a quality higher than // TODO: why does this take disabled, its always called with a quality higher than
// what makes sense with disabled == true // what makes sense with disabled == true
private void maybeEnableEncryption(int quality, boolean disabled) { private void maybeEnableEncryption(int quality, boolean disabled) {
DevicePolicyManager dpm = (DevicePolicyManager) getSystemService(DEVICE_POLICY_SERVICE); DevicePolicyManager dpm = (DevicePolicyManager) getSystemService(DEVICE_POLICY_SERVICE);
if (UserManager.get(getActivity()).isAdminUser() if (UserManager.get(getActivity()).isAdminUser()
@@ -381,8 +390,8 @@ public class ChooseLockGeneric extends SettingsActivity {
unlockMethodIntent); unlockMethodIntent);
intent.putExtra(ChooseLockSettingsHelper.EXTRA_KEY_FOR_FINGERPRINT, intent.putExtra(ChooseLockSettingsHelper.EXTRA_KEY_FOR_FINGERPRINT,
mForFingerprint); mForFingerprint);
intent.putExtra(ChooseLockSettingsHelper.EXTRA_KEY_FOR_FACE, intent.putExtra(ChooseLockSettingsHelper.EXTRA_KEY_FOR_FACE, mForFace);
mForFace); intent.putExtra(ChooseLockSettingsHelper.EXTRA_KEY_FOR_BIOMETRICS, mForBiometrics);
// If the caller requested Gatekeeper Password to be returned, we assume it came // If the caller requested Gatekeeper Password to be returned, we assume it came
// from biometric enrollment. This should be cleaned up, since requesting // from biometric enrollment. This should be cleaned up, since requesting
// Gatekeeper Password should not imply it came from biometric setup/settings. // Gatekeeper Password should not imply it came from biometric setup/settings.
@@ -531,6 +540,7 @@ public class ChooseLockGeneric extends SettingsActivity {
findPreference(ScreenLockType.NONE.preferenceKey).setViewId(R.id.lock_none); findPreference(ScreenLockType.NONE.preferenceKey).setViewId(R.id.lock_none);
findPreference(KEY_SKIP_FINGERPRINT).setViewId(R.id.lock_none); findPreference(KEY_SKIP_FINGERPRINT).setViewId(R.id.lock_none);
findPreference(KEY_SKIP_FACE).setViewId(R.id.lock_none); findPreference(KEY_SKIP_FACE).setViewId(R.id.lock_none);
findPreference(KEY_SKIP_BIOMETRICS).setViewId(R.id.lock_none);
findPreference(ScreenLockType.PIN.preferenceKey).setViewId(R.id.lock_pin); findPreference(ScreenLockType.PIN.preferenceKey).setViewId(R.id.lock_pin);
findPreference(ScreenLockType.PASSWORD.preferenceKey).setViewId(R.id.lock_password); findPreference(ScreenLockType.PASSWORD.preferenceKey).setViewId(R.id.lock_password);
} }
@@ -569,6 +579,12 @@ public class ChooseLockGeneric extends SettingsActivity {
setPreferenceTitle(ScreenLockType.PIN, R.string.face_unlock_set_unlock_pin); setPreferenceTitle(ScreenLockType.PIN, R.string.face_unlock_set_unlock_pin);
setPreferenceTitle(ScreenLockType.PASSWORD, setPreferenceTitle(ScreenLockType.PASSWORD,
R.string.face_unlock_set_unlock_password); R.string.face_unlock_set_unlock_password);
} else if (mForBiometrics) {
setPreferenceTitle(ScreenLockType.PATTERN,
R.string.biometrics_unlock_set_unlock_pattern);
setPreferenceTitle(ScreenLockType.PIN, R.string.biometrics_unlock_set_unlock_pin);
setPreferenceTitle(ScreenLockType.PASSWORD,
R.string.biometrics_unlock_set_unlock_password);
} }
if (mManagedPasswordProvider.isSettingManagedPasswordSupported()) { if (mManagedPasswordProvider.isSettingManagedPasswordSupported()) {
@@ -584,6 +600,9 @@ public class ChooseLockGeneric extends SettingsActivity {
if (!(mForFace && mIsSetNewPassword)) { if (!(mForFace && mIsSetNewPassword)) {
removePreference(KEY_SKIP_FACE); removePreference(KEY_SKIP_FACE);
} }
if (!(mForBiometrics && mIsSetNewPassword)) {
removePreference(KEY_SKIP_BIOMETRICS);
}
} }
private void setPreferenceTitle(ScreenLockType lock, @StringRes int title) { private void setPreferenceTitle(ScreenLockType lock, @StringRes int title) {
@@ -727,6 +746,7 @@ public class ChooseLockGeneric extends SettingsActivity {
.setRequestedMinComplexity(mRequestedMinComplexity) .setRequestedMinComplexity(mRequestedMinComplexity)
.setForFingerprint(mForFingerprint) .setForFingerprint(mForFingerprint)
.setForFace(mForFace) .setForFace(mForFace)
.setForBiometrics(mForBiometrics)
.setUserId(mUserId) .setUserId(mUserId)
.setRequestGatekeeperPasswordHandle(mRequestGatekeeperPasswordHandle); .setRequestGatekeeperPasswordHandle(mRequestGatekeeperPasswordHandle);
if (mUserPassword != null) { if (mUserPassword != null) {
@@ -743,6 +763,7 @@ public class ChooseLockGeneric extends SettingsActivity {
new ChooseLockPattern.IntentBuilder(getContext()) new ChooseLockPattern.IntentBuilder(getContext())
.setForFingerprint(mForFingerprint) .setForFingerprint(mForFingerprint)
.setForFace(mForFace) .setForFace(mForFace)
.setForBiometrics(mForBiometrics)
.setUserId(mUserId) .setUserId(mUserId)
.setRequestGatekeeperPasswordHandle(mRequestGatekeeperPasswordHandle); .setRequestGatekeeperPasswordHandle(mRequestGatekeeperPasswordHandle);
if (mUserPassword != null) { if (mUserPassword != null) {

View File

@@ -151,6 +151,11 @@ public class ChooseLockPassword extends SettingsActivity {
return this; return this;
} }
public IntentBuilder setForBiometrics(boolean forBiometrics) {
mIntent.putExtra(ChooseLockSettingsHelper.EXTRA_KEY_FOR_BIOMETRICS, forBiometrics);
return this;
}
public IntentBuilder setRequestedMinComplexity(@PasswordComplexity int level) { public IntentBuilder setRequestedMinComplexity(@PasswordComplexity int level) {
mIntent.putExtra(EXTRA_KEY_REQUESTED_MIN_COMPLEXITY, level); mIntent.putExtra(EXTRA_KEY_REQUESTED_MIN_COMPLEXITY, level);
return this; return this;
@@ -190,12 +195,16 @@ public class ChooseLockPassword extends SettingsActivity {
.getBooleanExtra(ChooseLockSettingsHelper.EXTRA_KEY_FOR_FINGERPRINT, false); .getBooleanExtra(ChooseLockSettingsHelper.EXTRA_KEY_FOR_FINGERPRINT, false);
final boolean forFace = getIntent() final boolean forFace = getIntent()
.getBooleanExtra(ChooseLockSettingsHelper.EXTRA_KEY_FOR_FACE, false); .getBooleanExtra(ChooseLockSettingsHelper.EXTRA_KEY_FOR_FACE, false);
final boolean forBiometrics = getIntent()
.getBooleanExtra(ChooseLockSettingsHelper.EXTRA_KEY_FOR_BIOMETRICS, false);
CharSequence msg = getText(R.string.lockpassword_choose_your_screen_lock_header); CharSequence msg = getText(R.string.lockpassword_choose_your_screen_lock_header);
if (forFingerprint) { if (forFingerprint) {
msg = getText(R.string.lockpassword_choose_your_password_header_for_fingerprint); msg = getText(R.string.lockpassword_choose_your_password_header_for_fingerprint);
} else if (forFace) { } else if (forFace) {
msg = getText(R.string.lockpassword_choose_your_password_header_for_face); msg = getText(R.string.lockpassword_choose_your_password_header_for_face);
} else if (forBiometrics) {
msg = getText(R.string.lockpassword_choose_your_password_header_for_biometrics);
} }
setTitle(msg); setTitle(msg);
@@ -232,6 +241,7 @@ public class ChooseLockPassword extends SettingsActivity {
private GlifLayout mLayout; private GlifLayout mLayout;
protected boolean mForFingerprint; protected boolean mForFingerprint;
protected boolean mForFace; protected boolean mForFace;
protected boolean mForBiometrics;
private LockscreenCredential mFirstPassword; private LockscreenCredential mFirstPassword;
private RecyclerView mPasswordRestrictionView; private RecyclerView mPasswordRestrictionView;
@@ -254,9 +264,11 @@ public class ChooseLockPassword extends SettingsActivity {
R.string.lockpassword_choose_your_screen_lock_header, // password R.string.lockpassword_choose_your_screen_lock_header, // password
R.string.lockpassword_choose_your_password_header_for_fingerprint, R.string.lockpassword_choose_your_password_header_for_fingerprint,
R.string.lockpassword_choose_your_password_header_for_face, R.string.lockpassword_choose_your_password_header_for_face,
R.string.lockpassword_choose_your_password_header_for_biometrics,
R.string.lockpassword_choose_your_screen_lock_header, // pin R.string.lockpassword_choose_your_screen_lock_header, // pin
R.string.lockpassword_choose_your_pin_header_for_fingerprint, R.string.lockpassword_choose_your_pin_header_for_fingerprint,
R.string.lockpassword_choose_your_pin_header_for_face, R.string.lockpassword_choose_your_pin_header_for_face,
R.string.lockpassword_choose_your_pin_header_for_biometrics,
R.string.lockpassword_choose_your_password_message, // added security message R.string.lockpassword_choose_your_password_message, // added security message
R.string.lock_settings_picker_biometrics_added_security_message, R.string.lock_settings_picker_biometrics_added_security_message,
R.string.lockpassword_choose_your_pin_message, R.string.lockpassword_choose_your_pin_message,
@@ -267,6 +279,8 @@ public class ChooseLockPassword extends SettingsActivity {
R.string.lockpassword_confirm_your_password_header, R.string.lockpassword_confirm_your_password_header,
R.string.lockpassword_confirm_your_password_header, R.string.lockpassword_confirm_your_password_header,
R.string.lockpassword_confirm_your_password_header, R.string.lockpassword_confirm_your_password_header,
R.string.lockpassword_confirm_your_password_header,
R.string.lockpassword_confirm_your_pin_header,
R.string.lockpassword_confirm_your_pin_header, R.string.lockpassword_confirm_your_pin_header,
R.string.lockpassword_confirm_your_pin_header, R.string.lockpassword_confirm_your_pin_header,
R.string.lockpassword_confirm_your_pin_header, R.string.lockpassword_confirm_your_pin_header,
@@ -280,6 +294,8 @@ public class ChooseLockPassword extends SettingsActivity {
R.string.lockpassword_confirm_passwords_dont_match, R.string.lockpassword_confirm_passwords_dont_match,
R.string.lockpassword_confirm_passwords_dont_match, R.string.lockpassword_confirm_passwords_dont_match,
R.string.lockpassword_confirm_passwords_dont_match, R.string.lockpassword_confirm_passwords_dont_match,
R.string.lockpassword_confirm_passwords_dont_match,
R.string.lockpassword_confirm_pins_dont_match,
R.string.lockpassword_confirm_pins_dont_match, R.string.lockpassword_confirm_pins_dont_match,
R.string.lockpassword_confirm_pins_dont_match, R.string.lockpassword_confirm_pins_dont_match,
R.string.lockpassword_confirm_pins_dont_match, R.string.lockpassword_confirm_pins_dont_match,
@@ -289,18 +305,22 @@ public class ChooseLockPassword extends SettingsActivity {
0, 0,
R.string.lockpassword_confirm_label); R.string.lockpassword_confirm_label);
Stage(int hintInAlpha, int hintInAlphaForFingerprint, int hintInAlphaForFace, Stage(int hintInAlpha,
int hintInNumeric, int hintInNumericForFingerprint, int hintInNumericForFace, int hintInAlphaForFingerprint, int hintInAlphaForFace, int hintInAlphaForBiometrics,
int hintInNumeric,
int hintInNumericForFingerprint, int hintInNumericForFace, int hintInNumericForBiometrics,
int messageInAlpha, int messageInAlphaForBiometrics, int messageInAlpha, int messageInAlphaForBiometrics,
int messageInNumeric, int messageInNumericForBiometrics, int messageInNumeric, int messageInNumericForBiometrics,
int nextButtonText) { int nextButtonText) {
this.alphaHint = hintInAlpha; this.alphaHint = hintInAlpha;
this.alphaHintForFingerprint = hintInAlphaForFingerprint; this.alphaHintForFingerprint = hintInAlphaForFingerprint;
this.alphaHintForFace = hintInAlphaForFace; this.alphaHintForFace = hintInAlphaForFace;
this.alphaHintForBiometrics = hintInAlphaForBiometrics;
this.numericHint = hintInNumeric; this.numericHint = hintInNumeric;
this.numericHintForFingerprint = hintInNumericForFingerprint; this.numericHintForFingerprint = hintInNumericForFingerprint;
this.numericHintForFace = hintInNumericForFace; this.numericHintForFace = hintInNumericForFace;
this.numericHintForBiometrics = hintInNumericForBiometrics;
this.alphaMessage = messageInAlpha; this.alphaMessage = messageInAlpha;
this.alphaMessageForBiometrics = messageInAlphaForBiometrics; this.alphaMessageForBiometrics = messageInAlphaForBiometrics;
@@ -312,16 +332,19 @@ public class ChooseLockPassword extends SettingsActivity {
public static final int TYPE_NONE = 0; public static final int TYPE_NONE = 0;
public static final int TYPE_FINGERPRINT = 1; public static final int TYPE_FINGERPRINT = 1;
public static final int TYPE_FACE = 2; public static final int TYPE_FACE = 2;
public static final int TYPE_BIOMETRIC = 3;
// Password // Password
public final int alphaHint; public final int alphaHint;
public final int alphaHintForFingerprint; public final int alphaHintForFingerprint;
public final int alphaHintForFace; public final int alphaHintForFace;
public final int alphaHintForBiometrics;
// PIN // PIN
public final int numericHint; public final int numericHint;
public final int numericHintForFingerprint; public final int numericHintForFingerprint;
public final int numericHintForFace; public final int numericHintForFace;
public final int numericHintForBiometrics;
public final int alphaMessage; public final int alphaMessage;
public final int alphaMessageForBiometrics; public final int alphaMessageForBiometrics;
@@ -335,6 +358,8 @@ public class ChooseLockPassword extends SettingsActivity {
return alphaHintForFingerprint; return alphaHintForFingerprint;
} else if (type == TYPE_FACE) { } else if (type == TYPE_FACE) {
return alphaHintForFace; return alphaHintForFace;
} else if (type == TYPE_BIOMETRIC) {
return alphaHintForBiometrics;
} else { } else {
return alphaHint; return alphaHint;
} }
@@ -343,6 +368,8 @@ public class ChooseLockPassword extends SettingsActivity {
return numericHintForFingerprint; return numericHintForFingerprint;
} else if (type == TYPE_FACE) { } else if (type == TYPE_FACE) {
return numericHintForFace; return numericHintForFace;
} else if (type == TYPE_BIOMETRIC) {
return numericHintForBiometrics;
} else { } else {
return numericHint; return numericHint;
} }
@@ -376,6 +403,8 @@ public class ChooseLockPassword extends SettingsActivity {
mForFingerprint = intent.getBooleanExtra( mForFingerprint = intent.getBooleanExtra(
ChooseLockSettingsHelper.EXTRA_KEY_FOR_FINGERPRINT, false); ChooseLockSettingsHelper.EXTRA_KEY_FOR_FINGERPRINT, false);
mForFace = intent.getBooleanExtra(ChooseLockSettingsHelper.EXTRA_KEY_FOR_FACE, false); mForFace = intent.getBooleanExtra(ChooseLockSettingsHelper.EXTRA_KEY_FOR_FACE, false);
mForBiometrics = intent.getBooleanExtra(
ChooseLockSettingsHelper.EXTRA_KEY_FOR_BIOMETRICS, false);
mMinComplexity = intent.getIntExtra( mMinComplexity = intent.getIntExtra(
EXTRA_KEY_REQUESTED_MIN_COMPLEXITY, PASSWORD_COMPLEXITY_NONE); EXTRA_KEY_REQUESTED_MIN_COMPLEXITY, PASSWORD_COMPLEXITY_NONE);
@@ -454,6 +483,8 @@ public class ChooseLockPassword extends SettingsActivity {
mLayout.setIcon(getActivity().getDrawable(R.drawable.ic_fingerprint_header)); mLayout.setIcon(getActivity().getDrawable(R.drawable.ic_fingerprint_header));
} else if (mForFace) { } else if (mForFace) {
mLayout.setIcon(getActivity().getDrawable(R.drawable.ic_face_header)); mLayout.setIcon(getActivity().getDrawable(R.drawable.ic_face_header));
} else if (mForBiometrics) {
mLayout.setIcon(getActivity().getDrawable(R.drawable.ic_lock));
} }
mIsAlphaMode = DevicePolicyManager.PASSWORD_QUALITY_ALPHABETIC == mRequestedQuality mIsAlphaMode = DevicePolicyManager.PASSWORD_QUALITY_ALPHABETIC == mRequestedQuality
@@ -546,9 +577,15 @@ public class ChooseLockPassword extends SettingsActivity {
} }
protected int getStageType() { protected int getStageType() {
return mForFingerprint ? Stage.TYPE_FINGERPRINT : if (mForFingerprint) {
mForFace ? Stage.TYPE_FACE : return Stage.TYPE_FINGERPRINT;
Stage.TYPE_NONE; } else if (mForFace) {
return Stage.TYPE_FACE;
} else if (mForBiometrics) {
return Stage.TYPE_BIOMETRIC;
} else {
return Stage.TYPE_NONE;
}
} }
private void setupPasswordRequirementsView(View view) { private void setupPasswordRequirementsView(View view) {

View File

@@ -81,7 +81,7 @@ public class ChooseLockPattern extends SettingsActivity {
* behavior. So, now an activity does not finish itself until it gets this * behavior. So, now an activity does not finish itself until it gets this
* result. * result.
*/ */
static final int RESULT_FINISHED = RESULT_FIRST_USER; public static final int RESULT_FINISHED = RESULT_FIRST_USER;
private static final String TAG = "ChooseLockPattern"; private static final String TAG = "ChooseLockPattern";
@@ -134,6 +134,11 @@ public class ChooseLockPattern extends SettingsActivity {
return this; return this;
} }
public IntentBuilder setForBiometrics(boolean forBiometrics) {
mIntent.putExtra(ChooseLockSettingsHelper.EXTRA_KEY_FOR_BIOMETRICS, forBiometrics);
return this;
}
/** /**
* Configures the launch such that at the end of the pattern enrollment, one of its * Configures the launch such that at the end of the pattern enrollment, one of its
* managed profile (specified by {@code profileId}) will have its lockscreen unified * managed profile (specified by {@code profileId}) will have its lockscreen unified
@@ -455,6 +460,7 @@ public class ChooseLockPattern extends SettingsActivity {
protected int mUserId; protected int mUserId;
protected boolean mForFingerprint; protected boolean mForFingerprint;
protected boolean mForFace; protected boolean mForFace;
protected boolean mForBiometrics;
private static final String KEY_UI_STAGE = "uiStage"; private static final String KEY_UI_STAGE = "uiStage";
private static final String KEY_PATTERN_CHOICE = "chosenPattern"; private static final String KEY_PATTERN_CHOICE = "chosenPattern";
@@ -488,6 +494,8 @@ public class ChooseLockPattern extends SettingsActivity {
ChooseLockSettingsHelper.EXTRA_KEY_FOR_FINGERPRINT, false); ChooseLockSettingsHelper.EXTRA_KEY_FOR_FINGERPRINT, false);
mForFace = intent.getBooleanExtra( mForFace = intent.getBooleanExtra(
ChooseLockSettingsHelper.EXTRA_KEY_FOR_FACE, false); ChooseLockSettingsHelper.EXTRA_KEY_FOR_FACE, false);
mForBiometrics = intent.getBooleanExtra(
ChooseLockSettingsHelper.EXTRA_KEY_FOR_BIOMETRICS, false);
} }
@Override @Override
@@ -506,6 +514,8 @@ public class ChooseLockPattern extends SettingsActivity {
layout.setIcon(getActivity().getDrawable(R.drawable.ic_fingerprint_header)); layout.setIcon(getActivity().getDrawable(R.drawable.ic_fingerprint_header));
} else if (mForFace) { } else if (mForFace) {
layout.setIcon(getActivity().getDrawable(R.drawable.ic_face_header)); layout.setIcon(getActivity().getDrawable(R.drawable.ic_face_header));
} else if (mForBiometrics) {
layout.setIcon(getActivity().getDrawable(R.drawable.ic_lock));
} }
} }
@@ -732,8 +742,8 @@ public class ChooseLockPattern extends SettingsActivity {
} else { } else {
mHeaderText.setText(stage.headerMessage); mHeaderText.setText(stage.headerMessage);
} }
final boolean forBiometrics = mForFingerprint || mForFace; final boolean forAnyBiometric = mForFingerprint || mForFace || mForBiometrics;
int message = forBiometrics ? stage.messageForBiometrics : stage.message; int message = forAnyBiometric ? stage.messageForBiometrics : stage.message;
if (message == ID_EMPTY_MESSAGE) { if (message == ID_EMPTY_MESSAGE) {
mMessageText.setText(""); mMessageText.setText("");
} else { } else {
@@ -756,7 +766,7 @@ public class ChooseLockPattern extends SettingsActivity {
mHeaderText.setTextColor(mDefaultHeaderColorList); mHeaderText.setTextColor(mDefaultHeaderColorList);
} }
if (stage == Stage.NeedToConfirm && forBiometrics) { if (stage == Stage.NeedToConfirm && forAnyBiometric) {
mHeaderText.setText(""); mHeaderText.setText("");
mTitleText.setText(R.string.lockpassword_draw_your_pattern_again_header); mTitleText.setText(R.string.lockpassword_draw_your_pattern_again_header);
} }

View File

@@ -50,8 +50,12 @@ public final class ChooseLockSettingsHelper {
public static final String EXTRA_KEY_FORCE_VERIFY = "force_verify"; public static final String EXTRA_KEY_FORCE_VERIFY = "force_verify";
// Gatekeeper HardwareAuthToken // Gatekeeper HardwareAuthToken
public static final String EXTRA_KEY_CHALLENGE_TOKEN = "hw_auth_token"; public static final String EXTRA_KEY_CHALLENGE_TOKEN = "hw_auth_token";
// For the fingerprint-only path
public static final String EXTRA_KEY_FOR_FINGERPRINT = "for_fingerprint"; public static final String EXTRA_KEY_FOR_FINGERPRINT = "for_fingerprint";
// For the face-only path
public static final String EXTRA_KEY_FOR_FACE = "for_face"; public static final String EXTRA_KEY_FOR_FACE = "for_face";
// For the paths where multiple biometric sensors exist
public static final String EXTRA_KEY_FOR_BIOMETRICS = "for_biometrics";
public static final String EXTRA_KEY_FOR_CHANGE_CRED_REQUIRED_FOR_BOOT = "for_cred_req_boot"; public static final String EXTRA_KEY_FOR_CHANGE_CRED_REQUIRED_FOR_BOOT = "for_cred_req_boot";
public static final String EXTRA_KEY_FOREGROUND_ONLY = "foreground_only"; public static final String EXTRA_KEY_FOREGROUND_ONLY = "foreground_only";
public static final String EXTRA_KEY_REQUEST_GK_PW_HANDLE = "request_gk_pw_handle"; public static final String EXTRA_KEY_REQUEST_GK_PW_HANDLE = "request_gk_pw_handle";

View File

@@ -111,28 +111,49 @@ final class SetNewPasswordController {
*/ */
public void dispatchSetNewPasswordIntent() { public void dispatchSetNewPasswordIntent() {
final Bundle extras; final Bundle extras;
// TODO: handle the case with multiple biometrics, perhaps take an arg for biometric type?
if (mPackageManager.hasSystemFeature(PackageManager.FEATURE_FACE) final boolean hasFeatureFingerprint = mPackageManager
&& mFaceManager != null .hasSystemFeature(PackageManager.FEATURE_FINGERPRINT);
&& mFaceManager.isHardwareDetected() final boolean hasFeatureFace = mPackageManager
&& !mFaceManager.hasEnrolledTemplates(mTargetUserId) .hasSystemFeature(PackageManager.FEATURE_FACE);
&& !isFaceDisabledByAdmin()) {
extras = getFaceChooseLockExtras(); final boolean shouldShowFingerprintEnroll = mFingerprintManager != null
} else if (mPackageManager.hasSystemFeature(PackageManager.FEATURE_FINGERPRINT)
&& mFingerprintManager != null
&& mFingerprintManager.isHardwareDetected() && mFingerprintManager.isHardwareDetected()
&& !mFingerprintManager.hasEnrolledFingerprints(mTargetUserId) && !mFingerprintManager.hasEnrolledFingerprints(mTargetUserId)
&& !isFingerprintDisabledByAdmin()) { && !isFingerprintDisabledByAdmin();
final boolean shouldShowFaceEnroll = mFaceManager != null
&& mFaceManager.isHardwareDetected()
&& !mFaceManager.hasEnrolledTemplates(mTargetUserId)
&& !isFaceDisabledByAdmin();
if (hasFeatureFace && shouldShowFaceEnroll
&& hasFeatureFingerprint && shouldShowFingerprintEnroll) {
extras = getBiometricChooseLockExtras();
} else if (hasFeatureFace && shouldShowFaceEnroll) {
extras = getFaceChooseLockExtras();
} else if (hasFeatureFingerprint && shouldShowFingerprintEnroll) {
extras = getFingerprintChooseLockExtras(); extras = getFingerprintChooseLockExtras();
} else { } else {
extras = new Bundle(); extras = new Bundle();
} }
// No matter we show fingerprint options or not, we should tell the next activity which // No matter we show fingerprint options or not, we should tell the next activity which
// user is setting new password. // user is setting new password.
extras.putInt(Intent.EXTRA_USER_ID, mTargetUserId); extras.putInt(Intent.EXTRA_USER_ID, mTargetUserId);
mUi.launchChooseLock(extras); mUi.launchChooseLock(extras);
} }
private Bundle getBiometricChooseLockExtras() {
Bundle chooseLockExtras = new Bundle();
chooseLockExtras.putInt(ChooseLockGeneric.ChooseLockGenericFragment.MINIMUM_QUALITY_KEY,
PASSWORD_QUALITY_SOMETHING);
chooseLockExtras.putBoolean(
ChooseLockGeneric.ChooseLockGenericFragment.HIDE_DISABLED_PREFS, true);
chooseLockExtras.putBoolean(ChooseLockSettingsHelper.EXTRA_KEY_REQUEST_GK_PW_HANDLE, true);
chooseLockExtras.putBoolean(ChooseLockSettingsHelper.EXTRA_KEY_FOR_BIOMETRICS, true);
return chooseLockExtras;
}
private Bundle getFingerprintChooseLockExtras() { private Bundle getFingerprintChooseLockExtras() {
Bundle chooseLockExtras = new Bundle(); Bundle chooseLockExtras = new Bundle();
chooseLockExtras.putInt(ChooseLockGeneric.ChooseLockGenericFragment.MINIMUM_QUALITY_KEY, chooseLockExtras.putInt(ChooseLockGeneric.ChooseLockGenericFragment.MINIMUM_QUALITY_KEY,

View File

@@ -200,8 +200,9 @@ public class SetupChooseLockGeneric extends ChooseLockGeneric {
.getBooleanExtra(SetupSkipDialog.EXTRA_FRP_SUPPORTED, false), .getBooleanExtra(SetupSkipDialog.EXTRA_FRP_SUPPORTED, false),
/* isPatternMode= */ false, /* isPatternMode= */ false,
/* isAlphaMode= */ false, /* isAlphaMode= */ false,
/* isFingerprintSupported= */ false, /* forFingerprint= */ false,
/* isFaceSupported= */ false /* forFace= */ false,
/* forBiometrics= */ false
); );
dialog.show(getFragmentManager()); dialog.show(getFragmentManager());
return true; return true;
@@ -242,7 +243,7 @@ public class SetupChooseLockGeneric extends ChooseLockGeneric {
} }
private boolean isForBiometric() { private boolean isForBiometric() {
return mForFingerprint || mForFace; return mForFingerprint || mForFace || mForBiometrics;
} }
} }

View File

@@ -104,18 +104,23 @@ public class SetupChooseLockPassword extends ChooseLockPassword {
@Override @Override
protected void onSkipOrClearButtonClick(View view) { protected void onSkipOrClearButtonClick(View view) {
if (mLeftButtonIsSkip) { if (mLeftButtonIsSkip) {
SetupSkipDialog dialog = SetupSkipDialog.newInstance( final Intent intent = getActivity().getIntent();
getActivity().getIntent() final boolean frpSupported = intent
.getBooleanExtra(SetupSkipDialog.EXTRA_FRP_SUPPORTED, false), .getBooleanExtra(SetupSkipDialog.EXTRA_FRP_SUPPORTED, false);
final boolean forFingerprint = intent
.getBooleanExtra(ChooseLockSettingsHelper.EXTRA_KEY_FOR_FINGERPRINT, false);
final boolean forFace = intent
.getBooleanExtra(ChooseLockSettingsHelper.EXTRA_KEY_FOR_FACE, false);
final boolean forBiometrics = intent
.getBooleanExtra(ChooseLockSettingsHelper.EXTRA_KEY_FOR_BIOMETRICS, false);
final SetupSkipDialog dialog = SetupSkipDialog.newInstance(
frpSupported,
/* isPatternMode= */ false, /* isPatternMode= */ false,
mIsAlphaMode, mIsAlphaMode,
getActivity().getIntent() forFingerprint,
.getBooleanExtra(ChooseLockSettingsHelper.EXTRA_KEY_FOR_FINGERPRINT, forFace,
false), forBiometrics);
getActivity().getIntent()
.getBooleanExtra(ChooseLockSettingsHelper.EXTRA_KEY_FOR_FACE, false)
);
dialog.show(getFragmentManager()); dialog.show(getFragmentManager());
return; return;
} }

View File

@@ -90,18 +90,23 @@ public class SetupChooseLockPattern extends ChooseLockPattern {
@Override @Override
protected void onSkipOrClearButtonClick(View view) { protected void onSkipOrClearButtonClick(View view) {
if (mLeftButtonIsSkip) { if (mLeftButtonIsSkip) {
SetupSkipDialog dialog = SetupSkipDialog.newInstance( final Intent intent = getActivity().getIntent();
getActivity().getIntent() final boolean frpSupported = intent
.getBooleanExtra(SetupSkipDialog.EXTRA_FRP_SUPPORTED, false), .getBooleanExtra(SetupSkipDialog.EXTRA_FRP_SUPPORTED, false);
final boolean forFingerprint = intent
.getBooleanExtra(ChooseLockSettingsHelper.EXTRA_KEY_FOR_FINGERPRINT, false);
final boolean forFace = intent
.getBooleanExtra(ChooseLockSettingsHelper.EXTRA_KEY_FOR_FACE, false);
final boolean forBiometrics = intent
.getBooleanExtra(ChooseLockSettingsHelper.EXTRA_KEY_FOR_BIOMETRICS, false);
final SetupSkipDialog dialog = SetupSkipDialog.newInstance(
frpSupported,
/* isPatternMode= */ true, /* isPatternMode= */ true,
/* isAlphaMode= */ false, /* isAlphaMode= */ false,
getActivity().getIntent() forFingerprint,
.getBooleanExtra(ChooseLockSettingsHelper.EXTRA_KEY_FOR_FINGERPRINT, forFace,
false), forBiometrics);
getActivity().getIntent()
.getBooleanExtra(ChooseLockSettingsHelper.EXTRA_KEY_FOR_FACE, false)
);
dialog.show(getFragmentManager()); dialog.show(getFragmentManager());
return; return;
} }

View File

@@ -43,14 +43,16 @@ public class SetupSkipDialog extends InstrumentedDialogFragment
public static final int RESULT_SKIP = Activity.RESULT_FIRST_USER + 10; public static final int RESULT_SKIP = Activity.RESULT_FIRST_USER + 10;
public static SetupSkipDialog newInstance(boolean isFrpSupported, boolean isPatternMode, public static SetupSkipDialog newInstance(boolean isFrpSupported, boolean isPatternMode,
boolean isAlphanumericMode, boolean isFingerprintSupported, boolean isFaceSupported) { boolean isAlphanumericMode, boolean forFingerprint, boolean forFace,
boolean forBiometrics) {
SetupSkipDialog dialog = new SetupSkipDialog(); SetupSkipDialog dialog = new SetupSkipDialog();
Bundle args = new Bundle(); Bundle args = new Bundle();
args.putBoolean(ARG_FRP_SUPPORTED, isFrpSupported); args.putBoolean(ARG_FRP_SUPPORTED, isFrpSupported);
args.putBoolean(ARG_LOCK_TYPE_PATTERN, isPatternMode); args.putBoolean(ARG_LOCK_TYPE_PATTERN, isPatternMode);
args.putBoolean(ARG_LOCK_TYPE_ALPHANUMERIC, isAlphanumericMode); args.putBoolean(ARG_LOCK_TYPE_ALPHANUMERIC, isAlphanumericMode);
args.putBoolean(ChooseLockSettingsHelper.EXTRA_KEY_FOR_FINGERPRINT, isFingerprintSupported); args.putBoolean(ChooseLockSettingsHelper.EXTRA_KEY_FOR_FINGERPRINT, forFingerprint);
args.putBoolean(ChooseLockSettingsHelper.EXTRA_KEY_FOR_FACE, isFaceSupported); args.putBoolean(ChooseLockSettingsHelper.EXTRA_KEY_FOR_FACE, forFace);
args.putBoolean(ChooseLockSettingsHelper.EXTRA_KEY_FOR_BIOMETRICS, forBiometrics);
dialog.setArguments(args); dialog.setArguments(args);
return dialog; return dialog;
} }
@@ -68,11 +70,13 @@ public class SetupSkipDialog extends InstrumentedDialogFragment
@NonNull @NonNull
public AlertDialog.Builder onCreateDialogBuilder() { public AlertDialog.Builder onCreateDialogBuilder() {
Bundle args = getArguments(); Bundle args = getArguments();
final boolean isFaceSupported = final boolean forFace =
args.getBoolean(ChooseLockSettingsHelper.EXTRA_KEY_FOR_FACE); args.getBoolean(ChooseLockSettingsHelper.EXTRA_KEY_FOR_FACE);
final boolean isFingerprintSupported = final boolean forFingerprint =
args.getBoolean(ChooseLockSettingsHelper.EXTRA_KEY_FOR_FINGERPRINT); args.getBoolean(ChooseLockSettingsHelper.EXTRA_KEY_FOR_FINGERPRINT);
if (isFaceSupported || isFingerprintSupported) { final boolean forBiometrics =
args.getBoolean(ChooseLockSettingsHelper.EXTRA_KEY_FOR_BIOMETRICS);
if (forFace || forFingerprint || forBiometrics) {
final int titleId; final int titleId;
if (args.getBoolean(ARG_LOCK_TYPE_PATTERN)) { if (args.getBoolean(ARG_LOCK_TYPE_PATTERN)) {
@@ -82,13 +86,20 @@ public class SetupSkipDialog extends InstrumentedDialogFragment
R.string.lock_screen_password_skip_title : R.string.lock_screen_pin_skip_title; R.string.lock_screen_password_skip_title : R.string.lock_screen_pin_skip_title;
} }
final int msgResId;
if (forBiometrics) {
msgResId = R.string.biometrics_lock_screen_setup_skip_dialog_text;
} else if (forFace) {
msgResId = R.string.face_lock_screen_setup_skip_dialog_text;
} else {
msgResId = R.string.fingerprint_lock_screen_setup_skip_dialog_text;
}
return new AlertDialog.Builder(getContext()) return new AlertDialog.Builder(getContext())
.setPositiveButton(R.string.skip_lock_screen_dialog_button_label, this) .setPositiveButton(R.string.skip_lock_screen_dialog_button_label, this)
.setNegativeButton(R.string.cancel_lock_screen_dialog_button_label, this) .setNegativeButton(R.string.cancel_lock_screen_dialog_button_label, this)
.setTitle(titleId) .setTitle(titleId)
.setMessage(isFaceSupported ? .setMessage(msgResId);
R.string.face_lock_screen_setup_skip_dialog_text :
R.string.fingerprint_lock_screen_setup_skip_dialog_text);
} else { } else {
return new AlertDialog.Builder(getContext()) return new AlertDialog.Builder(getContext())
.setPositiveButton(R.string.skip_anyway_button_label, this) .setPositiveButton(R.string.skip_anyway_button_label, this)

View File

@@ -23,6 +23,8 @@ import static org.robolectric.RuntimeEnvironment.application;
import android.app.KeyguardManager; import android.app.KeyguardManager;
import android.content.Intent; import android.content.Intent;
import android.content.pm.PackageManager; import android.content.pm.PackageManager;
import android.hardware.fingerprint.FingerprintManager;
import android.hardware.fingerprint.FingerprintSensorProperties;
import android.view.View; import android.view.View;
import android.widget.Button; import android.widget.Button;
@@ -53,6 +55,8 @@ import org.robolectric.shadows.ShadowActivity;
import org.robolectric.shadows.ShadowActivity.IntentForResult; import org.robolectric.shadows.ShadowActivity.IntentForResult;
import org.robolectric.shadows.ShadowKeyguardManager; import org.robolectric.shadows.ShadowKeyguardManager;
import java.util.ArrayList;
@RunWith(RobolectricTestRunner.class) @RunWith(RobolectricTestRunner.class)
@Config(shadows = { @Config(shadows = {
ShadowFingerprintManager.class, ShadowFingerprintManager.class,
@@ -69,6 +73,14 @@ public class SetupFingerprintEnrollIntroductionTest {
Shadows.shadowOf(application.getPackageManager()) Shadows.shadowOf(application.getPackageManager())
.setSystemFeature(PackageManager.FEATURE_FINGERPRINT, true); .setSystemFeature(PackageManager.FEATURE_FINGERPRINT, true);
final FingerprintSensorProperties prop = new FingerprintSensorProperties(0 /* sensorId */,
FingerprintSensorProperties.TYPE_REAR,
true /* resetLockoutRequiresHardwareAuthToken */,
5 /* maxTemplatesAllowed */);
final ArrayList<FingerprintSensorProperties> props = new ArrayList<>();
props.add(prop);
ShadowFingerprintManager.setSensorProperties(props);
FakeFeatureFactory.setupForTest(); FakeFeatureFactory.setupForTest();
final Intent intent = new Intent(); final Intent intent = new Intent();

View File

@@ -46,7 +46,7 @@ public class SetupSkipDialogTest {
@Test @Test
public void frpMessages_areShownCorrectly_whenNotSupported() { public void frpMessages_areShownCorrectly_whenNotSupported() {
SetupSkipDialog setupSkipDialog = SetupSkipDialog setupSkipDialog =
SetupSkipDialog.newInstance(false, false, false, false, false); SetupSkipDialog.newInstance(false, false, false, false, false, false);
setupSkipDialog.show(mActivity.getSupportFragmentManager()); setupSkipDialog.show(mActivity.getSupportFragmentManager());
AlertDialog alertDialog = ShadowAlertDialogCompat.getLatestAlertDialog(); AlertDialog alertDialog = ShadowAlertDialogCompat.getLatestAlertDialog();
@@ -61,7 +61,7 @@ public class SetupSkipDialogTest {
@Test @Test
public void frpMessages_areShownCorrectly_whenSupported() { public void frpMessages_areShownCorrectly_whenSupported() {
SetupSkipDialog setupSkipDialog = SetupSkipDialog setupSkipDialog =
SetupSkipDialog.newInstance(true, false, false, false, false); SetupSkipDialog.newInstance(true, false, false, false, false, false);
setupSkipDialog.show(mActivity.getSupportFragmentManager()); setupSkipDialog.show(mActivity.getSupportFragmentManager());
AlertDialog alertDialog = ShadowAlertDialogCompat.getLatestAlertDialog(); AlertDialog alertDialog = ShadowAlertDialogCompat.getLatestAlertDialog();
@@ -76,7 +76,7 @@ public class SetupSkipDialogTest {
@Test @Test
public void dialogMessage_whenSkipPinSetupForFace_shouldShownCorrectly() { public void dialogMessage_whenSkipPinSetupForFace_shouldShownCorrectly() {
SetupSkipDialog setupSkipDialog = SetupSkipDialog setupSkipDialog =
SetupSkipDialog.newInstance(true, false, false, false, true); SetupSkipDialog.newInstance(true, false, false, false, true, false);
setupSkipDialog.show(mActivity.getSupportFragmentManager()); setupSkipDialog.show(mActivity.getSupportFragmentManager());
AlertDialog alertDialog = ShadowAlertDialogCompat.getLatestAlertDialog(); AlertDialog alertDialog = ShadowAlertDialogCompat.getLatestAlertDialog();
@@ -85,13 +85,14 @@ public class SetupSkipDialogTest {
assertThat(mActivity.getString(R.string.lock_screen_pin_skip_title)).isEqualTo( assertThat(mActivity.getString(R.string.lock_screen_pin_skip_title)).isEqualTo(
shadowAlertDialog.getTitle()); shadowAlertDialog.getTitle());
assertThat(getSkipDialogMessage(false)).isEqualTo(shadowAlertDialog.getMessage()); assertThat(getSkipDialogMessage(false, true, false))
.isEqualTo(shadowAlertDialog.getMessage());
} }
@Test @Test
public void dialogMessage_whenSkipPasswordSetupForFace_shouldShownCorrectly() { public void dialogMessage_whenSkipPasswordSetupForFace_shouldShownCorrectly() {
SetupSkipDialog setupSkipDialog = SetupSkipDialog setupSkipDialog =
SetupSkipDialog.newInstance(true, false, true, false, true); SetupSkipDialog.newInstance(true, false, true, false, true, false);
setupSkipDialog.show(mActivity.getSupportFragmentManager()); setupSkipDialog.show(mActivity.getSupportFragmentManager());
AlertDialog alertDialog = ShadowAlertDialogCompat.getLatestAlertDialog(); AlertDialog alertDialog = ShadowAlertDialogCompat.getLatestAlertDialog();
@@ -100,13 +101,14 @@ public class SetupSkipDialogTest {
assertThat(mActivity.getString(R.string.lock_screen_password_skip_title)).isEqualTo( assertThat(mActivity.getString(R.string.lock_screen_password_skip_title)).isEqualTo(
shadowAlertDialog.getTitle()); shadowAlertDialog.getTitle());
assertThat(getSkipDialogMessage(false)).isEqualTo(shadowAlertDialog.getMessage()); assertThat(getSkipDialogMessage(false, true, false))
.isEqualTo(shadowAlertDialog.getMessage());
} }
@Test @Test
public void dialogMessage_whenSkipPatternSetupForFace_shouldShownCorrectly() { public void dialogMessage_whenSkipPatternSetupForFace_shouldShownCorrectly() {
SetupSkipDialog setupSkipDialog = SetupSkipDialog setupSkipDialog =
SetupSkipDialog.newInstance(true, true, false, false, true); SetupSkipDialog.newInstance(true, true, false, false, true, false);
setupSkipDialog.show(mActivity.getSupportFragmentManager()); setupSkipDialog.show(mActivity.getSupportFragmentManager());
AlertDialog alertDialog = ShadowAlertDialogCompat.getLatestAlertDialog(); AlertDialog alertDialog = ShadowAlertDialogCompat.getLatestAlertDialog();
@@ -115,13 +117,14 @@ public class SetupSkipDialogTest {
assertThat(mActivity.getString(R.string.lock_screen_pattern_skip_title)).isEqualTo( assertThat(mActivity.getString(R.string.lock_screen_pattern_skip_title)).isEqualTo(
shadowAlertDialog.getTitle()); shadowAlertDialog.getTitle());
assertThat(getSkipDialogMessage(false)).isEqualTo(shadowAlertDialog.getMessage()); assertThat(getSkipDialogMessage(false, true, false))
.isEqualTo(shadowAlertDialog.getMessage());
} }
@Test @Test
public void dialogMessage_whenSkipPinSetupForFingerprint_shouldShownCorrectly() { public void dialogMessage_whenSkipPinSetupForFingerprint_shouldShownCorrectly() {
SetupSkipDialog setupSkipDialog = SetupSkipDialog setupSkipDialog =
SetupSkipDialog.newInstance(true, false, false, true, false); SetupSkipDialog.newInstance(true, false, false, true, false, false);
setupSkipDialog.show(mActivity.getSupportFragmentManager()); setupSkipDialog.show(mActivity.getSupportFragmentManager());
AlertDialog alertDialog = ShadowAlertDialogCompat.getLatestAlertDialog(); AlertDialog alertDialog = ShadowAlertDialogCompat.getLatestAlertDialog();
@@ -130,13 +133,14 @@ public class SetupSkipDialogTest {
assertThat(mActivity.getString(R.string.lock_screen_pin_skip_title)).isEqualTo( assertThat(mActivity.getString(R.string.lock_screen_pin_skip_title)).isEqualTo(
shadowAlertDialog.getTitle()); shadowAlertDialog.getTitle());
assertThat(getSkipDialogMessage(true)).isEqualTo(shadowAlertDialog.getMessage()); assertThat(getSkipDialogMessage(true, false, false))
.isEqualTo(shadowAlertDialog.getMessage());
} }
@Test @Test
public void dialogMessage_whenSkipPasswordSetupForFingerprint_shouldShownCorrectly() { public void dialogMessage_whenSkipPasswordSetupForFingerprint_shouldShownCorrectly() {
SetupSkipDialog setupSkipDialog = SetupSkipDialog setupSkipDialog =
SetupSkipDialog.newInstance(true, false, true, true, false); SetupSkipDialog.newInstance(true, false, true, true, false, false);
setupSkipDialog.show(mActivity.getSupportFragmentManager()); setupSkipDialog.show(mActivity.getSupportFragmentManager());
AlertDialog alertDialog = ShadowAlertDialogCompat.getLatestAlertDialog(); AlertDialog alertDialog = ShadowAlertDialogCompat.getLatestAlertDialog();
@@ -145,13 +149,14 @@ public class SetupSkipDialogTest {
assertThat(mActivity.getString(R.string.lock_screen_password_skip_title)).isEqualTo( assertThat(mActivity.getString(R.string.lock_screen_password_skip_title)).isEqualTo(
shadowAlertDialog.getTitle()); shadowAlertDialog.getTitle());
assertThat(getSkipDialogMessage(true)).isEqualTo(shadowAlertDialog.getMessage()); assertThat(getSkipDialogMessage(true, false, false))
.isEqualTo(shadowAlertDialog.getMessage());
} }
@Test @Test
public void dialogMessage_whenSkipPatternSetupForFingerprint_shouldShownCorrectly() { public void dialogMessage_whenSkipPatternSetupForFingerprint_shouldShownCorrectly() {
SetupSkipDialog setupSkipDialog = SetupSkipDialog setupSkipDialog =
SetupSkipDialog.newInstance(true, true, false, true, false); SetupSkipDialog.newInstance(true, true, false, true, false, false);
setupSkipDialog.show(mActivity.getSupportFragmentManager()); setupSkipDialog.show(mActivity.getSupportFragmentManager());
AlertDialog alertDialog = ShadowAlertDialogCompat.getLatestAlertDialog(); AlertDialog alertDialog = ShadowAlertDialogCompat.getLatestAlertDialog();
@@ -160,13 +165,71 @@ public class SetupSkipDialogTest {
assertThat(mActivity.getString(R.string.lock_screen_pattern_skip_title)).isEqualTo( assertThat(mActivity.getString(R.string.lock_screen_pattern_skip_title)).isEqualTo(
shadowAlertDialog.getTitle()); shadowAlertDialog.getTitle());
assertThat(getSkipDialogMessage(true)).isEqualTo(shadowAlertDialog.getMessage()); assertThat(getSkipDialogMessage(true, false, false))
.isEqualTo(shadowAlertDialog.getMessage());
} }
public String getSkipDialogMessage(boolean isFingerprintSupported) {
return String.format( @Test
mActivity.getString(isFingerprintSupported ? public void dialogMessage_whenSkipPinSetupForBiometrics_shouldShownCorrectly() {
R.string.fingerprint_lock_screen_setup_skip_dialog_text : SetupSkipDialog setupSkipDialog =
R.string.face_lock_screen_setup_skip_dialog_text)); SetupSkipDialog.newInstance(true, false, false, false, false, true);
setupSkipDialog.show(mActivity.getSupportFragmentManager());
AlertDialog alertDialog = ShadowAlertDialogCompat.getLatestAlertDialog();
assertThat(alertDialog).isNotNull();
ShadowAlertDialogCompat shadowAlertDialog = ShadowAlertDialogCompat.shadowOf(alertDialog);
assertThat(mActivity.getString(R.string.lock_screen_pin_skip_title)).isEqualTo(
shadowAlertDialog.getTitle());
assertThat(getSkipDialogMessage(false, false, true))
.isEqualTo(shadowAlertDialog.getMessage());
}
@Test
public void dialogMessage_whenSkipPasswordSetupForBiometrics_shouldShownCorrectly() {
SetupSkipDialog setupSkipDialog =
SetupSkipDialog.newInstance(true, false, true, false, false, true);
setupSkipDialog.show(mActivity.getSupportFragmentManager());
AlertDialog alertDialog = ShadowAlertDialogCompat.getLatestAlertDialog();
assertThat(alertDialog).isNotNull();
ShadowAlertDialogCompat shadowAlertDialog = ShadowAlertDialogCompat.shadowOf(alertDialog);
assertThat(mActivity.getString(R.string.lock_screen_password_skip_title)).isEqualTo(
shadowAlertDialog.getTitle());
assertThat(getSkipDialogMessage(false, false, true))
.isEqualTo(shadowAlertDialog.getMessage());
}
@Test
public void dialogMessage_whenSkipPatternSetupForBiometrics_shouldShownCorrectly() {
SetupSkipDialog setupSkipDialog =
SetupSkipDialog.newInstance(true, true, false, false, false, true);
setupSkipDialog.show(mActivity.getSupportFragmentManager());
AlertDialog alertDialog = ShadowAlertDialogCompat.getLatestAlertDialog();
assertThat(alertDialog).isNotNull();
ShadowAlertDialogCompat shadowAlertDialog = ShadowAlertDialogCompat.shadowOf(alertDialog);
assertThat(mActivity.getString(R.string.lock_screen_pattern_skip_title)).isEqualTo(
shadowAlertDialog.getTitle());
assertThat(getSkipDialogMessage(false, false, true))
.isEqualTo(shadowAlertDialog.getMessage());
}
public String getSkipDialogMessage(boolean forFingerprint, boolean forFace,
boolean forBiometrics) {
final int resId;
if (forFingerprint) {
resId = R.string.fingerprint_lock_screen_setup_skip_dialog_text;
} else if (forFace) {
resId = R.string.face_lock_screen_setup_skip_dialog_text;
} else if (forBiometrics) {
resId = R.string.biometrics_lock_screen_setup_skip_dialog_text;
} else {
return null;
}
return String.format(mActivity.getString(resId));
} }
} }

View File

@@ -18,22 +18,30 @@ package com.android.settings.testutils.shadow;
import android.hardware.fingerprint.Fingerprint; import android.hardware.fingerprint.Fingerprint;
import android.hardware.fingerprint.FingerprintManager; import android.hardware.fingerprint.FingerprintManager;
import android.hardware.fingerprint.FingerprintSensorProperties;
import org.robolectric.annotation.Implementation; import org.robolectric.annotation.Implementation;
import org.robolectric.annotation.Implements; import org.robolectric.annotation.Implements;
import java.util.ArrayList;
import java.util.List; import java.util.List;
@Implements(FingerprintManager.class) @Implements(FingerprintManager.class)
public class ShadowFingerprintManager extends org.robolectric.shadows.ShadowFingerprintManager { public class ShadowFingerprintManager extends org.robolectric.shadows.ShadowFingerprintManager {
private static List<FingerprintSensorProperties> sFingerprintSensorProperties;
public static void setSensorProperties(List<FingerprintSensorProperties> props) {
sFingerprintSensorProperties = props;
}
@Implementation @Implementation
protected List<Fingerprint> getEnrolledFingerprints(int userId) { protected List<Fingerprint> getEnrolledFingerprints(int userId) {
return getEnrolledFingerprints(); return getEnrolledFingerprints();
} }
@Implementation @Implementation
protected long generateChallengeBlocking() { protected List<FingerprintSensorProperties> getSensorProperties() {
return 0L; return sFingerprintSensorProperties;
} }
} }