Snap for 9475114 from 22fc2f387b to udc-release
Change-Id: I080ca2e0f74fafd3e965dab4a5256d1d902ffd54
This commit is contained in:
@@ -993,6 +993,22 @@
|
||||
android:value="true"/>
|
||||
</activity>
|
||||
|
||||
<activity
|
||||
android:name=".Settings$RegionalPreferencesActivity"
|
||||
android:label="@string/regional_preferences_title"
|
||||
android:exported="true" >
|
||||
<intent-filter android:priority="1">
|
||||
<action android:name="android.settings.REGIONAL_PREFERENCES_SETTINGS" />
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
</intent-filter>
|
||||
<meta-data android:name="com.android.settings.FRAGMENT_CLASS"
|
||||
android:value="com.android.settings.regionalpreferences.RegionalPreferencesEntriesFragment"/>
|
||||
<meta-data android:name="com.android.settings.HIGHLIGHT_MENU_KEY"
|
||||
android:value="@string/menu_key_system"/>
|
||||
<meta-data android:name="com.android.settings.PRIMARY_PROFILE_CONTROLLED"
|
||||
android:value="true"/>
|
||||
</activity>
|
||||
|
||||
<activity
|
||||
android:name=".Settings$KeyboardSettingsActivity"
|
||||
android:label="@string/keyboard_settings"
|
||||
@@ -3971,7 +3987,7 @@
|
||||
android:theme="@style/Theme.AlertDialog"
|
||||
android:taskAffinity="com.android.settings.appops"
|
||||
android:excludeFromRecents="true"
|
||||
android:exported="false"
|
||||
android:exported="true"
|
||||
android:launchMode="singleTop">
|
||||
<intent-filter android:priority="1">
|
||||
<action android:name="android.settings.SHOW_RESTRICTED_SETTING_DIALOG" />
|
||||
|
||||
39
res/drawable/ic_enrollment_fingerprint.xml
Normal file
39
res/drawable/ic_enrollment_fingerprint.xml
Normal file
@@ -0,0 +1,39 @@
|
||||
<!--
|
||||
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
|
||||
-->
|
||||
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="72dp"
|
||||
android:height="72dp"
|
||||
android:tint="@*android:color/primary_text_material_dark"
|
||||
android:viewportWidth="72"
|
||||
android:viewportHeight="72">
|
||||
<path
|
||||
android:pathData=
|
||||
"M25.5,16.3283C28.47,14.8433 31.9167,14 35.5834,14C39.2501,14 42.6968,
|
||||
14.8433 45.6668,16.3283
|
||||
M20,28.6669C22.7683,24.3402 28.7084,21.3335 35.5834,21.3335C42.4585,
|
||||
21.3335 48.3985,24.3402 51.1669,28.6669
|
||||
M22.8607,47.0002C21.834,44.3235 21.834,41.5002 21.834,41.5002C21.834,
|
||||
34.4051 27.7374,28.6667 35.5841,28.6667C43.4308,28.6667 49.3341,34.4051 49.3341,41.5002
|
||||
M49.3344,41.5003V42.0319C49.3344,44.7636 47.1161,47.0003 44.3661,47.0003C41.9461,
|
||||
47.0003 39.8744,45.2403 39.471,42.857L38.9577,39.7769C38.591,37.5953 36.7027,
|
||||
36.0002 34.5027,36.0002C26.5826,36.0002 29.846,49.1087 35.291,50.6487
|
||||
M44.9713,54.6267C42.5513,56.7167 39.2879,58.0001 35.5846,58.0001C32.2296,
|
||||
58.0001 29.2229,56.9551 26.8945,55.195"
|
||||
android:strokeWidth="3"
|
||||
android:strokeColor="#ffffff"
|
||||
android:strokeLineCap="round"/>
|
||||
</vector>
|
||||
35
res/drawable/udfps_enroll_checkmark.xml
Normal file
35
res/drawable/udfps_enroll_checkmark.xml
Normal file
@@ -0,0 +1,35 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
~ Copyright (C) 2021 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.
|
||||
-->
|
||||
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="54dp"
|
||||
android:height="54dp"
|
||||
android:viewportWidth="54"
|
||||
android:viewportHeight="54">
|
||||
<path
|
||||
android:pathData="M26.9999,3.9619C39.7029,3.9619 50.0369,14.2969 50.0369,26.9999C50.0369,39.7029 39.7029,50.0379 26.9999,50.0379C14.2969,50.0379 3.9629,39.7029 3.9629,26.9999C3.9629,14.2969 14.2969,3.9619 26.9999,3.9619Z"
|
||||
android:fillColor="?android:colorBackground"
|
||||
android:fillType="evenOdd"/>
|
||||
<path
|
||||
android:pathData="M27,0C12.088,0 0,12.088 0,27C0,41.912 12.088,54 27,54C41.912,54 54,41.912 54,27C54,12.088 41.912,0 27,0ZM27,3.962C39.703,3.962 50.037,14.297 50.037,27C50.037,39.703 39.703,50.038 27,50.038C14.297,50.038 3.963,39.703 3.963,27C3.963,14.297 14.297,3.962 27,3.962Z"
|
||||
android:fillColor="@color/udfps_enroll_progress"
|
||||
android:fillType="evenOdd"/>
|
||||
<path
|
||||
android:pathData="M23.0899,38.8534L10.4199,26.1824L13.2479,23.3544L23.0899,33.1974L41.2389,15.0474L44.0679,17.8754L23.0899,38.8534Z"
|
||||
android:fillColor="@color/udfps_enroll_progress"
|
||||
android:fillType="evenOdd"/>
|
||||
</vector>
|
||||
@@ -39,6 +39,7 @@
|
||||
android:orientation="vertical">
|
||||
|
||||
<FrameLayout
|
||||
android:id="@+id/layout_container"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:clipChildren="false"
|
||||
|
||||
42
res/layout/udfps_enroll_view.xml
Normal file
42
res/layout/udfps_enroll_view.xml
Normal file
@@ -0,0 +1,42 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
~ Copyright (C) 2021 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.android.settings.biometrics.fingerprint.UdfpsEnrollView
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:id="@+id/udfps_animation_view"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<!-- The layout height/width are placeholders, which will be overwritten by
|
||||
FingerprintSensorPropertiesInternal. -->
|
||||
<View
|
||||
android:id="@+id/udfps_enroll_accessibility_view"
|
||||
android:layout_gravity="center"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:contentDescription="@string/accessibility_fingerprint_label"/>
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/udfps_enroll_animation_fp_progress_view"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"/>
|
||||
|
||||
<!-- Fingerprint -->
|
||||
<ImageView
|
||||
android:id="@+id/udfps_enroll_animation_fp_view"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"/>
|
||||
</com.android.settings.biometrics.fingerprint.UdfpsEnrollView>
|
||||
@@ -57,5 +57,15 @@
|
||||
|
||||
<!-- Icon tint color for battery usage system icon -->
|
||||
<color name="battery_usage_system_icon_color">@android:color/white</color>
|
||||
|
||||
|
||||
<!-- UDFPS colors -->
|
||||
<color name="udfps_enroll_icon">#7DA7F1</color>
|
||||
<color name="udfps_moving_target_fill">#475670</color>
|
||||
<!-- 50% of udfps_moving_target_fill-->
|
||||
<color name="udfps_moving_target_fill_error">#80475670</color>
|
||||
<color name="udfps_enroll_progress">#7DA7F1</color>
|
||||
<color name="udfps_enroll_progress_help">#607DA7F1</color>
|
||||
<color name="udfps_enroll_progress_help_with_talkback">#FFEE675C</color>
|
||||
</resources>
|
||||
|
||||
|
||||
@@ -184,4 +184,14 @@
|
||||
<attr name="ic_menu_moreoverflow" format="reference" />
|
||||
<attr name="side_margin" format="reference|dimension" />
|
||||
<attr name="wifi_signal_color" format="reference" />
|
||||
|
||||
<declare-styleable name="BiometricsEnrollView">
|
||||
<attr name="biometricsEnrollStyle" format="reference" />
|
||||
<attr name="biometricsEnrollIcon" format="reference|color" />
|
||||
<attr name="biometricsMovingTargetFill" format="reference|color" />
|
||||
<attr name="biometricsMovingTargetFillError" format="reference|color" />
|
||||
<attr name="biometricsEnrollProgress" format="reference|color" />
|
||||
<attr name="biometricsEnrollProgressHelp" format="reference|color" />
|
||||
<attr name="biometricsEnrollProgressHelpWithTalkback" format="reference|color" />
|
||||
</declare-styleable>
|
||||
</resources>
|
||||
|
||||
@@ -172,4 +172,13 @@
|
||||
|
||||
<!-- Icon tint color for battery usage system icon -->
|
||||
<color name="battery_usage_system_icon_color">?android:attr/textColorPrimary</color>
|
||||
|
||||
<!-- UDFPS colors -->
|
||||
<color name="udfps_enroll_icon">#699FF3</color>
|
||||
<color name="udfps_moving_target_fill">#C2D7F7</color>
|
||||
<!-- 50% of udfps_moving_target_fill-->
|
||||
<color name="udfps_moving_target_fill_error">#80C2D7F7</color>
|
||||
<color name="udfps_enroll_progress">#699FF3</color>
|
||||
<color name="udfps_enroll_progress_help">#70699FF3</color>
|
||||
<color name="udfps_enroll_progress_help_with_talkback">#FFEE675C</color>
|
||||
</resources>
|
||||
|
||||
@@ -578,13 +578,7 @@
|
||||
<!-- The option list for switch screen resolution -->
|
||||
<string-array name="config_screen_resolution_options_strings" translatable="false">
|
||||
<item>@string/screen_resolution_option_high</item>
|
||||
<item>@string/screen_resolution_option_highest</item>
|
||||
</string-array>
|
||||
|
||||
<!-- The option summary list for screen resolution -->
|
||||
<string-array name="config_screen_resolution_summaries_strings" translatable="false">
|
||||
<item>@string/screen_resolution_summary_high</item>
|
||||
<item>@string/screen_resolution_summary_highest</item>
|
||||
<item>@string/screen_resolution_option_full</item>
|
||||
</string-array>
|
||||
|
||||
<!-- Whether to aggregate for network selection list-->
|
||||
@@ -649,4 +643,25 @@
|
||||
|
||||
<!-- Whether the toggle for Auto-rotate with Face Detection should be shown. -->
|
||||
<bool name="config_auto_rotate_face_detection_available">true</bool>
|
||||
|
||||
<!-- The radius of the enrollment progress bar, in dp -->
|
||||
<integer name="config_udfpsEnrollProgressBar" translatable="false">
|
||||
280
|
||||
</integer>
|
||||
|
||||
<!-- Default udfps icon. Same path as ic_fingerprint.xml -->
|
||||
<string name="config_udfpsIcon" translatable="false">
|
||||
M25.5,16.3283C28.47,14.8433 31.9167,14 35.5834,14C39.2501,14 42.6968,14.8433 45.6668,16.3283
|
||||
M20,28.6669C22.7683,24.3402 28.7084,21.3335 35.5834,21.3335C42.4585,21.3335 48.3985,
|
||||
24.3402 51.1669,28.6669
|
||||
M22.8607,47.0002C21.834,44.3235 21.834,41.5002 21.834,41.5002C21.834,
|
||||
34.4051 27.7374,28.6667 35.5841,28.6667C43.4308,28.6667 49.3341,34.4051 49.3341,41.5002
|
||||
M49.3344,41.5003V42.0319C49.3344,44.7636 47.1161,47.0003 44.3661,47.0003C41.9461,
|
||||
47.0003 39.8744,45.2403 39.471,42.857L38.9577,
|
||||
39.7769C38.591,37.5953 36.7027,36.0002 34.5027,
|
||||
36.0002C26.5826,36.0002 29.846,49.1087 35.291,50.6487
|
||||
M44.9713,54.6267C42.5513,56.7167 39.2879,58.0001 35.5846,58.0001C32.2296,
|
||||
58.0001 29.2229,56.9551 26.8945,55.195
|
||||
</string>
|
||||
|
||||
</resources>
|
||||
|
||||
@@ -2116,14 +2116,10 @@
|
||||
|
||||
<!-- Display settings screen, screen resolution settings title [CHAR LIMIT=30] -->
|
||||
<string name="screen_resolution_title">Screen resolution</string>
|
||||
<!-- Display settings screen, screen resolution option for "FHD+" [CHAR LIMIT=45] -->
|
||||
<!-- Display settings screen, screen resolution option for high resolution [CHAR LIMIT=45] -->
|
||||
<string name="screen_resolution_option_high">High resolution</string>
|
||||
<!-- Display settings screen, screen resolution option for "QHD+" [CHAR LIMIT=45] -->
|
||||
<string name="screen_resolution_option_highest">Full resolution</string>
|
||||
<!-- Display settings screen, "FHD+" screen resolution summary [CHAR LIMIT=NONE] -->
|
||||
<string name="screen_resolution_summary_high">1080p FHD+</string>
|
||||
<!-- Display settings screen, "QHD+" screen resolution summary [CHAR LIMIT=NONE] -->
|
||||
<string name="screen_resolution_summary_highest">1440p QHD+</string>
|
||||
<!-- Display settings screen, screen resolution option for full resolution [CHAR LIMIT=45] -->
|
||||
<string name="screen_resolution_option_full">Full resolution</string>
|
||||
<!-- The footer message for switch screen resolution [CHAR LIMIT=NONE] -->
|
||||
<string name="screen_resolution_footer">Full resolution uses more of your battery. Switching your resolution may cause some apps to restart.</string>
|
||||
<!-- Message announced to a11y users when they selected one resolution [CHAR LIMIT=NONE] -->
|
||||
@@ -2452,7 +2448,9 @@
|
||||
<!-- About phone, title of EID for multi-sim devices -->
|
||||
<string name="eid_multi_sim">EID (sim slot <xliff:g id="eid_slot_id">%1$d</xliff:g>)</string>
|
||||
<!-- About phone screen, title for IMEI for multi-sim devices -->
|
||||
<string name="imei_multi_sim">IMEI (sim slot %1$d)</string>
|
||||
<string name="imei_multi_sim">IMEI (sim slot <xliff:g id="imei_slot_id">%1$d</xliff:g>)</string>
|
||||
<!-- About phone screen, title for primary IMEI for multi-sim devices -->
|
||||
<string name="imei_multi_sim_primary">IMEI (sim slot <xliff:g id="imei_slot_id_primary">%1$d</xliff:g>) (primary)</string>
|
||||
<!-- About phone screen, summary of the MAC address [CHAR LIMIT=80] -->
|
||||
<string name="view_saved_network">To view, choose saved network</string>
|
||||
<!-- Do not translate. About phone, status item title -->
|
||||
@@ -2468,7 +2466,9 @@
|
||||
<!-- About phone, status item title. The phone PRL Version of the current device.-->
|
||||
<string name="status_prl_version">PRL version</string>
|
||||
<!-- About phone screen, title for MEID for multi-sim devices -->
|
||||
<string name="meid_multi_sim">MEID (sim slot %1$d)</string>
|
||||
<string name="meid_multi_sim">MEID (sim slot <xliff:g id="meid_slot_id">%1$d</xliff:g>)</string>
|
||||
<!-- About phone screen, title for primary MEID for multi-sim devices -->
|
||||
<string name="meid_multi_sim_primary">MEID (sim slot <xliff:g id="meid_slot_id_primary">%1$d</xliff:g>) (primary)</string>
|
||||
<!-- About phone, status item title. The phone MEID number of the current LTE/CDMA device. [CHAR LIMIT=30] -->
|
||||
<string name="status_meid_number">MEID</string>
|
||||
<!-- About phone, status item title. The ICCID of the current LTE device. [CHAR LIMIT=30] -->
|
||||
@@ -10786,6 +10786,9 @@
|
||||
<!-- Subtext of system default app for current setting [CHAR LIMIT=NONE] -->
|
||||
<string name="system_default_app_subtext">System default</string>
|
||||
|
||||
<!-- Summary of a default app when there is no app set [CHAR LIMIT=60] -->
|
||||
<string name="default_app_none">None</string>
|
||||
|
||||
<!-- Developer settings: text for the bug report handler selection toast shown if an invalid bug report handler was chosen. [CHAR LIMIT=NONE] -->
|
||||
<string name="select_invalid_bug_report_handler_toast_text">This choice is no longer valid. Try again.</string>
|
||||
|
||||
@@ -11564,4 +11567,7 @@
|
||||
=1 {Apps installed more than # month ago}
|
||||
other {Apps installed more than # months ago}
|
||||
}</string>
|
||||
|
||||
<!-- Accessibility label for fingerprint sensor [CHAR LIMIT=NONE] -->
|
||||
<string name="accessibility_fingerprint_label">Fingerprint sensor</string>
|
||||
</resources>
|
||||
|
||||
@@ -925,4 +925,13 @@
|
||||
<item name="android:textAllCaps">false</item>
|
||||
<item name="android:singleLine">true</item>
|
||||
</style>
|
||||
|
||||
<style name="BiometricsEnrollStyle">
|
||||
<item name="biometricsEnrollIcon">@color/udfps_enroll_icon</item>
|
||||
<item name="biometricsMovingTargetFill">@color/udfps_moving_target_fill</item>
|
||||
<item name="biometricsMovingTargetFillError">@color/udfps_moving_target_fill_error</item>
|
||||
<item name="biometricsEnrollProgress">@color/udfps_enroll_progress</item>
|
||||
<item name="biometricsEnrollProgressHelp">@color/udfps_enroll_progress_help</item>
|
||||
<item name="biometricsEnrollProgressHelpWithTalkback">@color/udfps_enroll_progress_help_with_talkback</item>
|
||||
</style>
|
||||
</resources>
|
||||
|
||||
@@ -114,6 +114,8 @@ public class Settings extends SettingsActivity {
|
||||
public static class LocalePickerActivity extends SettingsActivity { /* empty */ }
|
||||
public static class LanguageAndInputSettingsActivity extends SettingsActivity { /* empty */ }
|
||||
public static class LanguageSettingsActivity extends SettingsActivity { /* empty */ }
|
||||
/** Activity for the regional preferences settings. */
|
||||
public static class RegionalPreferencesActivity extends SettingsActivity { /* empty */ }
|
||||
public static class KeyboardSettingsActivity extends SettingsActivity { /* empty */ }
|
||||
public static class UserDictionarySettingsActivity extends SettingsActivity { /* empty */ }
|
||||
public static class DarkThemeSettingsActivity extends SettingsActivity { /* empty */ }
|
||||
|
||||
@@ -40,6 +40,11 @@ public abstract class BiometricEnrollSidecar extends InstrumentedFragment {
|
||||
void onEnrollmentHelp(int helpMsgId, CharSequence helpString);
|
||||
void onEnrollmentError(int errMsgId, CharSequence errString);
|
||||
void onEnrollmentProgressChange(int steps, int remaining);
|
||||
/**
|
||||
* Called when a fingerprint image has been acquired.
|
||||
* @param isAcquiredGood whether the fingerprint image was good.
|
||||
*/
|
||||
default void onAcquired(boolean isAcquiredGood) { }
|
||||
}
|
||||
|
||||
private int mEnrollmentSteps = -1;
|
||||
@@ -100,6 +105,19 @@ public abstract class BiometricEnrollSidecar extends InstrumentedFragment {
|
||||
}
|
||||
}
|
||||
|
||||
private class QueuedAcquired extends QueuedEvent {
|
||||
private final boolean isAcquiredGood;
|
||||
|
||||
public QueuedAcquired(boolean isAcquiredGood) {
|
||||
this.isAcquiredGood = isAcquiredGood;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void send(Listener listener) {
|
||||
listener.onAcquired(isAcquiredGood);
|
||||
}
|
||||
}
|
||||
|
||||
private final Runnable mTimeoutRunnable = new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
@@ -189,6 +207,14 @@ public abstract class BiometricEnrollSidecar extends InstrumentedFragment {
|
||||
mEnrolling = false;
|
||||
}
|
||||
|
||||
protected void onAcquired(boolean isAcquiredGood) {
|
||||
if (mListener != null) {
|
||||
mListener.onAcquired(isAcquiredGood);
|
||||
} else {
|
||||
mQueuedEvents.add(new QueuedAcquired(isAcquiredGood));
|
||||
}
|
||||
}
|
||||
|
||||
public void setListener(Listener listener) {
|
||||
mListener = listener;
|
||||
if (mListener != null) {
|
||||
|
||||
@@ -34,11 +34,13 @@ import android.content.res.Configuration;
|
||||
import android.content.res.Resources;
|
||||
import android.graphics.PorterDuff;
|
||||
import android.graphics.PorterDuffColorFilter;
|
||||
import android.graphics.Rect;
|
||||
import android.graphics.drawable.Animatable2;
|
||||
import android.graphics.drawable.AnimatedVectorDrawable;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.graphics.drawable.LayerDrawable;
|
||||
import android.hardware.fingerprint.FingerprintManager;
|
||||
import android.hardware.fingerprint.FingerprintSensorProperties;
|
||||
import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
|
||||
import android.os.Bundle;
|
||||
import android.os.Process;
|
||||
@@ -46,15 +48,21 @@ import android.os.VibrationAttributes;
|
||||
import android.os.VibrationEffect;
|
||||
import android.os.Vibrator;
|
||||
import android.text.TextUtils;
|
||||
import android.util.DisplayUtils;
|
||||
import android.util.FeatureFlagUtils;
|
||||
import android.util.Log;
|
||||
import android.view.Display;
|
||||
import android.view.DisplayInfo;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.OrientationEventListener;
|
||||
import android.view.Surface;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.accessibility.AccessibilityEvent;
|
||||
import android.view.accessibility.AccessibilityManager;
|
||||
import android.view.animation.AnimationUtils;
|
||||
import android.view.animation.Interpolator;
|
||||
import android.widget.FrameLayout;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.ProgressBar;
|
||||
import android.widget.TextView;
|
||||
@@ -157,6 +165,9 @@ public class FingerprintEnrollEnrolling extends BiometricsEnrollEnrolling {
|
||||
private boolean mCanAssumeUdfps;
|
||||
private boolean mCanAssumeSfps;
|
||||
@Nullable private ProgressBar mProgressBar;
|
||||
@Nullable private UdfpsEnrollHelper mUdfpsEnrollHelper;
|
||||
// TODO(b/260617060): Do not hard-code mScaleFactor, referring to AuthController.
|
||||
private float mScaleFactor = 1.0f;
|
||||
private ObjectAnimator mProgressAnim;
|
||||
private TextView mErrorText;
|
||||
private Interpolator mFastOutSlowInInterpolator;
|
||||
@@ -245,7 +256,8 @@ public class FingerprintEnrollEnrolling extends BiometricsEnrollEnrolling {
|
||||
listenOrientationEvent();
|
||||
|
||||
if (mCanAssumeUdfps) {
|
||||
switch(getApplicationContext().getDisplay().getRotation()) {
|
||||
int rotation = getApplicationContext().getDisplay().getRotation();
|
||||
switch (rotation) {
|
||||
case Surface.ROTATION_90:
|
||||
final GlifLayout layout = (GlifLayout) getLayoutInflater().inflate(
|
||||
R.layout.udfps_enroll_enrolling, null, false);
|
||||
@@ -260,8 +272,12 @@ public class FingerprintEnrollEnrolling extends BiometricsEnrollEnrolling {
|
||||
layoutContainer.setPaddingRelative((int) getResources().getDimension(
|
||||
R.dimen.rotation_90_enroll_padding_start), 0, isLayoutRtl
|
||||
? 0 : (int) getResources().getDimension(
|
||||
R.dimen.rotation_90_enroll_padding_end), 0);
|
||||
R.dimen.rotation_90_enroll_padding_end), 0);
|
||||
layoutContainer.setLayoutParams(lp);
|
||||
if (FeatureFlagUtils.isEnabled(getApplicationContext(),
|
||||
FeatureFlagUtils.SETTINGS_SHOW_UDFPS_ENROLL_IN_SETTINGS)) {
|
||||
layout.addView(addUdfpsEnrollView(props.get(0)));
|
||||
}
|
||||
setContentView(layout, lp);
|
||||
break;
|
||||
|
||||
@@ -269,7 +285,31 @@ public class FingerprintEnrollEnrolling extends BiometricsEnrollEnrolling {
|
||||
case Surface.ROTATION_180:
|
||||
case Surface.ROTATION_270:
|
||||
default:
|
||||
setContentView(R.layout.udfps_enroll_enrolling);
|
||||
final GlifLayout defaultLayout = (GlifLayout) getLayoutInflater().inflate(
|
||||
R.layout.udfps_enroll_enrolling, null, false);
|
||||
if (FeatureFlagUtils.isEnabled(getApplicationContext(),
|
||||
FeatureFlagUtils.SETTINGS_SHOW_UDFPS_ENROLL_IN_SETTINGS)) {
|
||||
if (rotation == Surface.ROTATION_0 || rotation == Surface.ROTATION_180) {
|
||||
// In the portrait mode, set layout_container's height 0, so it's
|
||||
// always shown at the bottom of the screen.
|
||||
// Add udfps enroll view into layout_container instead of
|
||||
// udfps_enroll_enrolling, so that when the content is too long to
|
||||
// make udfps_enroll_enrolling larger than the screen, udfps enroll
|
||||
// view could still be set to right position by setting bottom margin to
|
||||
// its parent view (layout_container) because it's always at the
|
||||
// bottom of the screen.
|
||||
final FrameLayout portraitLayoutContainer = defaultLayout.findViewById(
|
||||
R.id.layout_container);
|
||||
final ViewGroup.LayoutParams containerLp =
|
||||
portraitLayoutContainer.getLayoutParams();
|
||||
containerLp.height = 0;
|
||||
portraitLayoutContainer.addView(addUdfpsEnrollView(props.get(0)));
|
||||
} else if (rotation == Surface.ROTATION_270) {
|
||||
defaultLayout.addView(addUdfpsEnrollView(props.get(0)));
|
||||
}
|
||||
}
|
||||
|
||||
setContentView(defaultLayout);
|
||||
break;
|
||||
}
|
||||
setDescriptionText(R.string.security_settings_udfps_enroll_start_message);
|
||||
@@ -766,6 +806,8 @@ public class FingerprintEnrollEnrolling extends BiometricsEnrollEnrolling {
|
||||
mErrorText.removeCallbacks(mTouchAgainRunnable);
|
||||
}
|
||||
showError(helpString);
|
||||
|
||||
if (mUdfpsEnrollHelper != null) mUdfpsEnrollHelper.onEnrollmentHelp();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -813,6 +855,13 @@ public class FingerprintEnrollEnrolling extends BiometricsEnrollEnrolling {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAcquired(boolean isAcquiredGood) {
|
||||
if (mUdfpsEnrollHelper != null) {
|
||||
mUdfpsEnrollHelper.onAcquired(isAcquiredGood);
|
||||
}
|
||||
}
|
||||
|
||||
private void updateProgress(boolean animate) {
|
||||
if (mSidecar == null || !mSidecar.isEnrolling()) {
|
||||
Log.d(TAG, "Enrollment not started yet");
|
||||
@@ -826,6 +875,12 @@ public class FingerprintEnrollEnrolling extends BiometricsEnrollEnrolling {
|
||||
if (mProgressBar != null && mProgressBar.getProgress() < progress) {
|
||||
clearError();
|
||||
}
|
||||
|
||||
if (mUdfpsEnrollHelper != null) {
|
||||
mUdfpsEnrollHelper.onEnrollmentProgress(mSidecar.getEnrollmentSteps(),
|
||||
mSidecar.getEnrollmentRemaining());
|
||||
}
|
||||
|
||||
if (animate) {
|
||||
animateProgress(progress);
|
||||
} else {
|
||||
@@ -1097,6 +1152,50 @@ public class FingerprintEnrollEnrolling extends BiometricsEnrollEnrolling {
|
||||
}
|
||||
}
|
||||
|
||||
private UdfpsEnrollView addUdfpsEnrollView(FingerprintSensorPropertiesInternal udfpsProps) {
|
||||
UdfpsEnrollView udfpsEnrollView = (UdfpsEnrollView) getLayoutInflater().inflate(
|
||||
R.layout.udfps_enroll_view, null, false);
|
||||
|
||||
DisplayInfo displayInfo = new DisplayInfo();
|
||||
getDisplay().getDisplayInfo(displayInfo);
|
||||
final Display.Mode maxDisplayMode =
|
||||
DisplayUtils.getMaximumResolutionDisplayMode(displayInfo.supportedModes);
|
||||
final float scaleFactor = android.util.DisplayUtils.getPhysicalPixelDisplaySizeRatio(
|
||||
maxDisplayMode.getPhysicalWidth(), maxDisplayMode.getPhysicalHeight(),
|
||||
displayInfo.getNaturalWidth(), displayInfo.getNaturalHeight());
|
||||
if (scaleFactor == Float.POSITIVE_INFINITY) {
|
||||
mScaleFactor = 1f;
|
||||
} else {
|
||||
mScaleFactor = scaleFactor;
|
||||
}
|
||||
|
||||
Rect udfpsBounds = udfpsProps.getLocation().getRect();
|
||||
udfpsBounds.scale(mScaleFactor);
|
||||
|
||||
final Rect overlayBounds = new Rect(
|
||||
0, /* left */
|
||||
displayInfo.getNaturalHeight() / 2, /* top */
|
||||
displayInfo.getNaturalWidth(), /* right */
|
||||
displayInfo.getNaturalHeight() /* botom */);
|
||||
|
||||
// TODO(b/260617060): Extract this logic into a 3rd party library for both Settings and
|
||||
// SysUI to depend on.
|
||||
UdfpsOverlayParams params = new UdfpsOverlayParams(
|
||||
udfpsBounds,
|
||||
overlayBounds,
|
||||
displayInfo.getNaturalWidth(),
|
||||
displayInfo.getNaturalHeight(),
|
||||
mScaleFactor,
|
||||
displayInfo.rotation,
|
||||
udfpsProps.sensorType == FingerprintSensorProperties.TYPE_UDFPS_OPTICAL);
|
||||
|
||||
udfpsEnrollView.setOverlayParams(params);
|
||||
mUdfpsEnrollHelper = new UdfpsEnrollHelper(getApplicationContext(), mFingerprintManager);
|
||||
udfpsEnrollView.setEnrollHelper(mUdfpsEnrollHelper);
|
||||
|
||||
return udfpsEnrollView;
|
||||
}
|
||||
|
||||
public static class IconTouchDialog extends InstrumentedDialogFragment {
|
||||
|
||||
@Override
|
||||
|
||||
@@ -108,6 +108,11 @@ public class FingerprintEnrollSidecar extends BiometricEnrollSidecar {
|
||||
FingerprintEnrollSidecar.super.onEnrollmentProgress(remaining);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAcquired(boolean isAcquiredGood) {
|
||||
FingerprintEnrollSidecar.super.onAcquired(isAcquiredGood);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onEnrollmentHelp(int helpMsgId, CharSequence helpString) {
|
||||
FingerprintEnrollSidecar.super.onEnrollmentHelp(helpMsgId, helpString);
|
||||
|
||||
@@ -91,6 +91,11 @@ public class FingerprintUpdater {
|
||||
BiometricsSafetySource.onBiometricsChanged(mContext); // biometrics data changed
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAcquired(boolean isAcquiredGood) {
|
||||
mCallback.onAcquired(isAcquiredGood);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -0,0 +1,309 @@
|
||||
/*
|
||||
* Copyright (C) 2022 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.fingerprint;
|
||||
|
||||
|
||||
import android.animation.Animator;
|
||||
import android.animation.AnimatorSet;
|
||||
import android.animation.ValueAnimator;
|
||||
import android.content.Context;
|
||||
import android.content.res.TypedArray;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.ColorFilter;
|
||||
import android.graphics.Paint;
|
||||
import android.graphics.PointF;
|
||||
import android.graphics.Rect;
|
||||
import android.graphics.RectF;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.graphics.drawable.ShapeDrawable;
|
||||
import android.graphics.drawable.shapes.PathShape;
|
||||
import android.util.AttributeSet;
|
||||
import android.util.PathParser;
|
||||
import android.view.animation.AccelerateDecelerateInterpolator;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import com.android.settings.R;
|
||||
|
||||
/**
|
||||
* UDFPS fingerprint drawable that is shown when enrolling
|
||||
*/
|
||||
public class UdfpsEnrollDrawable extends Drawable {
|
||||
private static final String TAG = "UdfpsAnimationEnroll";
|
||||
|
||||
private static final long TARGET_ANIM_DURATION_LONG = 800L;
|
||||
private static final long TARGET_ANIM_DURATION_SHORT = 600L;
|
||||
// 1 + SCALE_MAX is the maximum that the moving target will animate to
|
||||
private static final float SCALE_MAX = 0.25f;
|
||||
private static final float DEFAULT_STROKE_WIDTH = 3f;
|
||||
|
||||
@NonNull
|
||||
private final Drawable mMovingTargetFpIcon;
|
||||
@NonNull
|
||||
private final Paint mSensorOutlinePaint;
|
||||
@NonNull
|
||||
private final Paint mBlueFill;
|
||||
@NonNull
|
||||
private final ShapeDrawable mFingerprintDrawable;
|
||||
|
||||
private int mAlpha;
|
||||
private boolean mSkipDraw = false;
|
||||
|
||||
@Nullable
|
||||
private RectF mSensorRect;
|
||||
@Nullable
|
||||
private UdfpsEnrollHelper mEnrollHelper;
|
||||
|
||||
// Moving target animator set
|
||||
@Nullable
|
||||
AnimatorSet mTargetAnimatorSet;
|
||||
// Moving target location
|
||||
float mCurrentX;
|
||||
float mCurrentY;
|
||||
// Moving target size
|
||||
float mCurrentScale = 1.f;
|
||||
|
||||
@NonNull
|
||||
private final Animator.AnimatorListener mTargetAnimListener;
|
||||
|
||||
private boolean mShouldShowTipHint = false;
|
||||
private boolean mShouldShowEdgeHint = false;
|
||||
|
||||
private int mEnrollIcon;
|
||||
private int mMovingTargetFill;
|
||||
|
||||
UdfpsEnrollDrawable(@NonNull Context context, @Nullable AttributeSet attrs) {
|
||||
mFingerprintDrawable = defaultFactory(context);
|
||||
|
||||
loadResources(context, attrs);
|
||||
mSensorOutlinePaint = new Paint(0 /* flags */);
|
||||
mSensorOutlinePaint.setAntiAlias(true);
|
||||
mSensorOutlinePaint.setColor(mMovingTargetFill);
|
||||
mSensorOutlinePaint.setStyle(Paint.Style.FILL);
|
||||
|
||||
mBlueFill = new Paint(0 /* flags */);
|
||||
mBlueFill.setAntiAlias(true);
|
||||
mBlueFill.setColor(mMovingTargetFill);
|
||||
mBlueFill.setStyle(Paint.Style.FILL);
|
||||
|
||||
mMovingTargetFpIcon = context.getResources()
|
||||
.getDrawable(R.drawable.ic_enrollment_fingerprint, null);
|
||||
mMovingTargetFpIcon.setTint(mEnrollIcon);
|
||||
mMovingTargetFpIcon.mutate();
|
||||
|
||||
mFingerprintDrawable.setTint(mEnrollIcon);
|
||||
|
||||
setAlpha(255);
|
||||
mTargetAnimListener = new Animator.AnimatorListener() {
|
||||
@Override
|
||||
public void onAnimationStart(Animator animation) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAnimationEnd(Animator animation) {
|
||||
updateTipHintVisibility();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAnimationCancel(Animator animation) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAnimationRepeat(Animator animation) {
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/** The [sensorRect] coordinates for the sensor area. */
|
||||
void onSensorRectUpdated(@NonNull RectF sensorRect) {
|
||||
int margin = ((int) sensorRect.height()) / 8;
|
||||
Rect bounds = new Rect((int) (sensorRect.left) + margin, (int) (sensorRect.top) + margin,
|
||||
(int) (sensorRect.right) - margin, (int) (sensorRect.bottom) - margin);
|
||||
updateFingerprintIconBounds(bounds);
|
||||
mSensorRect = sensorRect;
|
||||
}
|
||||
|
||||
void setEnrollHelper(@NonNull UdfpsEnrollHelper helper) {
|
||||
mEnrollHelper = helper;
|
||||
}
|
||||
|
||||
void setShouldSkipDraw(boolean skipDraw) {
|
||||
if (mSkipDraw == skipDraw) {
|
||||
return;
|
||||
}
|
||||
mSkipDraw = skipDraw;
|
||||
invalidateSelf();
|
||||
}
|
||||
|
||||
void updateFingerprintIconBounds(@NonNull Rect bounds) {
|
||||
mFingerprintDrawable.setBounds(bounds);
|
||||
invalidateSelf();
|
||||
mMovingTargetFpIcon.setBounds(bounds);
|
||||
invalidateSelf();
|
||||
}
|
||||
|
||||
void onEnrollmentProgress(int remaining, int totalSteps) {
|
||||
if (mEnrollHelper == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!mEnrollHelper.isCenterEnrollmentStage()) {
|
||||
if (mTargetAnimatorSet != null && mTargetAnimatorSet.isRunning()) {
|
||||
mTargetAnimatorSet.end();
|
||||
}
|
||||
|
||||
final PointF point = mEnrollHelper.getNextGuidedEnrollmentPoint();
|
||||
if (mCurrentX != point.x || mCurrentY != point.y) {
|
||||
final ValueAnimator x = ValueAnimator.ofFloat(mCurrentX, point.x);
|
||||
x.addUpdateListener(animation -> {
|
||||
mCurrentX = (float) animation.getAnimatedValue();
|
||||
invalidateSelf();
|
||||
});
|
||||
|
||||
final ValueAnimator y = ValueAnimator.ofFloat(mCurrentY, point.y);
|
||||
y.addUpdateListener(animation -> {
|
||||
mCurrentY = (float) animation.getAnimatedValue();
|
||||
invalidateSelf();
|
||||
});
|
||||
|
||||
final boolean isMovingToCenter = point.x == 0f && point.y == 0f;
|
||||
final long duration = isMovingToCenter
|
||||
? TARGET_ANIM_DURATION_SHORT
|
||||
: TARGET_ANIM_DURATION_LONG;
|
||||
|
||||
final ValueAnimator scale = ValueAnimator.ofFloat(0, (float) Math.PI);
|
||||
scale.setDuration(duration);
|
||||
scale.addUpdateListener(animation -> {
|
||||
// Grow then shrink
|
||||
mCurrentScale = 1
|
||||
+ SCALE_MAX * (float) Math.sin((float) animation.getAnimatedValue());
|
||||
invalidateSelf();
|
||||
});
|
||||
|
||||
mTargetAnimatorSet = new AnimatorSet();
|
||||
|
||||
mTargetAnimatorSet.setInterpolator(new AccelerateDecelerateInterpolator());
|
||||
mTargetAnimatorSet.setDuration(duration);
|
||||
mTargetAnimatorSet.addListener(mTargetAnimListener);
|
||||
mTargetAnimatorSet.playTogether(x, y, scale);
|
||||
mTargetAnimatorSet.start();
|
||||
} else {
|
||||
updateTipHintVisibility();
|
||||
}
|
||||
} else {
|
||||
updateTipHintVisibility();
|
||||
}
|
||||
|
||||
updateEdgeHintVisibility();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void draw(@NonNull Canvas canvas) {
|
||||
if (mSkipDraw) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Draw moving target
|
||||
if (mEnrollHelper != null && !mEnrollHelper.isCenterEnrollmentStage()) {
|
||||
canvas.save();
|
||||
canvas.translate(mCurrentX, mCurrentY);
|
||||
|
||||
if (mSensorRect != null) {
|
||||
canvas.scale(mCurrentScale, mCurrentScale,
|
||||
mSensorRect.centerX(), mSensorRect.centerY());
|
||||
canvas.drawOval(mSensorRect, mBlueFill);
|
||||
}
|
||||
|
||||
mMovingTargetFpIcon.draw(canvas);
|
||||
canvas.restore();
|
||||
} else {
|
||||
if (mSensorRect != null) {
|
||||
canvas.drawOval(mSensorRect, mSensorOutlinePaint);
|
||||
}
|
||||
mFingerprintDrawable.draw(canvas);
|
||||
mFingerprintDrawable.setAlpha(getAlpha());
|
||||
mSensorOutlinePaint.setAlpha(getAlpha());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setAlpha(int alpha) {
|
||||
mAlpha = alpha;
|
||||
mFingerprintDrawable.setAlpha(alpha);
|
||||
mSensorOutlinePaint.setAlpha(alpha);
|
||||
mBlueFill.setAlpha(alpha);
|
||||
mMovingTargetFpIcon.setAlpha(alpha);
|
||||
invalidateSelf();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getAlpha() {
|
||||
return mAlpha;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setColorFilter(@Nullable ColorFilter colorFilter) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getOpacity() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
private void updateTipHintVisibility() {
|
||||
final boolean shouldShow = mEnrollHelper != null && mEnrollHelper.isTipEnrollmentStage();
|
||||
// With the new update, we will git rid of most of this code, and instead
|
||||
// we will change the fingerprint icon.
|
||||
if (mShouldShowTipHint == shouldShow) {
|
||||
return;
|
||||
}
|
||||
mShouldShowTipHint = shouldShow;
|
||||
}
|
||||
|
||||
private void updateEdgeHintVisibility() {
|
||||
final boolean shouldShow = mEnrollHelper != null && mEnrollHelper.isEdgeEnrollmentStage();
|
||||
if (mShouldShowEdgeHint == shouldShow) {
|
||||
return;
|
||||
}
|
||||
mShouldShowEdgeHint = shouldShow;
|
||||
}
|
||||
|
||||
private ShapeDrawable defaultFactory(Context context) {
|
||||
String fpPath = context.getResources().getString(R.string.config_udfpsIcon);
|
||||
ShapeDrawable drawable = new ShapeDrawable(
|
||||
new PathShape(PathParser.createPathFromPathData(fpPath), 72f, 72f)
|
||||
);
|
||||
drawable.mutate();
|
||||
drawable.getPaint().setStyle(Paint.Style.STROKE);
|
||||
drawable.getPaint().setStrokeCap(Paint.Cap.ROUND);
|
||||
drawable.getPaint().setStrokeWidth(DEFAULT_STROKE_WIDTH);
|
||||
return drawable;
|
||||
}
|
||||
|
||||
private void loadResources(Context context, @Nullable AttributeSet attrs) {
|
||||
final TypedArray ta = context.obtainStyledAttributes(attrs,
|
||||
R.styleable.BiometricsEnrollView, R.attr.biometricsEnrollStyle,
|
||||
R.style.BiometricsEnrollStyle);
|
||||
mEnrollIcon = ta.getColor(R.styleable.BiometricsEnrollView_biometricsEnrollIcon, 0);
|
||||
mMovingTargetFill = ta.getColor(
|
||||
R.styleable.BiometricsEnrollView_biometricsMovingTargetFill, 0);
|
||||
ta.recycle();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,231 @@
|
||||
/*
|
||||
* Copyright (C) 2022 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.fingerprint;
|
||||
|
||||
import android.annotation.NonNull;
|
||||
import android.annotation.Nullable;
|
||||
import android.content.Context;
|
||||
import android.graphics.PointF;
|
||||
import android.hardware.fingerprint.FingerprintManager;
|
||||
import android.os.Build;
|
||||
import android.os.UserHandle;
|
||||
import android.provider.Settings;
|
||||
import android.util.Log;
|
||||
import android.util.TypedValue;
|
||||
import android.view.accessibility.AccessibilityManager;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Helps keep track of enrollment state and animates the progress bar accordingly.
|
||||
*/
|
||||
public class UdfpsEnrollHelper {
|
||||
private static final String TAG = "UdfpsEnrollHelper";
|
||||
|
||||
private static final String SCALE_OVERRIDE =
|
||||
"com.android.systemui.biometrics.UdfpsEnrollHelper.scale";
|
||||
private static final float SCALE = 0.5f;
|
||||
|
||||
private static final String NEW_COORDS_OVERRIDE =
|
||||
"com.android.systemui.biometrics.UdfpsNewCoords";
|
||||
|
||||
interface Listener {
|
||||
void onEnrollmentProgress(int remaining, int totalSteps);
|
||||
|
||||
void onEnrollmentHelp(int remaining, int totalSteps);
|
||||
|
||||
void onAcquired(boolean animateIfLastStepGood);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
private final Context mContext;
|
||||
@NonNull
|
||||
private final FingerprintManager mFingerprintManager;
|
||||
private final boolean mAccessibilityEnabled;
|
||||
@NonNull
|
||||
private final List<PointF> mGuidedEnrollmentPoints;
|
||||
|
||||
private int mTotalSteps = -1;
|
||||
private int mRemainingSteps = -1;
|
||||
|
||||
// Note that this is actually not equal to "mTotalSteps - mRemainingSteps", because the
|
||||
// interface makes no promises about monotonically increasing by one each time.
|
||||
private int mLocationsEnrolled = 0;
|
||||
|
||||
private int mCenterTouchCount = 0;
|
||||
|
||||
@Nullable
|
||||
UdfpsEnrollHelper.Listener mListener;
|
||||
|
||||
public UdfpsEnrollHelper(@NonNull Context context,
|
||||
@NonNull FingerprintManager fingerprintManager) {
|
||||
|
||||
mContext = context;
|
||||
mFingerprintManager = fingerprintManager;
|
||||
|
||||
final AccessibilityManager am = context.getSystemService(AccessibilityManager.class);
|
||||
mAccessibilityEnabled = am.isEnabled();
|
||||
|
||||
mGuidedEnrollmentPoints = new ArrayList<>();
|
||||
|
||||
// Number of pixels per mm
|
||||
float px = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_MM, 1,
|
||||
context.getResources().getDisplayMetrics());
|
||||
boolean useNewCoords = Settings.Secure.getIntForUser(mContext.getContentResolver(),
|
||||
NEW_COORDS_OVERRIDE, 0,
|
||||
UserHandle.USER_CURRENT) != 0;
|
||||
if (useNewCoords && (Build.IS_ENG || Build.IS_USERDEBUG)) {
|
||||
Log.v(TAG, "Using new coordinates");
|
||||
mGuidedEnrollmentPoints.add(new PointF(-0.15f * px, -1.02f * px));
|
||||
mGuidedEnrollmentPoints.add(new PointF(-0.15f * px, 1.02f * px));
|
||||
mGuidedEnrollmentPoints.add(new PointF(0.29f * px, 0.00f * px));
|
||||
mGuidedEnrollmentPoints.add(new PointF(2.17f * px, -2.35f * px));
|
||||
mGuidedEnrollmentPoints.add(new PointF(1.07f * px, -3.96f * px));
|
||||
mGuidedEnrollmentPoints.add(new PointF(-0.37f * px, -4.31f * px));
|
||||
mGuidedEnrollmentPoints.add(new PointF(-1.69f * px, -3.29f * px));
|
||||
mGuidedEnrollmentPoints.add(new PointF(-2.48f * px, -1.23f * px));
|
||||
mGuidedEnrollmentPoints.add(new PointF(-2.48f * px, 1.23f * px));
|
||||
mGuidedEnrollmentPoints.add(new PointF(-1.69f * px, 3.29f * px));
|
||||
mGuidedEnrollmentPoints.add(new PointF(-0.37f * px, 4.31f * px));
|
||||
mGuidedEnrollmentPoints.add(new PointF(1.07f * px, 3.96f * px));
|
||||
mGuidedEnrollmentPoints.add(new PointF(2.17f * px, 2.35f * px));
|
||||
mGuidedEnrollmentPoints.add(new PointF(2.58f * px, 0.00f * px));
|
||||
} else {
|
||||
Log.v(TAG, "Using old coordinates");
|
||||
mGuidedEnrollmentPoints.add(new PointF(2.00f * px, 0.00f * px));
|
||||
mGuidedEnrollmentPoints.add(new PointF(0.87f * px, -2.70f * px));
|
||||
mGuidedEnrollmentPoints.add(new PointF(-1.80f * px, -1.31f * px));
|
||||
mGuidedEnrollmentPoints.add(new PointF(-1.80f * px, 1.31f * px));
|
||||
mGuidedEnrollmentPoints.add(new PointF(0.88f * px, 2.70f * px));
|
||||
mGuidedEnrollmentPoints.add(new PointF(3.94f * px, -1.06f * px));
|
||||
mGuidedEnrollmentPoints.add(new PointF(2.90f * px, -4.14f * px));
|
||||
mGuidedEnrollmentPoints.add(new PointF(-0.52f * px, -5.95f * px));
|
||||
mGuidedEnrollmentPoints.add(new PointF(-3.33f * px, -3.33f * px));
|
||||
mGuidedEnrollmentPoints.add(new PointF(-3.99f * px, -0.35f * px));
|
||||
mGuidedEnrollmentPoints.add(new PointF(-3.62f * px, 2.54f * px));
|
||||
mGuidedEnrollmentPoints.add(new PointF(-1.49f * px, 5.57f * px));
|
||||
mGuidedEnrollmentPoints.add(new PointF(2.29f * px, 4.92f * px));
|
||||
mGuidedEnrollmentPoints.add(new PointF(3.82f * px, 1.78f * px));
|
||||
}
|
||||
}
|
||||
|
||||
void onEnrollmentProgress(int totalSteps, int remaining) {
|
||||
if (mTotalSteps == -1) {
|
||||
mTotalSteps = totalSteps;
|
||||
}
|
||||
|
||||
if (remaining != mRemainingSteps) {
|
||||
mLocationsEnrolled++;
|
||||
if (isCenterEnrollmentStage()) {
|
||||
mCenterTouchCount++;
|
||||
}
|
||||
}
|
||||
|
||||
mRemainingSteps = remaining;
|
||||
|
||||
if (mListener != null && mTotalSteps != -1) {
|
||||
mListener.onEnrollmentProgress(remaining, mTotalSteps);
|
||||
}
|
||||
}
|
||||
|
||||
void onEnrollmentHelp() {
|
||||
if (mListener != null && mTotalSteps != -1) {
|
||||
mListener.onEnrollmentHelp(mRemainingSteps, mTotalSteps);
|
||||
}
|
||||
}
|
||||
|
||||
void onAcquired(boolean isAcquiredGood) {
|
||||
if (mListener != null && mTotalSteps != -1) {
|
||||
mListener.onAcquired(isAcquiredGood && animateIfLastStep());
|
||||
}
|
||||
}
|
||||
|
||||
void setListener(UdfpsEnrollHelper.Listener listener) {
|
||||
mListener = listener;
|
||||
|
||||
// Only notify during setListener if enrollment is already in progress, so the progress
|
||||
// bar can be updated. If enrollment has not started yet, the progress bar will be empty
|
||||
// anyway.
|
||||
if (mListener != null && mTotalSteps != -1) {
|
||||
mListener.onEnrollmentProgress(mRemainingSteps, mTotalSteps);
|
||||
}
|
||||
}
|
||||
|
||||
boolean isCenterEnrollmentStage() {
|
||||
if (mTotalSteps == -1 || mRemainingSteps == -1) {
|
||||
return true;
|
||||
}
|
||||
return mTotalSteps - mRemainingSteps < getStageThresholdSteps(mTotalSteps, 0);
|
||||
}
|
||||
|
||||
boolean isTipEnrollmentStage() {
|
||||
if (mTotalSteps == -1 || mRemainingSteps == -1) {
|
||||
return false;
|
||||
}
|
||||
final int progressSteps = mTotalSteps - mRemainingSteps;
|
||||
return progressSteps >= getStageThresholdSteps(mTotalSteps, 1)
|
||||
&& progressSteps < getStageThresholdSteps(mTotalSteps, 2);
|
||||
}
|
||||
|
||||
boolean isEdgeEnrollmentStage() {
|
||||
if (mTotalSteps == -1 || mRemainingSteps == -1) {
|
||||
return false;
|
||||
}
|
||||
return mTotalSteps - mRemainingSteps >= getStageThresholdSteps(mTotalSteps, 2);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
PointF getNextGuidedEnrollmentPoint() {
|
||||
if (mAccessibilityEnabled || !isGuidedEnrollmentStage()) {
|
||||
return new PointF(0f, 0f);
|
||||
}
|
||||
|
||||
float scale = SCALE;
|
||||
if (Build.IS_ENG || Build.IS_USERDEBUG) {
|
||||
scale = Settings.Secure.getFloatForUser(mContext.getContentResolver(),
|
||||
SCALE_OVERRIDE, SCALE,
|
||||
UserHandle.USER_CURRENT);
|
||||
}
|
||||
final int index = mLocationsEnrolled - mCenterTouchCount;
|
||||
final PointF originalPoint = mGuidedEnrollmentPoints
|
||||
.get(index % mGuidedEnrollmentPoints.size());
|
||||
return new PointF(originalPoint.x * scale, originalPoint.y * scale);
|
||||
}
|
||||
|
||||
boolean animateIfLastStep() {
|
||||
if (mListener == null) {
|
||||
Log.e(TAG, "animateIfLastStep, null listener");
|
||||
return false;
|
||||
}
|
||||
|
||||
return mRemainingSteps <= 2 && mRemainingSteps >= 0;
|
||||
}
|
||||
|
||||
private int getStageThresholdSteps(int totalSteps, int stageIndex) {
|
||||
return Math.round(totalSteps * mFingerprintManager.getEnrollStageThreshold(stageIndex));
|
||||
}
|
||||
|
||||
private boolean isGuidedEnrollmentStage() {
|
||||
if (mAccessibilityEnabled || mTotalSteps == -1 || mRemainingSteps == -1) {
|
||||
return false;
|
||||
}
|
||||
final int progressSteps = mTotalSteps - mRemainingSteps;
|
||||
return progressSteps >= getStageThresholdSteps(mTotalSteps, 0)
|
||||
&& progressSteps < getStageThresholdSteps(mTotalSteps, 1);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,419 @@
|
||||
/*
|
||||
* Copyright (C) 2021 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.fingerprint;
|
||||
|
||||
import android.animation.ValueAnimator;
|
||||
import android.content.Context;
|
||||
import android.content.res.TypedArray;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.ColorFilter;
|
||||
import android.graphics.Paint;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.os.Process;
|
||||
import android.os.VibrationAttributes;
|
||||
import android.os.VibrationEffect;
|
||||
import android.os.Vibrator;
|
||||
import android.util.AttributeSet;
|
||||
import android.util.DisplayMetrics;
|
||||
import android.view.accessibility.AccessibilityManager;
|
||||
import android.view.animation.DecelerateInterpolator;
|
||||
import android.view.animation.Interpolator;
|
||||
import android.view.animation.OvershootInterpolator;
|
||||
|
||||
import androidx.annotation.ColorInt;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import com.android.settings.R;
|
||||
|
||||
/**
|
||||
* UDFPS enrollment progress bar.
|
||||
*/
|
||||
public class UdfpsEnrollProgressBarDrawable extends Drawable {
|
||||
private static final String TAG = "UdfpsProgressBar";
|
||||
|
||||
private static final long CHECKMARK_ANIMATION_DELAY_MS = 200L;
|
||||
private static final long CHECKMARK_ANIMATION_DURATION_MS = 300L;
|
||||
private static final long FILL_COLOR_ANIMATION_DURATION_MS = 350L;
|
||||
private static final long PROGRESS_ANIMATION_DURATION_MS = 400L;
|
||||
private static final float STROKE_WIDTH_DP = 12f;
|
||||
private static final Interpolator DEACCEL = new DecelerateInterpolator();
|
||||
|
||||
private static final VibrationEffect VIBRATE_EFFECT_ERROR =
|
||||
VibrationEffect.createWaveform(new long[]{0, 5, 55, 60}, -1);
|
||||
private static final VibrationAttributes FINGERPRINT_ENROLLING_SONFICATION_ATTRIBUTES =
|
||||
VibrationAttributes.createForUsage(VibrationAttributes.USAGE_ACCESSIBILITY);
|
||||
|
||||
private static final VibrationAttributes HARDWARE_FEEDBACK_VIBRATION_ATTRIBUTES =
|
||||
VibrationAttributes.createForUsage(VibrationAttributes.USAGE_HARDWARE_FEEDBACK);
|
||||
|
||||
private static final VibrationEffect SUCCESS_VIBRATION_EFFECT =
|
||||
VibrationEffect.get(VibrationEffect.EFFECT_CLICK);
|
||||
|
||||
private final float mStrokeWidthPx;
|
||||
@ColorInt
|
||||
private final int mProgressColor;
|
||||
@ColorInt
|
||||
private final int mHelpColor;
|
||||
@ColorInt
|
||||
private final int mOnFirstBucketFailedColor;
|
||||
@NonNull
|
||||
private final Drawable mCheckmarkDrawable;
|
||||
@NonNull
|
||||
private final Interpolator mCheckmarkInterpolator;
|
||||
@NonNull
|
||||
private final Paint mBackgroundPaint;
|
||||
@NonNull
|
||||
private final Paint mFillPaint;
|
||||
@NonNull
|
||||
private final Vibrator mVibrator;
|
||||
@NonNull
|
||||
private final boolean mIsAccessibilityEnabled;
|
||||
@NonNull
|
||||
private final Context mContext;
|
||||
|
||||
private boolean mAfterFirstTouch;
|
||||
|
||||
private int mRemainingSteps = 0;
|
||||
private int mTotalSteps = 0;
|
||||
private float mProgress = 0f;
|
||||
@Nullable
|
||||
private ValueAnimator mProgressAnimator;
|
||||
@NonNull
|
||||
private final ValueAnimator.AnimatorUpdateListener mProgressUpdateListener;
|
||||
|
||||
private boolean mShowingHelp = false;
|
||||
@Nullable
|
||||
private ValueAnimator mFillColorAnimator;
|
||||
@NonNull
|
||||
private final ValueAnimator.AnimatorUpdateListener mFillColorUpdateListener;
|
||||
|
||||
@Nullable
|
||||
private ValueAnimator mBackgroundColorAnimator;
|
||||
@NonNull
|
||||
private final ValueAnimator.AnimatorUpdateListener mBackgroundColorUpdateListener;
|
||||
|
||||
private boolean mComplete = false;
|
||||
private float mCheckmarkScale = 0f;
|
||||
@Nullable
|
||||
private ValueAnimator mCheckmarkAnimator;
|
||||
@NonNull
|
||||
private final ValueAnimator.AnimatorUpdateListener mCheckmarkUpdateListener;
|
||||
|
||||
private int mMovingTargetFill;
|
||||
private int mMovingTargetFillError;
|
||||
private int mEnrollProgress;
|
||||
private int mEnrollProgressHelp;
|
||||
private int mEnrollProgressHelpWithTalkback;
|
||||
|
||||
public UdfpsEnrollProgressBarDrawable(@NonNull Context context, @Nullable AttributeSet attrs) {
|
||||
mContext = context;
|
||||
|
||||
loadResources(context, attrs);
|
||||
float density = context.getResources().getDisplayMetrics().densityDpi;
|
||||
mStrokeWidthPx = STROKE_WIDTH_DP * (density / DisplayMetrics.DENSITY_DEFAULT);
|
||||
mProgressColor = mEnrollProgress;
|
||||
final AccessibilityManager am = context.getSystemService(AccessibilityManager.class);
|
||||
mIsAccessibilityEnabled = am.isTouchExplorationEnabled();
|
||||
mOnFirstBucketFailedColor = mMovingTargetFillError;
|
||||
if (!mIsAccessibilityEnabled) {
|
||||
mHelpColor = mEnrollProgressHelp;
|
||||
} else {
|
||||
mHelpColor = mEnrollProgressHelpWithTalkback;
|
||||
}
|
||||
mCheckmarkDrawable = context.getDrawable(R.drawable.udfps_enroll_checkmark);
|
||||
mCheckmarkDrawable.mutate();
|
||||
mCheckmarkInterpolator = new OvershootInterpolator();
|
||||
|
||||
mBackgroundPaint = new Paint();
|
||||
mBackgroundPaint.setStrokeWidth(mStrokeWidthPx);
|
||||
mBackgroundPaint.setColor(mMovingTargetFill);
|
||||
mBackgroundPaint.setAntiAlias(true);
|
||||
mBackgroundPaint.setStyle(Paint.Style.STROKE);
|
||||
mBackgroundPaint.setStrokeCap(Paint.Cap.ROUND);
|
||||
|
||||
// Progress fill should *not* use the extracted system color.
|
||||
mFillPaint = new Paint();
|
||||
mFillPaint.setStrokeWidth(mStrokeWidthPx);
|
||||
mFillPaint.setColor(mProgressColor);
|
||||
mFillPaint.setAntiAlias(true);
|
||||
mFillPaint.setStyle(Paint.Style.STROKE);
|
||||
mFillPaint.setStrokeCap(Paint.Cap.ROUND);
|
||||
|
||||
mVibrator = mContext.getSystemService(Vibrator.class);
|
||||
|
||||
mProgressUpdateListener = animation -> {
|
||||
mProgress = (float) animation.getAnimatedValue();
|
||||
invalidateSelf();
|
||||
};
|
||||
|
||||
mFillColorUpdateListener = animation -> {
|
||||
mFillPaint.setColor((int) animation.getAnimatedValue());
|
||||
invalidateSelf();
|
||||
};
|
||||
|
||||
mCheckmarkUpdateListener = animation -> {
|
||||
mCheckmarkScale = (float) animation.getAnimatedValue();
|
||||
invalidateSelf();
|
||||
};
|
||||
|
||||
mBackgroundColorUpdateListener = animation -> {
|
||||
mBackgroundPaint.setColor((int) animation.getAnimatedValue());
|
||||
invalidateSelf();
|
||||
};
|
||||
}
|
||||
|
||||
void onEnrollmentProgress(int remaining, int totalSteps) {
|
||||
mAfterFirstTouch = true;
|
||||
updateState(remaining, totalSteps, false /* showingHelp */);
|
||||
}
|
||||
|
||||
void onEnrollmentHelp(int remaining, int totalSteps) {
|
||||
updateState(remaining, totalSteps, true /* showingHelp */);
|
||||
}
|
||||
|
||||
void onLastStepAcquired() {
|
||||
updateState(0, mTotalSteps, false /* showingHelp */);
|
||||
}
|
||||
|
||||
private void updateState(int remainingSteps, int totalSteps, boolean showingHelp) {
|
||||
updateProgress(remainingSteps, totalSteps, showingHelp);
|
||||
updateFillColor(showingHelp);
|
||||
}
|
||||
|
||||
private void updateProgress(int remainingSteps, int totalSteps, boolean showingHelp) {
|
||||
if (mRemainingSteps == remainingSteps && mTotalSteps == totalSteps) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (mShowingHelp) {
|
||||
if (mVibrator != null && mIsAccessibilityEnabled) {
|
||||
mVibrator.vibrate(Process.myUid(), mContext.getOpPackageName(),
|
||||
VIBRATE_EFFECT_ERROR, getClass().getSimpleName() + "::onEnrollmentHelp",
|
||||
FINGERPRINT_ENROLLING_SONFICATION_ATTRIBUTES);
|
||||
}
|
||||
} else {
|
||||
// If the first touch is an error, remainingSteps will be -1 and the callback
|
||||
// doesn't come from onEnrollmentHelp. If we are in the accessibility flow,
|
||||
// we still would like to vibrate.
|
||||
if (mVibrator != null) {
|
||||
if (remainingSteps == -1 && mIsAccessibilityEnabled) {
|
||||
mVibrator.vibrate(Process.myUid(), mContext.getOpPackageName(),
|
||||
VIBRATE_EFFECT_ERROR,
|
||||
getClass().getSimpleName() + "::onFirstTouchError",
|
||||
FINGERPRINT_ENROLLING_SONFICATION_ATTRIBUTES);
|
||||
} else if (remainingSteps != -1 && !mIsAccessibilityEnabled) {
|
||||
mVibrator.vibrate(Process.myUid(),
|
||||
mContext.getOpPackageName(),
|
||||
SUCCESS_VIBRATION_EFFECT,
|
||||
getClass().getSimpleName() + "::OnEnrollmentProgress",
|
||||
HARDWARE_FEEDBACK_VIBRATION_ATTRIBUTES);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
mShowingHelp = showingHelp;
|
||||
mRemainingSteps = remainingSteps;
|
||||
mTotalSteps = totalSteps;
|
||||
|
||||
final int progressSteps = Math.max(0, totalSteps - remainingSteps);
|
||||
|
||||
// If needed, add 1 to progress and total steps to account for initial touch.
|
||||
final int adjustedSteps = mAfterFirstTouch ? progressSteps + 1 : progressSteps;
|
||||
final int adjustedTotal = mAfterFirstTouch ? mTotalSteps + 1 : mTotalSteps;
|
||||
|
||||
final float targetProgress = Math.min(1f, (float) adjustedSteps / (float) adjustedTotal);
|
||||
|
||||
if (mProgressAnimator != null && mProgressAnimator.isRunning()) {
|
||||
mProgressAnimator.cancel();
|
||||
}
|
||||
|
||||
mProgressAnimator = ValueAnimator.ofFloat(mProgress, targetProgress);
|
||||
mProgressAnimator.setDuration(PROGRESS_ANIMATION_DURATION_MS);
|
||||
mProgressAnimator.addUpdateListener(mProgressUpdateListener);
|
||||
mProgressAnimator.start();
|
||||
|
||||
if (remainingSteps == 0) {
|
||||
startCompletionAnimation();
|
||||
} else if (remainingSteps > 0) {
|
||||
rollBackCompletionAnimation();
|
||||
}
|
||||
}
|
||||
|
||||
private void animateBackgroundColor() {
|
||||
if (mBackgroundColorAnimator != null && mBackgroundColorAnimator.isRunning()) {
|
||||
mBackgroundColorAnimator.end();
|
||||
}
|
||||
mBackgroundColorAnimator = ValueAnimator.ofArgb(mBackgroundPaint.getColor(),
|
||||
mOnFirstBucketFailedColor);
|
||||
mBackgroundColorAnimator.setDuration(FILL_COLOR_ANIMATION_DURATION_MS);
|
||||
mBackgroundColorAnimator.setRepeatCount(1);
|
||||
mBackgroundColorAnimator.setRepeatMode(ValueAnimator.REVERSE);
|
||||
mBackgroundColorAnimator.setInterpolator(DEACCEL);
|
||||
mBackgroundColorAnimator.addUpdateListener(mBackgroundColorUpdateListener);
|
||||
mBackgroundColorAnimator.start();
|
||||
}
|
||||
|
||||
private void updateFillColor(boolean showingHelp) {
|
||||
if (!mAfterFirstTouch && showingHelp) {
|
||||
// If we are on the first touch, animate the background color
|
||||
// instead of the progress color.
|
||||
animateBackgroundColor();
|
||||
return;
|
||||
}
|
||||
|
||||
if (mFillColorAnimator != null && mFillColorAnimator.isRunning()) {
|
||||
mFillColorAnimator.end();
|
||||
}
|
||||
|
||||
@ColorInt final int targetColor = showingHelp ? mHelpColor : mProgressColor;
|
||||
mFillColorAnimator = ValueAnimator.ofArgb(mFillPaint.getColor(), targetColor);
|
||||
mFillColorAnimator.setDuration(FILL_COLOR_ANIMATION_DURATION_MS);
|
||||
mFillColorAnimator.setRepeatCount(1);
|
||||
mFillColorAnimator.setRepeatMode(ValueAnimator.REVERSE);
|
||||
mFillColorAnimator.setInterpolator(DEACCEL);
|
||||
mFillColorAnimator.addUpdateListener(mFillColorUpdateListener);
|
||||
mFillColorAnimator.start();
|
||||
}
|
||||
|
||||
private void startCompletionAnimation() {
|
||||
if (mComplete) {
|
||||
return;
|
||||
}
|
||||
mComplete = true;
|
||||
|
||||
if (mCheckmarkAnimator != null && mCheckmarkAnimator.isRunning()) {
|
||||
mCheckmarkAnimator.cancel();
|
||||
}
|
||||
|
||||
mCheckmarkAnimator = ValueAnimator.ofFloat(mCheckmarkScale, 1f);
|
||||
mCheckmarkAnimator.setStartDelay(CHECKMARK_ANIMATION_DELAY_MS);
|
||||
mCheckmarkAnimator.setDuration(CHECKMARK_ANIMATION_DURATION_MS);
|
||||
mCheckmarkAnimator.setInterpolator(mCheckmarkInterpolator);
|
||||
mCheckmarkAnimator.addUpdateListener(mCheckmarkUpdateListener);
|
||||
mCheckmarkAnimator.start();
|
||||
}
|
||||
|
||||
private void rollBackCompletionAnimation() {
|
||||
if (!mComplete) {
|
||||
return;
|
||||
}
|
||||
mComplete = false;
|
||||
|
||||
// Adjust duration based on how much of the completion animation has played.
|
||||
final float animatedFraction = mCheckmarkAnimator != null
|
||||
? mCheckmarkAnimator.getAnimatedFraction()
|
||||
: 0f;
|
||||
final long durationMs = Math.round(CHECKMARK_ANIMATION_DELAY_MS * animatedFraction);
|
||||
|
||||
if (mCheckmarkAnimator != null && mCheckmarkAnimator.isRunning()) {
|
||||
mCheckmarkAnimator.cancel();
|
||||
}
|
||||
|
||||
mCheckmarkAnimator = ValueAnimator.ofFloat(mCheckmarkScale, 0f);
|
||||
mCheckmarkAnimator.setDuration(durationMs);
|
||||
mCheckmarkAnimator.addUpdateListener(mCheckmarkUpdateListener);
|
||||
mCheckmarkAnimator.start();
|
||||
}
|
||||
|
||||
private void loadResources(Context context, @Nullable AttributeSet attrs) {
|
||||
final TypedArray ta = context.obtainStyledAttributes(attrs,
|
||||
R.styleable.BiometricsEnrollView, R.attr.biometricsEnrollStyle,
|
||||
R.style.BiometricsEnrollStyle);
|
||||
mMovingTargetFill = ta.getColor(
|
||||
R.styleable.BiometricsEnrollView_biometricsMovingTargetFill, 0);
|
||||
mMovingTargetFillError = ta.getColor(
|
||||
R.styleable.BiometricsEnrollView_biometricsMovingTargetFillError, 0);
|
||||
mEnrollProgress = ta.getColor(
|
||||
R.styleable.BiometricsEnrollView_biometricsEnrollProgress, 0);
|
||||
mEnrollProgressHelp = ta.getColor(
|
||||
R.styleable.BiometricsEnrollView_biometricsEnrollProgressHelp, 0);
|
||||
mEnrollProgressHelpWithTalkback = ta.getColor(
|
||||
R.styleable.BiometricsEnrollView_biometricsEnrollProgressHelpWithTalkback, 0);
|
||||
ta.recycle();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void draw(@NonNull Canvas canvas) {
|
||||
canvas.save();
|
||||
|
||||
// Progress starts from the top, instead of the right
|
||||
canvas.rotate(-90f, getBounds().centerX(), getBounds().centerY());
|
||||
|
||||
final float halfPaddingPx = mStrokeWidthPx / 2f;
|
||||
|
||||
if (mProgress < 1f) {
|
||||
// Draw the background color of the progress circle.
|
||||
canvas.drawArc(
|
||||
halfPaddingPx,
|
||||
halfPaddingPx,
|
||||
getBounds().right - halfPaddingPx,
|
||||
getBounds().bottom - halfPaddingPx,
|
||||
0f /* startAngle */,
|
||||
360f /* sweepAngle */,
|
||||
false /* useCenter */,
|
||||
mBackgroundPaint);
|
||||
}
|
||||
|
||||
if (mProgress > 0f) {
|
||||
// Draw the filled portion of the progress circle.
|
||||
canvas.drawArc(
|
||||
halfPaddingPx,
|
||||
halfPaddingPx,
|
||||
getBounds().right - halfPaddingPx,
|
||||
getBounds().bottom - halfPaddingPx,
|
||||
0f /* startAngle */,
|
||||
360f * mProgress /* sweepAngle */,
|
||||
false /* useCenter */,
|
||||
mFillPaint);
|
||||
}
|
||||
|
||||
canvas.restore();
|
||||
|
||||
if (mCheckmarkScale > 0f) {
|
||||
final float offsetScale = (float) Math.sqrt(2) / 2f;
|
||||
final float centerXOffset = (getBounds().width() - mStrokeWidthPx) / 2f * offsetScale;
|
||||
final float centerYOffset = (getBounds().height() - mStrokeWidthPx) / 2f * offsetScale;
|
||||
final float centerX = getBounds().centerX() + centerXOffset;
|
||||
final float centerY = getBounds().centerY() + centerYOffset;
|
||||
|
||||
final float boundsXOffset =
|
||||
mCheckmarkDrawable.getIntrinsicWidth() / 2f * mCheckmarkScale;
|
||||
final float boundsYOffset =
|
||||
mCheckmarkDrawable.getIntrinsicHeight() / 2f * mCheckmarkScale;
|
||||
|
||||
final int left = Math.round(centerX - boundsXOffset);
|
||||
final int top = Math.round(centerY - boundsYOffset);
|
||||
final int right = Math.round(centerX + boundsXOffset);
|
||||
final int bottom = Math.round(centerY + boundsYOffset);
|
||||
mCheckmarkDrawable.setBounds(left, top, right, bottom);
|
||||
mCheckmarkDrawable.draw(canvas);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setAlpha(int alpha) {}
|
||||
|
||||
@Override
|
||||
public void setColorFilter(@Nullable ColorFilter colorFilter) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getOpacity() {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,204 @@
|
||||
/*
|
||||
* Copyright (C) 2022 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.fingerprint;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.Rect;
|
||||
import android.graphics.RectF;
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
import android.util.AttributeSet;
|
||||
import android.util.RotationUtils;
|
||||
import android.view.Gravity;
|
||||
import android.view.Surface;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.FrameLayout;
|
||||
import android.widget.ImageView;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import com.android.settings.R;
|
||||
|
||||
/**
|
||||
* View corresponding with udfps_enroll_view.xml
|
||||
*/
|
||||
public class UdfpsEnrollView extends FrameLayout implements UdfpsEnrollHelper.Listener {
|
||||
@NonNull
|
||||
private final UdfpsEnrollDrawable mFingerprintDrawable;
|
||||
@NonNull
|
||||
private final UdfpsEnrollProgressBarDrawable mFingerprintProgressDrawable;
|
||||
@NonNull
|
||||
private final Handler mHandler;
|
||||
|
||||
@NonNull
|
||||
private ImageView mFingerprintProgressView;
|
||||
|
||||
private int mProgressBarRadius;
|
||||
|
||||
// sensorRect may be bigger than the sensor. True sensor dimensions are defined in
|
||||
// overlayParams.sensorBounds
|
||||
private Rect mSensorRect;
|
||||
private UdfpsOverlayParams mOverlayParams;
|
||||
|
||||
public UdfpsEnrollView(Context context, @Nullable AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
mFingerprintDrawable = new UdfpsEnrollDrawable(mContext, attrs);
|
||||
mFingerprintProgressDrawable = new UdfpsEnrollProgressBarDrawable(context, attrs);
|
||||
mHandler = new Handler(Looper.getMainLooper());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onFinishInflate() {
|
||||
ImageView fingerprintView = findViewById(R.id.udfps_enroll_animation_fp_view);
|
||||
fingerprintView.setImageDrawable(mFingerprintDrawable);
|
||||
mFingerprintProgressView = findViewById(R.id.udfps_enroll_animation_fp_progress_view);
|
||||
mFingerprintProgressView.setImageDrawable(mFingerprintProgressDrawable);
|
||||
}
|
||||
|
||||
// Implements UdfpsEnrollHelper.Listener
|
||||
@Override
|
||||
public void onEnrollmentProgress(int remaining, int totalSteps) {
|
||||
mHandler.post(() -> {
|
||||
mFingerprintProgressDrawable.onEnrollmentProgress(remaining, totalSteps);
|
||||
mFingerprintDrawable.onEnrollmentProgress(remaining, totalSteps);
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onEnrollmentHelp(int remaining, int totalSteps) {
|
||||
mHandler.post(() -> mFingerprintProgressDrawable.onEnrollmentHelp(remaining, totalSteps));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAcquired(boolean animateIfLastStepGood) {
|
||||
mHandler.post(() -> {
|
||||
if (animateIfLastStepGood) mFingerprintProgressDrawable.onLastStepAcquired();
|
||||
});
|
||||
}
|
||||
|
||||
void setOverlayParams(UdfpsOverlayParams params) {
|
||||
mOverlayParams = params;
|
||||
|
||||
post(() -> {
|
||||
mProgressBarRadius =
|
||||
(int) (mOverlayParams.getScaleFactor() * getContext().getResources().getInteger(
|
||||
R.integer.config_udfpsEnrollProgressBar));
|
||||
mSensorRect = mOverlayParams.getSensorBounds();
|
||||
|
||||
onSensorRectUpdated();
|
||||
});
|
||||
}
|
||||
|
||||
void setEnrollHelper(UdfpsEnrollHelper enrollHelper) {
|
||||
mFingerprintDrawable.setEnrollHelper(enrollHelper);
|
||||
enrollHelper.setListener(this);
|
||||
}
|
||||
|
||||
private void onSensorRectUpdated() {
|
||||
updateDimensions();
|
||||
updateAccessibilityViewLocation();
|
||||
|
||||
// Updates sensor rect in relation to the overlay view
|
||||
mSensorRect.set(getPaddingX(), getPaddingY(),
|
||||
(mOverlayParams.getSensorBounds().width() + getPaddingX()),
|
||||
(mOverlayParams.getSensorBounds().height() + getPaddingY()));
|
||||
mFingerprintDrawable.onSensorRectUpdated(new RectF(mSensorRect));
|
||||
}
|
||||
|
||||
private void updateDimensions() {
|
||||
// Original sensorBounds assume portrait mode.
|
||||
Rect rotatedBounds = mOverlayParams.getSensorBounds();
|
||||
int rotation = mOverlayParams.getRotation();
|
||||
if (rotation == Surface.ROTATION_90 || rotation == Surface.ROTATION_270) {
|
||||
RotationUtils.rotateBounds(
|
||||
rotatedBounds,
|
||||
mOverlayParams.getNaturalDisplayWidth(),
|
||||
mOverlayParams.getNaturalDisplayHeight(),
|
||||
rotation
|
||||
);
|
||||
}
|
||||
|
||||
// Use parent view's and rotatedBound's absolute coordinates to decide the margins of
|
||||
// UdfpsEnrollView, so that its center keeps consistent with sensor rect's.
|
||||
ViewGroup parentView = (ViewGroup) getParent();
|
||||
int[] coords = parentView.getLocationOnScreen();
|
||||
int parentLeft = coords[0];
|
||||
int parentTop = coords[1];
|
||||
int parentRight = parentLeft + parentView.getWidth();
|
||||
int parentBottom = parentTop + parentView.getHeight();
|
||||
MarginLayoutParams marginLayoutParams = (MarginLayoutParams) getLayoutParams();
|
||||
FrameLayout.LayoutParams params = (LayoutParams) getLayoutParams();
|
||||
|
||||
switch (rotation) {
|
||||
case Surface.ROTATION_0:
|
||||
case Surface.ROTATION_180:
|
||||
params.gravity = Gravity.RIGHT | Gravity.TOP;
|
||||
marginLayoutParams.rightMargin = parentRight - rotatedBounds.right - getPaddingX();
|
||||
marginLayoutParams.topMargin = rotatedBounds.top - parentTop - getPaddingY();
|
||||
break;
|
||||
case Surface.ROTATION_90:
|
||||
params.gravity = Gravity.RIGHT | Gravity.BOTTOM;
|
||||
marginLayoutParams.rightMargin = parentRight - rotatedBounds.right - getPaddingX();
|
||||
marginLayoutParams.bottomMargin =
|
||||
parentBottom - rotatedBounds.bottom - getPaddingY();
|
||||
break;
|
||||
case Surface.ROTATION_270:
|
||||
params.gravity = Gravity.LEFT | Gravity.BOTTOM;
|
||||
marginLayoutParams.leftMargin = rotatedBounds.left - parentLeft - getPaddingX();
|
||||
marginLayoutParams.bottomMargin =
|
||||
parentBottom - rotatedBounds.bottom - getPaddingY();
|
||||
break;
|
||||
}
|
||||
|
||||
params.height = rotatedBounds.height() + 2 * getPaddingX();
|
||||
params.width = rotatedBounds.width() + 2 * getPaddingY();
|
||||
setLayoutParams(params);
|
||||
}
|
||||
|
||||
private void updateAccessibilityViewLocation() {
|
||||
View fingerprintAccessibilityView = findViewById(R.id.udfps_enroll_accessibility_view);
|
||||
ViewGroup.LayoutParams params = fingerprintAccessibilityView.getLayoutParams();
|
||||
params.width = mOverlayParams.getSensorBounds().width();
|
||||
params.height = mOverlayParams.getSensorBounds().height();
|
||||
fingerprintAccessibilityView.setLayoutParams(params);
|
||||
fingerprintAccessibilityView.requestLayout();
|
||||
}
|
||||
|
||||
private void onFingerDown() {
|
||||
if (mOverlayParams.isOptical()) {
|
||||
mFingerprintDrawable.setShouldSkipDraw(true);
|
||||
mFingerprintDrawable.invalidateSelf();
|
||||
}
|
||||
}
|
||||
|
||||
private void onFingerUp() {
|
||||
if (mOverlayParams.isOptical()) {
|
||||
mFingerprintDrawable.setShouldSkipDraw(false);
|
||||
mFingerprintDrawable.invalidateSelf();
|
||||
}
|
||||
}
|
||||
|
||||
private int getPaddingX() {
|
||||
return mProgressBarRadius;
|
||||
}
|
||||
|
||||
private int getPaddingY() {
|
||||
return mProgressBarRadius;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,95 @@
|
||||
/*
|
||||
* Copyright (C) 2022 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.fingerprint;
|
||||
|
||||
import android.graphics.Rect;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
/**
|
||||
* Collection of parameters that define an under-display fingerprint sensor (UDFPS) overlay.
|
||||
*
|
||||
* [sensorBounds] coordinates of the bounding box around the sensor in natural orientation, in
|
||||
* pixels, for the current resolution.
|
||||
*
|
||||
* [overlayBounds] coordinates of the UI overlay in natural orientation, in pixels, for the current
|
||||
* resolution.
|
||||
*
|
||||
* [naturalDisplayWidth] width of the physical display in natural orientation, in pixels, for the
|
||||
* current resolution.
|
||||
*
|
||||
* [naturalDisplayHeight] height of the physical display in natural orientation, in pixels, for the
|
||||
* current resolution.
|
||||
*
|
||||
* [scaleFactor] ratio of a dimension in the current resolution to the corresponding dimension in
|
||||
* the native resolution.
|
||||
*
|
||||
* [rotation] current rotation of the display.
|
||||
*/
|
||||
public final class UdfpsOverlayParams {
|
||||
@NonNull
|
||||
private final Rect mSensorBounds;
|
||||
@NonNull
|
||||
private final Rect mOverlayBounds;
|
||||
private final int mNaturalDisplayWidth;
|
||||
private final int mNaturalDisplayHeight;
|
||||
private final float mScaleFactor;
|
||||
private final int mRotation;
|
||||
private final boolean mIsOptical;
|
||||
|
||||
public UdfpsOverlayParams(@NonNull Rect sensorBounds, @NonNull Rect overlayBounds,
|
||||
int naturalDisplayWidth, int naturalDisplayHeight, float scaleFactor, int rotation,
|
||||
boolean isOptical) {
|
||||
mSensorBounds = sensorBounds;
|
||||
mOverlayBounds = overlayBounds;
|
||||
mNaturalDisplayWidth = naturalDisplayWidth;
|
||||
mNaturalDisplayHeight = naturalDisplayHeight;
|
||||
mScaleFactor = scaleFactor;
|
||||
mRotation = rotation;
|
||||
mIsOptical = isOptical;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public Rect getSensorBounds() {
|
||||
return mSensorBounds;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public Rect getOverlayBounds() {
|
||||
return mOverlayBounds;
|
||||
}
|
||||
|
||||
public int getNaturalDisplayWidth() {
|
||||
return mNaturalDisplayWidth;
|
||||
}
|
||||
|
||||
public int getNaturalDisplayHeight() {
|
||||
return mNaturalDisplayHeight;
|
||||
}
|
||||
|
||||
public float getScaleFactor() {
|
||||
return mScaleFactor;
|
||||
}
|
||||
|
||||
public int getRotation() {
|
||||
return mRotation;
|
||||
}
|
||||
|
||||
public boolean isOptical() {
|
||||
return mIsOptical;
|
||||
}
|
||||
}
|
||||
@@ -53,6 +53,14 @@ public class FingerprintRepository {
|
||||
return prop != null && prop.isAnyUdfpsType();
|
||||
}
|
||||
|
||||
/**
|
||||
* The first sensor type is Side fps sensor or not
|
||||
*/
|
||||
public boolean canAssumeSfps() {
|
||||
FingerprintSensorPropertiesInternal prop = getFirstFingerprintSensorPropertiesInternal();
|
||||
return prop != null && prop.isAnySidefpsType();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get max possible number of fingerprints for a user
|
||||
*/
|
||||
@@ -78,6 +86,7 @@ public class FingerprintRepository {
|
||||
|
||||
@Nullable
|
||||
private FingerprintSensorPropertiesInternal getFirstFingerprintSensorPropertiesInternal() {
|
||||
// TODO(b/264827022) use API addAuthenticatorsRegisteredCallback
|
||||
final List<FingerprintSensorPropertiesInternal> props =
|
||||
mFingerprintManager.getSensorPropertiesInternal();
|
||||
return props.size() > 0 ? props.get(0) : null;
|
||||
|
||||
@@ -30,6 +30,8 @@ import com.android.settings.biometrics2.data.repository.FingerprintRepository;
|
||||
*/
|
||||
public class BiometricsRepositoryProviderImpl implements BiometricsRepositoryProvider {
|
||||
|
||||
private static volatile FingerprintRepository sFingerprintRepository;
|
||||
|
||||
/**
|
||||
* Get FingerprintRepository
|
||||
*/
|
||||
@@ -41,6 +43,13 @@ public class BiometricsRepositoryProviderImpl implements BiometricsRepositoryPro
|
||||
if (fingerprintManager == null) {
|
||||
return null;
|
||||
}
|
||||
return new FingerprintRepository(fingerprintManager);
|
||||
if (sFingerprintRepository == null) {
|
||||
synchronized (FingerprintRepository.class) {
|
||||
if (sFingerprintRepository == null) {
|
||||
sFingerprintRepository = new FingerprintRepository(fingerprintManager);
|
||||
}
|
||||
}
|
||||
}
|
||||
return sFingerprintRepository;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,12 +27,18 @@ import androidx.lifecycle.ViewModelProvider.AndroidViewModelFactory;
|
||||
import androidx.lifecycle.viewmodel.CreationExtras;
|
||||
|
||||
import com.android.internal.widget.LockPatternUtils;
|
||||
import com.android.settings.biometrics.fingerprint.FingerprintUpdater;
|
||||
import com.android.settings.biometrics2.data.repository.FingerprintRepository;
|
||||
import com.android.settings.biometrics2.ui.viewmodel.AutoCredentialViewModel;
|
||||
import com.android.settings.biometrics2.ui.viewmodel.AutoCredentialViewModel.ChallengeGenerator;
|
||||
import com.android.settings.biometrics2.ui.viewmodel.DeviceFoldedViewModel;
|
||||
import com.android.settings.biometrics2.ui.viewmodel.DeviceRotationViewModel;
|
||||
import com.android.settings.biometrics2.ui.viewmodel.FingerprintEnrollFindSensorViewModel;
|
||||
import com.android.settings.biometrics2.ui.viewmodel.FingerprintEnrollIntroViewModel;
|
||||
import com.android.settings.biometrics2.ui.viewmodel.FingerprintEnrollProgressViewModel;
|
||||
import com.android.settings.biometrics2.ui.viewmodel.FingerprintEnrollmentViewModel;
|
||||
import com.android.settings.overlay.FeatureFactory;
|
||||
import com.android.systemui.unfold.compat.ScreenSizeFoldProvider;
|
||||
|
||||
/**
|
||||
* View model factory for biometric enrollment fragment
|
||||
@@ -42,7 +48,7 @@ public class BiometricsViewModelFactory implements ViewModelProvider.Factory {
|
||||
private static final String TAG = "BiometricsViewModelFact";
|
||||
|
||||
public static final CreationExtras.Key<ChallengeGenerator> CHALLENGE_GENERATOR =
|
||||
new CreationExtras.Key<ChallengeGenerator>() {};
|
||||
new CreationExtras.Key<>() {};
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
@@ -59,7 +65,22 @@ public class BiometricsViewModelFactory implements ViewModelProvider.Factory {
|
||||
final BiometricsRepositoryProvider provider = FeatureFactory.getFactory(application)
|
||||
.getBiometricsRepositoryProvider();
|
||||
|
||||
if (modelClass.isAssignableFrom(FingerprintEnrollIntroViewModel.class)) {
|
||||
if (modelClass.isAssignableFrom(AutoCredentialViewModel.class)) {
|
||||
final LockPatternUtils lockPatternUtils =
|
||||
featureFactory.getSecurityFeatureProvider().getLockPatternUtils(application);
|
||||
final ChallengeGenerator challengeGenerator = extras.get(CHALLENGE_GENERATOR);
|
||||
if (challengeGenerator != null) {
|
||||
return (T) new AutoCredentialViewModel(application, lockPatternUtils,
|
||||
challengeGenerator);
|
||||
}
|
||||
} else if (modelClass.isAssignableFrom(DeviceFoldedViewModel.class)) {
|
||||
return (T) new DeviceFoldedViewModel(new ScreenSizeFoldProvider(application),
|
||||
application.getMainExecutor());
|
||||
} else if (modelClass.isAssignableFrom(DeviceRotationViewModel.class)) {
|
||||
return (T) new DeviceRotationViewModel(application);
|
||||
} else if (modelClass.isAssignableFrom(FingerprintEnrollFindSensorViewModel.class)) {
|
||||
return (T) new FingerprintEnrollFindSensorViewModel(application);
|
||||
} else if (modelClass.isAssignableFrom(FingerprintEnrollIntroViewModel.class)) {
|
||||
final FingerprintRepository repository = provider.getFingerprintRepository(application);
|
||||
if (repository != null) {
|
||||
return (T) new FingerprintEnrollIntroViewModel(application, repository);
|
||||
@@ -70,14 +91,9 @@ public class BiometricsViewModelFactory implements ViewModelProvider.Factory {
|
||||
return (T) new FingerprintEnrollmentViewModel(application, repository,
|
||||
application.getSystemService(KeyguardManager.class));
|
||||
}
|
||||
} else if (modelClass.isAssignableFrom(AutoCredentialViewModel.class)) {
|
||||
final LockPatternUtils lockPatternUtils =
|
||||
featureFactory.getSecurityFeatureProvider().getLockPatternUtils(application);
|
||||
final ChallengeGenerator challengeGenerator = extras.get(CHALLENGE_GENERATOR);
|
||||
if (challengeGenerator != null) {
|
||||
return (T) new AutoCredentialViewModel(application, lockPatternUtils,
|
||||
challengeGenerator);
|
||||
}
|
||||
} else if (modelClass.isAssignableFrom(FingerprintEnrollProgressViewModel.class)) {
|
||||
return (T) new FingerprintEnrollProgressViewModel(application,
|
||||
new FingerprintUpdater(application));
|
||||
}
|
||||
return create(modelClass);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
/*
|
||||
* Copyright (C) 2022 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.biometrics2.ui.model;
|
||||
|
||||
/**
|
||||
* Biometric Enrollment progress
|
||||
*/
|
||||
public final class EnrollmentProgress {
|
||||
|
||||
public static final int INITIAL_STEPS = -1;
|
||||
public static final int INITIAL_REMAINING = 0;
|
||||
|
||||
private final int mSteps;
|
||||
private final int mRemaining;
|
||||
|
||||
public EnrollmentProgress(int steps, int remaining) {
|
||||
mSteps = steps;
|
||||
mRemaining = remaining;
|
||||
}
|
||||
|
||||
public int getSteps() {
|
||||
return mSteps;
|
||||
}
|
||||
|
||||
public int getRemaining() {
|
||||
return mRemaining;
|
||||
}
|
||||
|
||||
public boolean isInitialStep() {
|
||||
return mSteps == INITIAL_STEPS;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return getClass().getSimpleName() + "@" + Integer.toHexString(hashCode())
|
||||
+ "{steps:" + mSteps + ", remaining:" + mRemaining + "}";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,209 @@
|
||||
/*
|
||||
* Copyright (C) 2022 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.biometrics2.ui.view;
|
||||
|
||||
import static android.hardware.fingerprint.FingerprintManager.ENROLL_FIND_SENSOR;
|
||||
import static android.view.View.OnClickListener;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Context;
|
||||
import android.os.Bundle;
|
||||
import android.util.Log;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.fragment.app.Fragment;
|
||||
import androidx.fragment.app.FragmentActivity;
|
||||
import androidx.lifecycle.Observer;
|
||||
import androidx.lifecycle.ViewModelProvider;
|
||||
|
||||
import com.android.settings.R;
|
||||
import com.android.settings.biometrics.fingerprint.FingerprintFindSensorAnimation;
|
||||
import com.android.settings.biometrics2.ui.model.EnrollmentProgress;
|
||||
import com.android.settings.biometrics2.ui.viewmodel.FingerprintEnrollFindSensorViewModel;
|
||||
import com.android.settings.biometrics2.ui.viewmodel.FingerprintEnrollProgressViewModel;
|
||||
|
||||
import com.google.android.setupcompat.template.FooterBarMixin;
|
||||
import com.google.android.setupcompat.template.FooterButton;
|
||||
import com.google.android.setupdesign.GlifLayout;
|
||||
|
||||
/**
|
||||
* Fragment explaining the side fingerprint sensor location for fingerprint enrollment.
|
||||
* It interacts with ProgressViewModel, and FingerprintFindSensorAnimation.
|
||||
* <pre>
|
||||
| Has | UDFPS | SFPS | Other (Rear FPS) |
|
||||
|---------------------|-------|------|------------------|
|
||||
| Primary button | Yes | No | No |
|
||||
| Illustration Lottie | Yes | Yes | No |
|
||||
| Animation | No | No | Depend on layout |
|
||||
| Progress ViewModel | No | Yes | Yes |
|
||||
| Orientation detect | No | Yes | No |
|
||||
| Foldable detect | No | Yes | No |
|
||||
</pre>
|
||||
*/
|
||||
public class FingerprintEnrollFindRfpsFragment extends Fragment {
|
||||
|
||||
private static final boolean DEBUG = false;
|
||||
private static final String TAG = "FingerprintEnrollFindRfpsFragment";
|
||||
|
||||
private FingerprintEnrollFindSensorViewModel mViewModel;
|
||||
private FingerprintEnrollProgressViewModel mPorgressViewModel;
|
||||
|
||||
private View mView;
|
||||
private GlifLayout mGlifLayout;
|
||||
private FooterBarMixin mFooterBarMixin;
|
||||
private final OnClickListener mOnSkipClickListener = (v) -> mViewModel.onSkipButtonClick();
|
||||
@Nullable private FingerprintFindSensorAnimation mAnimation;
|
||||
|
||||
private final Observer<EnrollmentProgress> mProgressObserver = progress -> {
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "mProgressObserver(" + progress + ")");
|
||||
}
|
||||
if (progress != null && !progress.isInitialStep()) {
|
||||
mViewModel.onStartButtonClick();
|
||||
}
|
||||
};
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container,
|
||||
@Nullable Bundle savedInstanceState) {
|
||||
final Context context = inflater.getContext();
|
||||
mView = inflater.inflate(R.layout.fingerprint_enroll_find_sensor, container, false);
|
||||
mGlifLayout = mView.findViewById(R.id.setup_wizard_layout);
|
||||
mFooterBarMixin = mGlifLayout.getMixin(FooterBarMixin.class);
|
||||
mFooterBarMixin.setSecondaryButton(
|
||||
new FooterButton.Builder(context)
|
||||
.setText(R.string.security_settings_fingerprint_enroll_enrolling_skip)
|
||||
.setButtonType(FooterButton.ButtonType.SKIP)
|
||||
.setTheme(R.style.SudGlifButton_Secondary)
|
||||
.build()
|
||||
);
|
||||
View animationView = mView.findViewById(R.id.fingerprint_sensor_location_animation);
|
||||
if (animationView instanceof FingerprintFindSensorAnimation) {
|
||||
mAnimation = (FingerprintFindSensorAnimation) animationView;
|
||||
}
|
||||
return mView;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
|
||||
super.onViewCreated(view, savedInstanceState);
|
||||
final Activity activity = getActivity();
|
||||
final GlifLayoutHelper glifLayoutHelper = new GlifLayoutHelper(activity, mGlifLayout);
|
||||
glifLayoutHelper.setHeaderText(
|
||||
R.string.security_settings_fingerprint_enroll_find_sensor_title);
|
||||
glifLayoutHelper.setDescriptionText(
|
||||
getText(R.string.security_settings_fingerprint_enroll_find_sensor_message));
|
||||
mFooterBarMixin.getSecondaryButton().setOnClickListener(mOnSkipClickListener);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStart() {
|
||||
super.onStart();
|
||||
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "onStart(), start looking for fingerprint, animation exist:"
|
||||
+ (mAnimation != null));
|
||||
}
|
||||
startLookingForFingerprint();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResume() {
|
||||
if (mAnimation != null) {
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "onResume(), start animation");
|
||||
}
|
||||
mAnimation.startAnimation();
|
||||
}
|
||||
super.onResume();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPause() {
|
||||
if (mAnimation != null) {
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "onPause(), pause animation");
|
||||
}
|
||||
mAnimation.pauseAnimation();
|
||||
}
|
||||
super.onPause();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStop() {
|
||||
super.onStop();
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "onStop(), stop looking for fingerprint, animation exist:"
|
||||
+ (mAnimation != null));
|
||||
}
|
||||
stopLookingForFingerprint();
|
||||
}
|
||||
|
||||
private void startLookingForFingerprint() {
|
||||
if (mPorgressViewModel.isEnrolling()) {
|
||||
Log.d(TAG, "startLookingForFingerprint(), failed because isEnrolling is true before"
|
||||
+ " starting");
|
||||
return;
|
||||
}
|
||||
|
||||
mPorgressViewModel.clearProgressLiveData();
|
||||
mPorgressViewModel.getProgressLiveData().observe(this, mProgressObserver);
|
||||
final boolean startResult = mPorgressViewModel.startEnrollment(ENROLL_FIND_SENSOR);
|
||||
if (!startResult) {
|
||||
Log.e(TAG, "startLookingForFingerprint(), failed to start enrollment");
|
||||
}
|
||||
}
|
||||
|
||||
private void stopLookingForFingerprint() {
|
||||
if (!mPorgressViewModel.isEnrolling()) {
|
||||
Log.d(TAG, "stopLookingForFingerprint(), failed because isEnrolling is false before"
|
||||
+ " stopping");
|
||||
return;
|
||||
}
|
||||
|
||||
mPorgressViewModel.getProgressLiveData().removeObserver(mProgressObserver);
|
||||
final boolean cancelResult = mPorgressViewModel.cancelEnrollment();
|
||||
if (!cancelResult) {
|
||||
Log.e(TAG, "stopLookingForFingerprint(), failed to cancel enrollment");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroy() {
|
||||
if (mAnimation != null) {
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "onDestroy(), stop animation");
|
||||
}
|
||||
mAnimation.stopAnimation();
|
||||
}
|
||||
super.onDestroy();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAttach(@NonNull Context context) {
|
||||
final FragmentActivity activity = getActivity();
|
||||
final ViewModelProvider provider = new ViewModelProvider(activity);
|
||||
mViewModel = provider.get(FingerprintEnrollFindSensorViewModel.class);
|
||||
mPorgressViewModel = provider.get(FingerprintEnrollProgressViewModel.class);
|
||||
super.onAttach(context);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,251 @@
|
||||
/*
|
||||
* Copyright (C) 2022 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.biometrics2.ui.view;
|
||||
|
||||
import static android.hardware.fingerprint.FingerprintManager.ENROLL_FIND_SENSOR;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Context;
|
||||
import android.os.Bundle;
|
||||
import android.util.Log;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.Surface;
|
||||
import android.view.View;
|
||||
import android.view.View.OnClickListener;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.annotation.RawRes;
|
||||
import androidx.fragment.app.Fragment;
|
||||
import androidx.fragment.app.FragmentActivity;
|
||||
import androidx.lifecycle.LiveData;
|
||||
import androidx.lifecycle.Observer;
|
||||
import androidx.lifecycle.ViewModelProvider;
|
||||
|
||||
import com.android.settings.R;
|
||||
import com.android.settings.biometrics2.ui.model.EnrollmentProgress;
|
||||
import com.android.settings.biometrics2.ui.viewmodel.DeviceFoldedViewModel;
|
||||
import com.android.settings.biometrics2.ui.viewmodel.DeviceRotationViewModel;
|
||||
import com.android.settings.biometrics2.ui.viewmodel.FingerprintEnrollFindSensorViewModel;
|
||||
import com.android.settings.biometrics2.ui.viewmodel.FingerprintEnrollProgressViewModel;
|
||||
import com.android.settingslib.widget.LottieColorUtils;
|
||||
|
||||
import com.airbnb.lottie.LottieAnimationView;
|
||||
import com.google.android.setupcompat.template.FooterBarMixin;
|
||||
import com.google.android.setupcompat.template.FooterButton;
|
||||
import com.google.android.setupdesign.GlifLayout;
|
||||
|
||||
/**
|
||||
* Fragment explaining the side fingerprint sensor location for fingerprint enrollment.
|
||||
* It interacts with ProgressViewModel, FoldCallback (for different lottie), and
|
||||
* LottieAnimationView.
|
||||
* <pre>
|
||||
| Has | UDFPS | SFPS | Other (Rear FPS) |
|
||||
|---------------------|-------|------|------------------|
|
||||
| Primary button | Yes | No | No |
|
||||
| Illustration Lottie | Yes | Yes | No |
|
||||
| Animation | No | No | Depend on layout |
|
||||
| Progress ViewModel | No | Yes | Yes |
|
||||
| Orientation detect | No | Yes | No |
|
||||
| Foldable detect | No | Yes | No |
|
||||
</pre>
|
||||
*/
|
||||
public class FingerprintEnrollFindSfpsFragment extends Fragment {
|
||||
|
||||
private static final boolean DEBUG = false;
|
||||
private static final String TAG = "FingerprintEnrollFindSfpsFragment";
|
||||
|
||||
private FingerprintEnrollFindSensorViewModel mViewModel;
|
||||
private FingerprintEnrollProgressViewModel mProgressViewModel;
|
||||
private DeviceRotationViewModel mRotationViewModel;
|
||||
private DeviceFoldedViewModel mFoldedViewModel;
|
||||
|
||||
private final Observer<Integer> mRotationObserver = rotation -> {
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "rotationObserver " + rotation);
|
||||
}
|
||||
if (rotation == null) {
|
||||
return;
|
||||
}
|
||||
onRotationChanged(rotation);
|
||||
};
|
||||
|
||||
@Surface.Rotation private int mAnimationRotation = -1;
|
||||
|
||||
private View mView;
|
||||
private GlifLayout mGlifLayout;
|
||||
private FooterBarMixin mFooterBarMixin;
|
||||
private final OnClickListener mOnSkipClickListener = (v) -> mViewModel.onSkipButtonClick();
|
||||
private LottieAnimationView mIllustrationLottie;
|
||||
|
||||
private final Observer<EnrollmentProgress> mProgressObserver = progress -> {
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "mProgressObserver(" + progress + ")");
|
||||
}
|
||||
if (progress != null && !progress.isInitialStep()) {
|
||||
mViewModel.onStartButtonClick();
|
||||
}
|
||||
};
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container,
|
||||
@Nullable Bundle savedInstanceState) {
|
||||
final Context context = inflater.getContext();
|
||||
mView = inflater.inflate(R.layout.sfps_enroll_find_sensor_layout, container, false);
|
||||
mGlifLayout = mView.findViewById(R.id.setup_wizard_layout);
|
||||
mIllustrationLottie = mView.findViewById(R.id.illustration_lottie);
|
||||
mFooterBarMixin = mGlifLayout.getMixin(FooterBarMixin.class);
|
||||
mFooterBarMixin.setSecondaryButton(
|
||||
new FooterButton.Builder(context)
|
||||
.setText(R.string.security_settings_fingerprint_enroll_enrolling_skip)
|
||||
.setButtonType(FooterButton.ButtonType.SKIP)
|
||||
.setTheme(R.style.SudGlifButton_Secondary)
|
||||
.build()
|
||||
);
|
||||
return mView;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
|
||||
super.onViewCreated(view, savedInstanceState);
|
||||
final Activity activity = getActivity();
|
||||
final GlifLayoutHelper glifLayoutHelper = new GlifLayoutHelper(activity, mGlifLayout);
|
||||
glifLayoutHelper.setHeaderText(R.string.security_settings_sfps_enroll_find_sensor_title);
|
||||
glifLayoutHelper.setDescriptionText(
|
||||
getText(R.string.security_settings_sfps_enroll_find_sensor_message));
|
||||
mFooterBarMixin.getSecondaryButton().setOnClickListener(mOnSkipClickListener);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStart() {
|
||||
super.onStart();
|
||||
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "onStart(), start looking for fingerprint");
|
||||
}
|
||||
startLookingForFingerprint();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResume() {
|
||||
super.onResume();
|
||||
final LiveData<Integer> rotationLiveData = mRotationViewModel.getLiveData();
|
||||
playLottieAnimation(rotationLiveData.getValue());
|
||||
rotationLiveData.observe(this, mRotationObserver);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPause() {
|
||||
mRotationViewModel.getLiveData().removeObserver(mRotationObserver);
|
||||
super.onPause();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStop() {
|
||||
super.onStop();
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "onStop(), stop looking for fingerprint");
|
||||
}
|
||||
stopLookingForFingerprint();
|
||||
}
|
||||
|
||||
private void startLookingForFingerprint() {
|
||||
if (mProgressViewModel.isEnrolling()) {
|
||||
Log.d(TAG, "startLookingForFingerprint(), failed because isEnrolling is true before"
|
||||
+ " starting");
|
||||
return;
|
||||
}
|
||||
|
||||
mProgressViewModel.clearProgressLiveData();
|
||||
mProgressViewModel.getProgressLiveData().observe(this, mProgressObserver);
|
||||
final boolean startResult = mProgressViewModel.startEnrollment(ENROLL_FIND_SENSOR);
|
||||
if (!startResult) {
|
||||
Log.e(TAG, "startLookingForFingerprint(), failed to start enrollment");
|
||||
}
|
||||
}
|
||||
|
||||
private void stopLookingForFingerprint() {
|
||||
if (!mProgressViewModel.isEnrolling()) {
|
||||
Log.d(TAG, "stopLookingForFingerprint(), failed because isEnrolling is false before"
|
||||
+ " stopping");
|
||||
return;
|
||||
}
|
||||
|
||||
mProgressViewModel.getProgressLiveData().removeObserver(mProgressObserver);
|
||||
final boolean cancelResult = mProgressViewModel.cancelEnrollment();
|
||||
if (!cancelResult) {
|
||||
Log.e(TAG, "stopLookingForFingerprint(), failed to cancel enrollment");
|
||||
}
|
||||
}
|
||||
|
||||
private void onRotationChanged(@Surface.Rotation int newRotation) {
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "onRotationChanged() from " + mAnimationRotation + " to " + newRotation);
|
||||
}
|
||||
if ((newRotation + 2) % 4 == mAnimationRotation) {
|
||||
playLottieAnimation(newRotation);
|
||||
}
|
||||
// Fragment will be re-created if it's changed between landscape and portrait, so no need to
|
||||
// handle other cases.
|
||||
}
|
||||
|
||||
private void playLottieAnimation(@Surface.Rotation int rotation) {
|
||||
@RawRes final int animationRawRes = getSfpsLottieAnimationRawRes(rotation);
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "play lottie animation " + animationRawRes
|
||||
+ ", previous rotation:" + mAnimationRotation + ", new rotation:" + rotation);
|
||||
}
|
||||
|
||||
mAnimationRotation = rotation;
|
||||
mIllustrationLottie.setAnimation(animationRawRes);
|
||||
LottieColorUtils.applyDynamicColors(getActivity(), mIllustrationLottie);
|
||||
mIllustrationLottie.setVisibility(View.VISIBLE);
|
||||
mIllustrationLottie.playAnimation();
|
||||
}
|
||||
|
||||
@RawRes
|
||||
private int getSfpsLottieAnimationRawRes(@Surface.Rotation int rotation) {
|
||||
final boolean isFolded = !Boolean.FALSE.equals(mFoldedViewModel.getLiveData().getValue());
|
||||
switch (rotation) {
|
||||
case Surface.ROTATION_90:
|
||||
return isFolded ? R.raw.fingerprint_edu_lottie_folded_top_left
|
||||
: R.raw.fingerprint_edu_lottie_portrait_top_left;
|
||||
case Surface.ROTATION_180 :
|
||||
return isFolded ? R.raw.fingerprint_edu_lottie_folded_bottom_left
|
||||
: R.raw.fingerprint_edu_lottie_landscape_bottom_left;
|
||||
case Surface.ROTATION_270 :
|
||||
return isFolded ? R.raw.fingerprint_edu_lottie_folded_bottom_right
|
||||
: R.raw.fingerprint_edu_lottie_portrait_bottom_right;
|
||||
default :
|
||||
return isFolded ? R.raw.fingerprint_edu_lottie_folded_top_right
|
||||
: R.raw.fingerprint_edu_lottie_landscape_top_right;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAttach(@NonNull Context context) {
|
||||
final FragmentActivity activity = getActivity();
|
||||
final ViewModelProvider provider = new ViewModelProvider(activity);
|
||||
mViewModel = provider.get(FingerprintEnrollFindSensorViewModel.class);
|
||||
mProgressViewModel = provider.get(FingerprintEnrollProgressViewModel.class);
|
||||
mRotationViewModel = provider.get(DeviceRotationViewModel.class);
|
||||
mFoldedViewModel = provider.get(DeviceFoldedViewModel.class);
|
||||
super.onAttach(context);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,115 @@
|
||||
/*
|
||||
* Copyright (C) 2022 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.biometrics2.ui.view;
|
||||
|
||||
import static android.view.View.OnClickListener;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Context;
|
||||
import android.os.Bundle;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.fragment.app.Fragment;
|
||||
import androidx.lifecycle.ViewModelProvider;
|
||||
|
||||
import com.android.settings.R;
|
||||
import com.android.settings.biometrics2.ui.viewmodel.FingerprintEnrollFindSensorViewModel;
|
||||
|
||||
import com.airbnb.lottie.LottieAnimationView;
|
||||
import com.google.android.setupcompat.template.FooterBarMixin;
|
||||
import com.google.android.setupcompat.template.FooterButton;
|
||||
import com.google.android.setupdesign.GlifLayout;
|
||||
|
||||
/**
|
||||
* Fragment explaining the under-display fingerprint sensor location for fingerprint enrollment.
|
||||
* It interacts with Primary button, and LottieAnimationView.
|
||||
* <pre>
|
||||
| Has | UDFPS | SFPS | Other (Rear FPS) |
|
||||
|---------------------|-------|------|------------------|
|
||||
| Primary button | Yes | No | No |
|
||||
| Illustration Lottie | Yes | Yes | No |
|
||||
| Animation | No | No | Depend on layout |
|
||||
| Progress ViewModel | No | Yes | Yes |
|
||||
| Orientation detect | No | Yes | No |
|
||||
| Foldable detect | No | Yes | No |
|
||||
</pre>
|
||||
*/
|
||||
public class FingerprintEnrollFindUdfpsFragment extends Fragment {
|
||||
|
||||
private FingerprintEnrollFindSensorViewModel mViewModel;
|
||||
|
||||
private View mView;
|
||||
private GlifLayout mGlifLayout;
|
||||
private FooterBarMixin mFooterBarMixin;
|
||||
private final OnClickListener mOnSkipClickListener = (v) -> mViewModel.onSkipButtonClick();
|
||||
private final OnClickListener mOnStartClickListener = (v) -> mViewModel.onStartButtonClick();
|
||||
private LottieAnimationView mIllustrationLottie;
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container,
|
||||
@Nullable Bundle savedInstanceState) {
|
||||
final Context context = inflater.getContext();
|
||||
mView = inflater.inflate(R.layout.udfps_enroll_find_sensor_layout, container, false);
|
||||
mGlifLayout = mView.findViewById(R.id.setup_wizard_layout);
|
||||
mIllustrationLottie = mView.findViewById(R.id.illustration_lottie);
|
||||
mFooterBarMixin = mGlifLayout.getMixin(FooterBarMixin.class);
|
||||
mFooterBarMixin.setSecondaryButton(
|
||||
new FooterButton.Builder(context)
|
||||
.setText(R.string.security_settings_fingerprint_enroll_enrolling_skip)
|
||||
.setButtonType(FooterButton.ButtonType.SKIP)
|
||||
.setTheme(R.style.SudGlifButton_Secondary)
|
||||
.build()
|
||||
);
|
||||
mFooterBarMixin.setPrimaryButton(
|
||||
new FooterButton.Builder(context)
|
||||
.setText(R.string.security_settings_udfps_enroll_find_sensor_start_button)
|
||||
.setButtonType(FooterButton.ButtonType.NEXT)
|
||||
.setTheme(R.style.SudGlifButton_Primary)
|
||||
.build()
|
||||
);
|
||||
return mView;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
|
||||
super.onViewCreated(view, savedInstanceState);
|
||||
final Activity activity = getActivity();
|
||||
final GlifLayoutHelper glifLayoutHelper = new GlifLayoutHelper(activity, mGlifLayout);
|
||||
glifLayoutHelper.setHeaderText(R.string.security_settings_udfps_enroll_find_sensor_title);
|
||||
glifLayoutHelper.setDescriptionText(
|
||||
getText(R.string.security_settings_udfps_enroll_find_sensor_message));
|
||||
mFooterBarMixin.getSecondaryButton().setOnClickListener(mOnSkipClickListener);
|
||||
mFooterBarMixin.getPrimaryButton().setOnClickListener(mOnStartClickListener);
|
||||
mIllustrationLottie.setOnClickListener(mOnStartClickListener);
|
||||
|
||||
if (mViewModel.isAccessibilityEnabled()) {
|
||||
mIllustrationLottie.setAnimation(R.raw.udfps_edu_a11y_lottie);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAttach(@NonNull Context context) {
|
||||
mViewModel = new ViewModelProvider(getActivity()).get(
|
||||
FingerprintEnrollFindSensorViewModel.class);
|
||||
super.onAttach(context);
|
||||
}
|
||||
}
|
||||
@@ -24,18 +24,17 @@ import static com.android.settings.biometrics2.ui.model.FingerprintEnrollIntroSt
|
||||
|
||||
import static com.google.android.setupdesign.util.DynamicColorPalette.ColorType.ACCENT;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.app.admin.DevicePolicyManager;
|
||||
import android.content.Context;
|
||||
import android.graphics.PorterDuff;
|
||||
import android.graphics.PorterDuffColorFilter;
|
||||
import android.os.Bundle;
|
||||
import android.text.Html;
|
||||
import android.text.TextUtils;
|
||||
import android.text.method.LinkMovementMethod;
|
||||
import android.util.Log;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.View.OnClickListener;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.TextView;
|
||||
@@ -69,6 +68,9 @@ public class FingerprintEnrollIntroFragment extends Fragment {
|
||||
private View mView = null;
|
||||
private FooterButton mPrimaryFooterButton = null;
|
||||
private FooterButton mSecondaryFooterButton = null;
|
||||
private final OnClickListener mOnNextClickListener = (v) -> mViewModel.onNextButtonClick();
|
||||
private final OnClickListener mOnSkipOrCancelClickListener =
|
||||
(v) -> mViewModel.onSkipOrCancelButtonClick();
|
||||
private ImageView mIconShield = null;
|
||||
private TextView mFooterMessage6 = null;
|
||||
@Nullable private PorterDuffColorFilter mIconColorFilter;
|
||||
@@ -150,8 +152,8 @@ public class FingerprintEnrollIntroFragment extends Fragment {
|
||||
|
||||
final Context context = view.getContext();
|
||||
|
||||
mPrimaryFooterButton.setOnClickListener(mViewModel::onNextButtonClick);
|
||||
mSecondaryFooterButton.setOnClickListener(mViewModel::onSkipOrCancelButtonClick);
|
||||
mPrimaryFooterButton.setOnClickListener(mOnNextClickListener);
|
||||
mSecondaryFooterButton.setOnClickListener(mOnSkipOrCancelClickListener);
|
||||
|
||||
if (mViewModel.canAssumeUdfps()) {
|
||||
mFooterMessage6.setVisibility(View.VISIBLE);
|
||||
@@ -165,15 +167,15 @@ public class FingerprintEnrollIntroFragment extends Fragment {
|
||||
? R.string.security_settings_fingerprint_enroll_introduction_cancel
|
||||
: R.string.security_settings_fingerprint_enroll_introduction_no_thanks);
|
||||
|
||||
final GlifLayoutHelper glifLayoutHelper = new GlifLayoutHelper(getActivity(), getLayout());
|
||||
if (mViewModel.isBiometricUnlockDisabledByAdmin()
|
||||
&& !mViewModel.isParentalConsentRequired()) {
|
||||
setHeaderText(
|
||||
getActivity(),
|
||||
glifLayoutHelper.setHeaderText(
|
||||
R.string.security_settings_fingerprint_enroll_introduction_title_unlock_disabled
|
||||
);
|
||||
getLayout().setDescriptionText(getDescriptionDisabledByAdmin(context));
|
||||
glifLayoutHelper.setDescriptionText(getDescriptionDisabledByAdmin(context));
|
||||
} else {
|
||||
setHeaderText(getActivity(),
|
||||
glifLayoutHelper.setHeaderText(
|
||||
R.string.security_settings_fingerprint_enroll_introduction_title);
|
||||
}
|
||||
observePageStatusLiveDataIfNeed();
|
||||
@@ -192,7 +194,7 @@ public class FingerprintEnrollIntroFragment extends Fragment {
|
||||
final RequireScrollMixin requireScrollMixin = getLayout()
|
||||
.getMixin(RequireScrollMixin.class);
|
||||
requireScrollMixin.requireScrollWithButton(getActivity(), mPrimaryFooterButton,
|
||||
getMoreButtonTextRes(), mViewModel::onNextButtonClick);
|
||||
getMoreButtonTextRes(), mOnNextClickListener);
|
||||
|
||||
// Always set true to setHasScrolledToBottom() before registering listener through
|
||||
// setOnRequireScrollStateChangedListener(), because listener will not be called if first
|
||||
@@ -253,21 +255,6 @@ public class FingerprintEnrollIntroFragment extends Fragment {
|
||||
}
|
||||
}
|
||||
|
||||
private void setHeaderText(@NonNull Activity activity, int resId) {
|
||||
TextView layoutTitle = getLayout().getHeaderTextView();
|
||||
CharSequence previousTitle = layoutTitle.getText();
|
||||
CharSequence title = activity.getText(resId);
|
||||
if (previousTitle != title) {
|
||||
if (!TextUtils.isEmpty(previousTitle)) {
|
||||
layoutTitle.setAccessibilityLiveRegion(View.ACCESSIBILITY_LIVE_REGION_POLITE);
|
||||
}
|
||||
getLayout().setHeaderText(title);
|
||||
getLayout().getHeaderTextView().setContentDescription(title);
|
||||
activity.setTitle(title);
|
||||
}
|
||||
getLayout().getHeaderTextView().setContentDescription(activity.getText(resId));
|
||||
}
|
||||
|
||||
void updateFooterButtons(@NonNull FingerprintEnrollIntroStatus status) {
|
||||
@StringRes final int scrollToBottomPrimaryResId =
|
||||
status.getEnrollableStatus() == FINGERPRINT_ENROLLABLE_OK
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
|
||||
package com.android.settings.biometrics2.ui.view;
|
||||
|
||||
import static androidx.activity.result.contract.ActivityResultContracts.StartActivityForResult;
|
||||
import static androidx.lifecycle.ViewModelProvider.AndroidViewModelFactory.APPLICATION_KEY;
|
||||
|
||||
import static com.android.settings.biometrics2.factory.BiometricsViewModelFactory.CHALLENGE_GENERATOR;
|
||||
@@ -23,24 +24,34 @@ import static com.android.settings.biometrics2.ui.viewmodel.AutoCredentialViewMo
|
||||
import static com.android.settings.biometrics2.ui.viewmodel.AutoCredentialViewModel.CREDENTIAL_FAIL_NEED_TO_CONFIRM_LOCK;
|
||||
import static com.android.settings.biometrics2.ui.viewmodel.AutoCredentialViewModel.CREDENTIAL_IS_GENERATING_CHALLENGE;
|
||||
import static com.android.settings.biometrics2.ui.viewmodel.AutoCredentialViewModel.CREDENTIAL_VALID;
|
||||
import static com.android.settings.biometrics2.ui.viewmodel.FingerprintEnrollFindSensorViewModel.FINGERPRINT_ENROLL_FIND_SENSOR_ACTION_DIALOG;
|
||||
import static com.android.settings.biometrics2.ui.viewmodel.FingerprintEnrollFindSensorViewModel.FINGERPRINT_ENROLL_FIND_SENSOR_ACTION_SKIP;
|
||||
import static com.android.settings.biometrics2.ui.viewmodel.FingerprintEnrollFindSensorViewModel.FINGERPRINT_ENROLL_FIND_SENSOR_ACTION_START;
|
||||
import static com.android.settings.biometrics2.ui.viewmodel.FingerprintEnrollFindSensorViewModel.FingerprintEnrollFindSensorAction;
|
||||
import static com.android.settings.biometrics2.ui.viewmodel.FingerprintEnrollIntroViewModel.FINGERPRINT_ENROLL_INTRO_ACTION_CONTINUE_ENROLL;
|
||||
import static com.android.settings.biometrics2.ui.viewmodel.FingerprintEnrollIntroViewModel.FINGERPRINT_ENROLL_INTRO_ACTION_DONE_AND_FINISH;
|
||||
import static com.android.settings.biometrics2.ui.viewmodel.FingerprintEnrollIntroViewModel.FINGERPRINT_ENROLL_INTRO_ACTION_SKIP_OR_CANCEL;
|
||||
import static com.android.settings.biometrics2.ui.viewmodel.FingerprintEnrollIntroViewModel.FingerprintEnrollIntroAction;
|
||||
|
||||
import android.annotation.StyleRes;
|
||||
import android.app.Application;
|
||||
import android.content.Intent;
|
||||
import android.content.res.ColorStateList;
|
||||
import android.content.res.Resources;
|
||||
import android.graphics.Color;
|
||||
import android.os.Bundle;
|
||||
import android.util.Log;
|
||||
|
||||
import androidx.activity.result.ActivityResult;
|
||||
import androidx.activity.result.ActivityResultCallback;
|
||||
import androidx.activity.result.ActivityResultLauncher;
|
||||
import androidx.activity.result.contract.ActivityResultContracts;
|
||||
import androidx.annotation.ColorInt;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.fragment.app.Fragment;
|
||||
import androidx.fragment.app.FragmentActivity;
|
||||
import androidx.fragment.app.FragmentManager;
|
||||
import androidx.lifecycle.Observer;
|
||||
import androidx.lifecycle.ViewModelProvider;
|
||||
import androidx.lifecycle.viewmodel.CreationExtras;
|
||||
import androidx.lifecycle.viewmodel.MutableCreationExtras;
|
||||
@@ -48,14 +59,16 @@ import androidx.lifecycle.viewmodel.MutableCreationExtras;
|
||||
import com.android.settings.R;
|
||||
import com.android.settings.Utils;
|
||||
import com.android.settings.biometrics.BiometricEnrollBase;
|
||||
import com.android.settings.biometrics.fingerprint.FingerprintEnrollFindSensor;
|
||||
import com.android.settings.biometrics.fingerprint.SetupFingerprintEnrollFindSensor;
|
||||
import com.android.settings.biometrics.fingerprint.FingerprintEnrollEnrolling;
|
||||
import com.android.settings.biometrics.fingerprint.SetupFingerprintEnrollEnrolling;
|
||||
import com.android.settings.biometrics2.data.repository.FingerprintRepository;
|
||||
import com.android.settings.biometrics2.factory.BiometricsViewModelFactory;
|
||||
import com.android.settings.biometrics2.ui.model.EnrollmentRequest;
|
||||
import com.android.settings.biometrics2.ui.viewmodel.AutoCredentialViewModel;
|
||||
import com.android.settings.biometrics2.ui.viewmodel.AutoCredentialViewModel.FingerprintChallengeGenerator;
|
||||
import com.android.settings.biometrics2.ui.viewmodel.FingerprintEnrollFindSensorViewModel;
|
||||
import com.android.settings.biometrics2.ui.viewmodel.FingerprintEnrollIntroViewModel;
|
||||
import com.android.settings.biometrics2.ui.viewmodel.FingerprintEnrollProgressViewModel;
|
||||
import com.android.settings.biometrics2.ui.viewmodel.FingerprintEnrollmentViewModel;
|
||||
import com.android.settings.overlay.FeatureFactory;
|
||||
|
||||
@@ -66,37 +79,57 @@ import com.google.android.setupdesign.util.ThemeHelper;
|
||||
*/
|
||||
public class FingerprintEnrollmentActivity extends FragmentActivity {
|
||||
|
||||
private static final boolean DEBUG = false;
|
||||
private static final String TAG = "FingerprintEnrollmentActivity";
|
||||
|
||||
private static final String INTRO_TAG = "enroll-intro";
|
||||
private static final String FIND_UDFPS_TAG = "enroll-find-udfps";
|
||||
private static final String FIND_SFPS_TAG = "enroll-find-sfps";
|
||||
private static final String FIND_RFPS_TAG = "enroll-find-rfps";
|
||||
private static final String SKIP_SETUP_FIND_FPS_DIALOG_TAG = "skip-setup-dialog";
|
||||
|
||||
protected static final int LAUNCH_CONFIRM_LOCK_ACTIVITY = 1;
|
||||
|
||||
private ViewModelProvider mViewModelProvider;
|
||||
private FingerprintEnrollmentViewModel mViewModel;
|
||||
private AutoCredentialViewModel mAutoCredentialViewModel;
|
||||
private ActivityResultLauncher<Intent> mNextActivityLauncher;
|
||||
private ActivityResultLauncher<Intent> mChooseLockLauncher;
|
||||
private final Observer<Integer> mIntroActionObserver = action -> {
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "mIntroActionObserver(" + action + ")");
|
||||
}
|
||||
if (action != null) {
|
||||
onIntroAction(action);
|
||||
}
|
||||
};
|
||||
private final Observer<Integer> mFindSensorActionObserver = action -> {
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "mFindSensorActionObserver(" + action + ")");
|
||||
}
|
||||
if (action != null) {
|
||||
onFindSensorAction(action);
|
||||
}
|
||||
};
|
||||
private final ActivityResultCallback<ActivityResult> mNextActivityResultCallback =
|
||||
result -> mViewModel.onContinueEnrollActivityResult(result,
|
||||
mAutoCredentialViewModel.getUserId());
|
||||
private final ActivityResultLauncher<Intent> mNextActivityLauncher =
|
||||
registerForActivityResult(new StartActivityForResult(), mNextActivityResultCallback);
|
||||
private final ActivityResultCallback<ActivityResult> mChooseLockResultCallback =
|
||||
result -> onChooseOrConfirmLockResult(true /* isChooseLock */, result);
|
||||
private final ActivityResultLauncher<Intent> mChooseLockLauncher =
|
||||
registerForActivityResult(new StartActivityForResult(), mChooseLockResultCallback);
|
||||
|
||||
@Override
|
||||
protected void onCreate(@Nullable Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
mNextActivityLauncher = registerForActivityResult(
|
||||
new ActivityResultContracts.StartActivityForResult(),
|
||||
(it) -> mViewModel.onContinueEnrollActivityResult(
|
||||
it,
|
||||
mAutoCredentialViewModel.getUserId())
|
||||
);
|
||||
mChooseLockLauncher = registerForActivityResult(
|
||||
new ActivityResultContracts.StartActivityForResult(),
|
||||
(it) -> onChooseOrConfirmLockResult(true, it)
|
||||
);
|
||||
mViewModelProvider = new ViewModelProvider(this);
|
||||
|
||||
ViewModelProvider viewModelProvider = new ViewModelProvider(this);
|
||||
|
||||
mViewModel = viewModelProvider.get(FingerprintEnrollmentViewModel.class);
|
||||
mViewModel = mViewModelProvider.get(FingerprintEnrollmentViewModel.class);
|
||||
mViewModel.setRequest(new EnrollmentRequest(getIntent(), getApplicationContext()));
|
||||
mViewModel.setSavedInstanceState(savedInstanceState);
|
||||
|
||||
mAutoCredentialViewModel = viewModelProvider.get(AutoCredentialViewModel.class);
|
||||
mAutoCredentialViewModel = mViewModelProvider.get(AutoCredentialViewModel.class);
|
||||
mAutoCredentialViewModel.setCredentialModel(savedInstanceState, getIntent());
|
||||
|
||||
// Theme
|
||||
@@ -106,20 +139,37 @@ public class FingerprintEnrollmentActivity extends FragmentActivity {
|
||||
|
||||
// fragment
|
||||
setContentView(R.layout.biometric_enrollment_container);
|
||||
final FingerprintEnrollIntroViewModel introViewModel =
|
||||
viewModelProvider.get(FingerprintEnrollIntroViewModel.class);
|
||||
introViewModel.setEnrollmentRequest(mViewModel.getRequest());
|
||||
introViewModel.setUserId(mAutoCredentialViewModel.getUserId());
|
||||
|
||||
if (DEBUG) {
|
||||
Log.e(TAG, "onCreate() has savedInstance:" + (savedInstanceState != null));
|
||||
}
|
||||
if (savedInstanceState == null) {
|
||||
checkCredential();
|
||||
|
||||
final String tag = "FingerprintEnrollIntroFragment";
|
||||
getSupportFragmentManager().beginTransaction()
|
||||
.setReorderingAllowed(true)
|
||||
.add(R.id.fragment_container_view, FingerprintEnrollIntroFragment.class, null,
|
||||
tag)
|
||||
.commit();
|
||||
startIntroFragment();
|
||||
} else {
|
||||
final FragmentManager manager = getSupportFragmentManager();
|
||||
String[] tags = new String[] {
|
||||
FIND_UDFPS_TAG,
|
||||
FIND_SFPS_TAG,
|
||||
FIND_RFPS_TAG,
|
||||
INTRO_TAG
|
||||
};
|
||||
for (String tag: tags) {
|
||||
final Fragment fragment = manager.findFragmentByTag(tag);
|
||||
if (fragment == null) {
|
||||
continue;
|
||||
}
|
||||
if (DEBUG) {
|
||||
Log.e(TAG, "onCreate() currentFragment:" + tag);
|
||||
}
|
||||
if (tag.equals(INTRO_TAG)) {
|
||||
attachIntroViewModel();
|
||||
} else { // FIND_UDFPS_TAG, FIND_SFPS_TAG, FIND_RFPS_TAG
|
||||
attachFindSensorViewModel();
|
||||
attachIntroViewModel();
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// observe LiveData
|
||||
@@ -128,11 +178,80 @@ public class FingerprintEnrollmentActivity extends FragmentActivity {
|
||||
|
||||
mAutoCredentialViewModel.getGenerateChallengeFailedLiveData().observe(this,
|
||||
this::onGenerateChallengeFailed);
|
||||
}
|
||||
|
||||
private void startIntroFragment() {
|
||||
attachIntroViewModel();
|
||||
getSupportFragmentManager().beginTransaction()
|
||||
.setReorderingAllowed(true)
|
||||
.replace(R.id.fragment_container_view, FingerprintEnrollIntroFragment.class, null,
|
||||
INTRO_TAG)
|
||||
.commit();
|
||||
}
|
||||
|
||||
private void attachIntroViewModel() {
|
||||
final FingerprintEnrollIntroViewModel introViewModel =
|
||||
mViewModelProvider.get(FingerprintEnrollIntroViewModel.class);
|
||||
|
||||
introViewModel.setEnrollmentRequest(mViewModel.getRequest());
|
||||
introViewModel.setUserId(mAutoCredentialViewModel.getUserId());
|
||||
|
||||
// Clear ActionLiveData in FragmentViewModel to prevent getting previous action during
|
||||
// recreate, like press 'I agree' then press 'back' in FingerprintEnrollFindSensor activity.
|
||||
// recreate, like press 'Agree' then press 'back' in FingerprintEnrollFindSensor activity.
|
||||
introViewModel.clearActionLiveData();
|
||||
introViewModel.getActionLiveData().observe(this, this::observeIntroAction);
|
||||
introViewModel.getActionLiveData().observe(this, mIntroActionObserver);
|
||||
}
|
||||
|
||||
// We need to make sure token is valid before entering find sensor page
|
||||
private void startFindSensorFragment() {
|
||||
attachFindSensorViewModel();
|
||||
if (mViewModel.canAssumeUdfps()) {
|
||||
// UDFPS does not need to start real fingerprint enrolling during finding sensor
|
||||
startFindFpsFragmentWithProgressViewModel(FingerprintEnrollFindUdfpsFragment.class,
|
||||
FIND_UDFPS_TAG, false /* initProgressViewModel */);
|
||||
} else if (mViewModel.canAssumeSfps()) {
|
||||
startFindFpsFragmentWithProgressViewModel(FingerprintEnrollFindSfpsFragment.class,
|
||||
FIND_SFPS_TAG, true /* initProgressViewModel */);
|
||||
} else {
|
||||
startFindFpsFragmentWithProgressViewModel(FingerprintEnrollFindRfpsFragment.class,
|
||||
FIND_RFPS_TAG, true /* initProgressViewModel */);
|
||||
}
|
||||
}
|
||||
|
||||
private void startFindFpsFragmentWithProgressViewModel(
|
||||
@NonNull Class<? extends Fragment> findFpsClass, @NonNull String tag,
|
||||
boolean initProgressViewModel) {
|
||||
if (initProgressViewModel) {
|
||||
final FingerprintEnrollProgressViewModel progressViewModel =
|
||||
mViewModelProvider.get(FingerprintEnrollProgressViewModel.class);
|
||||
progressViewModel.setUserId(mAutoCredentialViewModel.getUserId());
|
||||
progressViewModel.setToken(mAutoCredentialViewModel.getToken());
|
||||
}
|
||||
final FingerprintEnrollFindSensorViewModel findSensorViewModel =
|
||||
mViewModelProvider.get(FingerprintEnrollFindSensorViewModel.class);
|
||||
findSensorViewModel.setIsSuw(mViewModel.getRequest().isSuw());
|
||||
getSupportFragmentManager().beginTransaction()
|
||||
.setReorderingAllowed(true)
|
||||
.setCustomAnimations(R.anim.sud_slide_next_in, R.anim.sud_slide_next_out,
|
||||
R.anim.sud_slide_back_in, R.anim.sud_slide_back_out)
|
||||
.replace(R.id.fragment_container_view, findFpsClass, null, tag)
|
||||
.addToBackStack(tag)
|
||||
.commit();
|
||||
}
|
||||
|
||||
private void attachFindSensorViewModel() {
|
||||
final FingerprintEnrollFindSensorViewModel findSensorViewModel =
|
||||
mViewModelProvider.get(FingerprintEnrollFindSensorViewModel.class);
|
||||
|
||||
// Clear ActionLiveData in FragmentViewModel to prevent getting previous action during
|
||||
// recreate, like press 'Start' then press 'back' in FingerprintEnrollEnrolling activity.
|
||||
findSensorViewModel.clearActionLiveData();
|
||||
findSensorViewModel.getActionLiveData().observe(this, mFindSensorActionObserver);
|
||||
}
|
||||
|
||||
private void startSkipSetupFindFpsDialog() {
|
||||
new SkipSetupFindFpsDialog().show(getSupportFragmentManager(),
|
||||
SKIP_SETUP_FIND_FPS_DIALOG_TAG);
|
||||
}
|
||||
|
||||
private void onGenerateChallengeFailed(@NonNull Boolean ignoredBoolean) {
|
||||
@@ -217,10 +336,7 @@ public class FingerprintEnrollmentActivity extends FragmentActivity {
|
||||
}
|
||||
}
|
||||
|
||||
private void observeIntroAction(@Nullable Integer action) {
|
||||
if (action == null) {
|
||||
return;
|
||||
}
|
||||
private void onIntroAction(@FingerprintEnrollIntroAction int action) {
|
||||
switch (action) {
|
||||
case FINGERPRINT_ENROLL_INTRO_ACTION_DONE_AND_FINISH: {
|
||||
onSetActivityResult(
|
||||
@@ -233,13 +349,30 @@ public class FingerprintEnrollmentActivity extends FragmentActivity {
|
||||
return;
|
||||
}
|
||||
case FINGERPRINT_ENROLL_INTRO_ACTION_CONTINUE_ENROLL: {
|
||||
startFindSensorFragment();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void onFindSensorAction(@FingerprintEnrollFindSensorAction int action) {
|
||||
switch (action) {
|
||||
case FINGERPRINT_ENROLL_FIND_SENSOR_ACTION_SKIP: {
|
||||
onSetActivityResult(
|
||||
new ActivityResult(BiometricEnrollBase.RESULT_SKIP, null));
|
||||
return;
|
||||
}
|
||||
case FINGERPRINT_ENROLL_FIND_SENSOR_ACTION_DIALOG: {
|
||||
startSkipSetupFindFpsDialog();
|
||||
return;
|
||||
}
|
||||
case FINGERPRINT_ENROLL_FIND_SENSOR_ACTION_START: {
|
||||
final boolean isSuw = mViewModel.getRequest().isSuw();
|
||||
if (!mViewModel.isWaitingActivityResult().compareAndSet(false, true)) {
|
||||
Log.w(TAG, "startNext, isSuw:" + isSuw + ", fail to set isWaiting flag");
|
||||
}
|
||||
final Intent intent = new Intent(this, isSuw
|
||||
? SetupFingerprintEnrollFindSensor.class
|
||||
: FingerprintEnrollFindSensor.class);
|
||||
Intent intent = new Intent(this, isSuw
|
||||
? SetupFingerprintEnrollEnrolling.class
|
||||
: FingerprintEnrollEnrolling.class);
|
||||
intent.putExtras(mAutoCredentialViewModel.createCredentialIntentExtra());
|
||||
intent.putExtras(mViewModel.getNextActivityBaseIntentExtras());
|
||||
mNextActivityLauncher.launch(intent);
|
||||
@@ -253,6 +386,12 @@ public class FingerprintEnrollmentActivity extends FragmentActivity {
|
||||
mViewModel.checkFinishActivityDuringOnPause(isFinishing(), isChangingConfigurations());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onApplyThemeResource(Resources.Theme theme, @StyleRes int resid, boolean first) {
|
||||
theme.applyStyle(R.style.SetupWizardPartnerResource, true);
|
||||
super.onApplyThemeResource(theme, resid, first);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
|
||||
if (requestCode == LAUNCH_CONFIRM_LOCK_ACTIVITY) {
|
||||
|
||||
@@ -0,0 +1,70 @@
|
||||
/*
|
||||
* Copyright (C) 2022 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.biometrics2.ui.view;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.text.TextUtils;
|
||||
import android.view.View;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.StringRes;
|
||||
|
||||
import com.google.android.setupdesign.GlifLayout;
|
||||
|
||||
/**
|
||||
* Utils class for GlifLayout
|
||||
*/
|
||||
public class GlifLayoutHelper {
|
||||
|
||||
@NonNull private final Activity mActivity;
|
||||
@NonNull private final GlifLayout mGlifLayout;
|
||||
|
||||
public GlifLayoutHelper(@NonNull Activity activity, @NonNull GlifLayout glifLayout) {
|
||||
mActivity = activity;
|
||||
mGlifLayout = glifLayout;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets header text to GlifLayout
|
||||
*/
|
||||
public void setHeaderText(@StringRes int textResId) {
|
||||
TextView layoutTitle = mGlifLayout.getHeaderTextView();
|
||||
CharSequence previousTitle = layoutTitle.getText();
|
||||
CharSequence title = mActivity.getText(textResId);
|
||||
if (previousTitle != title) {
|
||||
if (!TextUtils.isEmpty(previousTitle)) {
|
||||
layoutTitle.setAccessibilityLiveRegion(View.ACCESSIBILITY_LIVE_REGION_POLITE);
|
||||
}
|
||||
mGlifLayout.setHeaderText(title);
|
||||
mGlifLayout.getHeaderTextView().setContentDescription(title);
|
||||
mActivity.setTitle(title);
|
||||
}
|
||||
mGlifLayout.getHeaderTextView().setContentDescription(title);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets description text to GlifLayout
|
||||
*/
|
||||
public void setDescriptionText(CharSequence description) {
|
||||
CharSequence previousDescription = mGlifLayout.getDescriptionText();
|
||||
// Prevent a11y for re-reading the same string
|
||||
if (!TextUtils.equals(previousDescription, description)) {
|
||||
mGlifLayout.setDescriptionText(description);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
/*
|
||||
* Copyright (C) 2022 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.biometrics2.ui.view;
|
||||
|
||||
import static android.content.DialogInterface.OnClickListener;
|
||||
|
||||
import android.app.Dialog;
|
||||
import android.app.settings.SettingsEnums;
|
||||
import android.content.Context;
|
||||
import android.os.Bundle;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
import androidx.lifecycle.ViewModelProvider;
|
||||
|
||||
import com.android.settings.R;
|
||||
import com.android.settings.biometrics2.ui.viewmodel.FingerprintEnrollFindSensorViewModel;
|
||||
import com.android.settings.core.instrumentation.InstrumentedDialogFragment;
|
||||
|
||||
/**
|
||||
* Skip dialog which shows when user clicks "Do it later" button in FingerprintFindSensor page.
|
||||
*/
|
||||
public class SkipSetupFindFpsDialog extends InstrumentedDialogFragment {
|
||||
|
||||
private FingerprintEnrollFindSensorViewModel mViewModel;
|
||||
private final OnClickListener mOnSkipClickListener =
|
||||
(d, w) -> mViewModel.onSkipDialogButtonClick();
|
||||
|
||||
@Override
|
||||
public int getMetricsCategory() {
|
||||
return SettingsEnums.DIALOG_FINGERPRINT_SKIP_SETUP;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Dialog onCreateDialog(Bundle savedInstanceState) {
|
||||
return onCreateDialogBuilder().create();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns builder for this dialog
|
||||
*/
|
||||
@NonNull
|
||||
private AlertDialog.Builder onCreateDialogBuilder() {
|
||||
return new AlertDialog.Builder(getActivity(), R.style.Theme_AlertDialog)
|
||||
.setTitle(R.string.setup_fingerprint_enroll_skip_title)
|
||||
.setPositiveButton(R.string.skip_anyway_button_label, mOnSkipClickListener)
|
||||
.setNegativeButton(R.string.go_back_button_label, null)
|
||||
.setMessage(R.string.setup_fingerprint_enroll_skip_after_adding_lock_text);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAttach(Context context) {
|
||||
mViewModel = new ViewModelProvider(getActivity()).get(
|
||||
FingerprintEnrollFindSensorViewModel.class);
|
||||
super.onAttach(context);
|
||||
}
|
||||
}
|
||||
@@ -253,7 +253,11 @@ public class AutoCredentialViewModel extends AndroidViewModel {
|
||||
if (isUnspecifiedPassword()) {
|
||||
return CREDENTIAL_FAIL_NEED_TO_CHOOSE_LOCK;
|
||||
} else if (mCredentialModel.isValidGkPwHandle()) {
|
||||
generateChallenge(mCredentialModel.getGkPwHandle());
|
||||
final long gkPwHandle = mCredentialModel.getGkPwHandle();
|
||||
mCredentialModel.clearGkPwHandle();
|
||||
// GkPwHandle is got through caller activity, we shall not revoke it after
|
||||
// generateChallenge(). Let caller activity to make decision.
|
||||
generateChallenge(gkPwHandle, false /* revokeGkPwHandle */);
|
||||
mIsGeneratingChallengeDuringCheckingCredential = true;
|
||||
return CREDENTIAL_IS_GENERATING_CHALLENGE;
|
||||
} else {
|
||||
@@ -261,7 +265,7 @@ public class AutoCredentialViewModel extends AndroidViewModel {
|
||||
}
|
||||
}
|
||||
|
||||
private void generateChallenge(long gkPwHandle) {
|
||||
private void generateChallenge(long gkPwHandle, boolean revokeGkPwHandle) {
|
||||
mChallengeGenerator.setCallback((sensorId, userId, challenge) -> {
|
||||
try {
|
||||
final byte[] newToken = requestGatekeeperHat(gkPwHandle, challenge, userId);
|
||||
@@ -274,11 +278,13 @@ public class AutoCredentialViewModel extends AndroidViewModel {
|
||||
return;
|
||||
}
|
||||
|
||||
mLockPatternUtils.removeGatekeeperPasswordHandle(gkPwHandle);
|
||||
mCredentialModel.clearGkPwHandle();
|
||||
if (revokeGkPwHandle) {
|
||||
mLockPatternUtils.removeGatekeeperPasswordHandle(gkPwHandle);
|
||||
}
|
||||
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "generateChallenge " + mCredentialModel);
|
||||
Log.d(TAG, "generateChallenge(), model:" + mCredentialModel
|
||||
+ ", revokeGkPwHandle:" + revokeGkPwHandle);
|
||||
}
|
||||
|
||||
// Check credential again
|
||||
@@ -314,7 +320,9 @@ public class AutoCredentialViewModel extends AndroidViewModel {
|
||||
if (data != null) {
|
||||
final long gkPwHandle = result.getData().getLongExtra(
|
||||
EXTRA_KEY_GK_PW_HANDLE, INVALID_GK_PW_HANDLE);
|
||||
generateChallenge(gkPwHandle);
|
||||
// Revoke self requested GkPwHandle because it shall only used once inside this
|
||||
// activity lifecycle.
|
||||
generateChallenge(gkPwHandle, true /* revokeGkPwHandle */);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -328,6 +336,14 @@ public class AutoCredentialViewModel extends AndroidViewModel {
|
||||
return mCredentialModel.getUserId();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get userId for this credential
|
||||
*/
|
||||
@Nullable
|
||||
public byte[] getToken() {
|
||||
return mCredentialModel.getToken();
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private byte[] requestGatekeeperHat(long gkPwHandle, long challenge, int userId)
|
||||
throws IllegalStateException {
|
||||
|
||||
@@ -0,0 +1,66 @@
|
||||
/*
|
||||
* Copyright (C) 2023 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.settings.biometrics2.ui.viewmodel;
|
||||
|
||||
import android.util.Log;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.lifecycle.LiveData;
|
||||
import androidx.lifecycle.MutableLiveData;
|
||||
import androidx.lifecycle.ViewModel;
|
||||
|
||||
import com.android.systemui.unfold.compat.ScreenSizeFoldProvider;
|
||||
import com.android.systemui.unfold.updates.FoldProvider;
|
||||
|
||||
import java.util.concurrent.Executor;
|
||||
|
||||
/**
|
||||
* ViewModel explaining the fingerprint sensor location for fingerprint enrollment.
|
||||
*/
|
||||
public class DeviceFoldedViewModel extends ViewModel {
|
||||
|
||||
private static final String TAG = "DeviceFoldedViewModel";
|
||||
|
||||
@NonNull private final MutableLiveData<Boolean> mLiveData =
|
||||
new MutableLiveData<>(null);
|
||||
|
||||
private final ScreenSizeFoldProvider mScreenSizeFoldProvider;
|
||||
private final FoldProvider.FoldCallback mIsFoldedCallback = isFolded -> {
|
||||
Log.d(TAG, "onFoldUpdated= " + isFolded);
|
||||
mLiveData.postValue(isFolded);
|
||||
};
|
||||
|
||||
public DeviceFoldedViewModel(@NonNull ScreenSizeFoldProvider screenSizeFoldProvider,
|
||||
@NonNull Executor executor) {
|
||||
super();
|
||||
mScreenSizeFoldProvider = screenSizeFoldProvider;
|
||||
mScreenSizeFoldProvider.registerCallback(mIsFoldedCallback, executor);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns FoldedLiveData
|
||||
*/
|
||||
public LiveData<Boolean> getLiveData() {
|
||||
return mLiveData;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onCleared() {
|
||||
mScreenSizeFoldProvider.unregisterCallback(mIsFoldedCallback);
|
||||
super.onCleared();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
/*
|
||||
* Copyright (C) 2022 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.biometrics2.ui.viewmodel;
|
||||
|
||||
import static android.hardware.display.DisplayManager.DisplayListener;
|
||||
|
||||
import android.app.Application;
|
||||
import android.hardware.display.DisplayManager;
|
||||
import android.util.Log;
|
||||
import android.view.DisplayInfo;
|
||||
import android.view.Surface;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.lifecycle.AndroidViewModel;
|
||||
import androidx.lifecycle.LiveData;
|
||||
import androidx.lifecycle.MutableLiveData;
|
||||
|
||||
/**
|
||||
* ViewModel explaining the fingerprint sensor location for fingerprint enrollment.
|
||||
*/
|
||||
public class DeviceRotationViewModel extends AndroidViewModel {
|
||||
|
||||
private static final boolean DEBUG = false;
|
||||
private static final String TAG = "RotationViewModel";
|
||||
|
||||
private final DisplayManager mDisplayManager;
|
||||
@NonNull private final DisplayInfo mDisplayInfo = new DisplayInfo();
|
||||
private final DisplayListener mDisplayListener = new DisplayListener() {
|
||||
@Override
|
||||
public void onDisplayAdded(int displayId) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDisplayRemoved(int displayId) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDisplayChanged(int displayId) {
|
||||
final int rotation = getRotation();
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "onDisplayChanged(" + displayId + "), rotation:" + rotation);
|
||||
}
|
||||
mLiveData.postValue(rotation);
|
||||
}
|
||||
};
|
||||
|
||||
@NonNull private final MutableLiveData<Integer> mLiveData =
|
||||
new MutableLiveData<>(getRotation());
|
||||
|
||||
public DeviceRotationViewModel(@NonNull Application application) {
|
||||
super(application);
|
||||
mDisplayManager = application.getSystemService(DisplayManager.class);
|
||||
mDisplayManager.registerDisplayListener(mDisplayListener,
|
||||
application.getMainThreadHandler());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns current rotation
|
||||
*/
|
||||
@Surface.Rotation
|
||||
private int getRotation() {
|
||||
getApplication().getDisplay().getDisplayInfo(mDisplayInfo);
|
||||
return mDisplayInfo.rotation;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns RotationLiveData
|
||||
*/
|
||||
public LiveData<Integer> getLiveData() {
|
||||
return mLiveData;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onCleared() {
|
||||
mDisplayManager.unregisterDisplayListener(mDisplayListener);
|
||||
super.onCleared();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,135 @@
|
||||
/*
|
||||
* Copyright (C) 2022 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.biometrics2.ui.viewmodel;
|
||||
|
||||
import android.annotation.IntDef;
|
||||
import android.app.Application;
|
||||
import android.util.Log;
|
||||
import android.view.accessibility.AccessibilityManager;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.lifecycle.AndroidViewModel;
|
||||
import androidx.lifecycle.LiveData;
|
||||
import androidx.lifecycle.MutableLiveData;
|
||||
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
|
||||
/**
|
||||
* ViewModel explaining the fingerprint sensor location for fingerprint enrollment.
|
||||
*/
|
||||
public class FingerprintEnrollFindSensorViewModel extends AndroidViewModel {
|
||||
|
||||
private static final boolean DEBUG = false;
|
||||
private static final String TAG = "FingerprintEnrollFindSensorViewModel";
|
||||
|
||||
/**
|
||||
* User clicks 'Skip' button on this page in Settings
|
||||
*/
|
||||
public static final int FINGERPRINT_ENROLL_FIND_SENSOR_ACTION_SKIP = 0;
|
||||
|
||||
/**
|
||||
* User clicks 'Skip' button on this page in SetupWizard flow
|
||||
*/
|
||||
public static final int FINGERPRINT_ENROLL_FIND_SENSOR_ACTION_DIALOG = 1;
|
||||
|
||||
/**
|
||||
* User clicks 'Start' button on this page
|
||||
*/
|
||||
public static final int FINGERPRINT_ENROLL_FIND_SENSOR_ACTION_START = 2;
|
||||
|
||||
@IntDef(prefix = { "FINGERPRINT_ENROLL_FIND_SENSOR_ACTION_" }, value = {
|
||||
FINGERPRINT_ENROLL_FIND_SENSOR_ACTION_SKIP,
|
||||
FINGERPRINT_ENROLL_FIND_SENSOR_ACTION_DIALOG,
|
||||
FINGERPRINT_ENROLL_FIND_SENSOR_ACTION_START
|
||||
})
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
public @interface FingerprintEnrollFindSensorAction {}
|
||||
|
||||
private final AccessibilityManager mAccessibilityManager;
|
||||
|
||||
private boolean mIsSuw = false;
|
||||
@NonNull private final MutableLiveData<Integer> mActionLiveData = new MutableLiveData<>();
|
||||
|
||||
public FingerprintEnrollFindSensorViewModel(@NonNull Application application) {
|
||||
super(application);
|
||||
mAccessibilityManager = application.getSystemService(AccessibilityManager.class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets isSetupWizard or not
|
||||
*/
|
||||
public void setIsSuw(boolean isSuw) {
|
||||
mIsSuw = isSuw;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns action live data that user chooses
|
||||
*/
|
||||
public LiveData<Integer> getActionLiveData() {
|
||||
return mActionLiveData;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear ActionLiveData to prevent get obsolete data
|
||||
*/
|
||||
public void clearActionLiveData() {
|
||||
mActionLiveData.setValue(null);
|
||||
}
|
||||
|
||||
/**
|
||||
* User clicks skip button on dialog
|
||||
*/
|
||||
public void onSkipDialogButtonClick() {
|
||||
final int action = FINGERPRINT_ENROLL_FIND_SENSOR_ACTION_SKIP;
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "onSkipDialogButtonClick, post " + action);
|
||||
}
|
||||
mActionLiveData.postValue(action);
|
||||
}
|
||||
|
||||
/**
|
||||
* User clicks skip button
|
||||
*/
|
||||
public void onSkipButtonClick() {
|
||||
final int action = mIsSuw
|
||||
? FINGERPRINT_ENROLL_FIND_SENSOR_ACTION_DIALOG
|
||||
: FINGERPRINT_ENROLL_FIND_SENSOR_ACTION_SKIP;
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "onSkipButtonClick, post action " + action);
|
||||
}
|
||||
mActionLiveData.postValue(action);
|
||||
}
|
||||
|
||||
/**
|
||||
* User clicks start button
|
||||
*/
|
||||
public void onStartButtonClick() {
|
||||
final int action = FINGERPRINT_ENROLL_FIND_SENSOR_ACTION_START;
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "onStartButtonClick, post action " + action);
|
||||
}
|
||||
mActionLiveData.postValue(action);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the info about accessibility is enabled or not
|
||||
*/
|
||||
public boolean isAccessibilityEnabled() {
|
||||
return mAccessibilityManager.isEnabled();
|
||||
}
|
||||
}
|
||||
@@ -24,7 +24,6 @@ import android.annotation.IntDef;
|
||||
import android.app.Application;
|
||||
import android.os.UserHandle;
|
||||
import android.util.Log;
|
||||
import android.view.View;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.lifecycle.AndroidViewModel;
|
||||
@@ -189,7 +188,7 @@ public class FingerprintEnrollIntroViewModel extends AndroidViewModel
|
||||
/**
|
||||
* User clicks next button
|
||||
*/
|
||||
public void onNextButtonClick(View ignoredView) {
|
||||
public void onNextButtonClick() {
|
||||
final Integer status = mEnrollableStatusLiveData.getValue();
|
||||
switch (status != null ? status : ENROLLABLE_STATUS_DEFAULT) {
|
||||
case FINGERPRINT_ENROLLABLE_ERROR_REACH_MAX:
|
||||
@@ -206,7 +205,7 @@ public class FingerprintEnrollIntroViewModel extends AndroidViewModel
|
||||
/**
|
||||
* User clicks skip/cancel button
|
||||
*/
|
||||
public void onSkipOrCancelButtonClick(View ignoredView) {
|
||||
public void onSkipOrCancelButtonClick() {
|
||||
mActionLiveData.postValue(FINGERPRINT_ENROLL_INTRO_ACTION_SKIP_OR_CANCEL);
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,231 @@
|
||||
/*
|
||||
* Copyright (C) 2022 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.biometrics2.ui.viewmodel;
|
||||
|
||||
import static com.android.settings.biometrics2.ui.model.EnrollmentProgress.INITIAL_REMAINING;
|
||||
import static com.android.settings.biometrics2.ui.model.EnrollmentProgress.INITIAL_STEPS;
|
||||
|
||||
import android.app.Application;
|
||||
import android.content.res.Resources;
|
||||
import android.hardware.fingerprint.FingerprintManager.EnrollReason;
|
||||
import android.hardware.fingerprint.FingerprintManager.EnrollmentCallback;
|
||||
import android.os.CancellationSignal;
|
||||
import android.os.SystemClock;
|
||||
import android.os.UserHandle;
|
||||
import android.util.Log;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.lifecycle.AndroidViewModel;
|
||||
import androidx.lifecycle.LiveData;
|
||||
import androidx.lifecycle.MutableLiveData;
|
||||
|
||||
import com.android.settings.R;
|
||||
import com.android.settings.biometrics.fingerprint.FingerprintUpdater;
|
||||
import com.android.settings.biometrics.fingerprint.MessageDisplayController;
|
||||
import com.android.settings.biometrics2.ui.model.EnrollmentProgress;
|
||||
|
||||
/**
|
||||
* Progress ViewModel handles the state around biometric enrollment. It manages the state of
|
||||
* enrollment throughout the activity lifecycle so the app can continue after an event like
|
||||
* rotation.
|
||||
*/
|
||||
public class FingerprintEnrollProgressViewModel extends AndroidViewModel {
|
||||
|
||||
private static final boolean DEBUG = false;
|
||||
private static final String TAG = "FingerprintEnrollProgressViewModel";
|
||||
|
||||
private final MutableLiveData<EnrollmentProgress> mProgressLiveData = new MutableLiveData<>(
|
||||
new EnrollmentProgress(INITIAL_STEPS, INITIAL_REMAINING));
|
||||
|
||||
private byte[] mToken = null;
|
||||
private int mUserId = UserHandle.myUserId();
|
||||
|
||||
private final FingerprintUpdater mFingerprintUpdater;
|
||||
private final MessageDisplayController mMessageDisplayController;
|
||||
private EnrollmentHelper mEnrollmentHelper;
|
||||
private final EnrollmentCallback mEnrollmentCallback = new EnrollmentCallback() {
|
||||
|
||||
@Override
|
||||
public void onEnrollmentProgress(int remaining) {
|
||||
final int currentSteps = getSteps();
|
||||
final EnrollmentProgress progress = new EnrollmentProgress(
|
||||
currentSteps == INITIAL_STEPS ? remaining : getSteps(), remaining);
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "onEnrollmentProgress(" + remaining + "), steps: " + currentSteps
|
||||
+ ", post progress as " + progress);
|
||||
}
|
||||
mProgressLiveData.postValue(progress);
|
||||
// TODO set enrolling to false when remaining is 0 during implementing b/260957933
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onEnrollmentHelp(int helpMsgId, CharSequence helpString) {
|
||||
// TODO add LiveData for help message during implementing b/260957933
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onEnrollmentError(int errMsgId, CharSequence errString) {
|
||||
// TODO add LiveData for error message during implementing b/260957933
|
||||
}
|
||||
};
|
||||
|
||||
public FingerprintEnrollProgressViewModel(@NonNull Application application,
|
||||
@NonNull FingerprintUpdater fingerprintUpdater) {
|
||||
super(application);
|
||||
mFingerprintUpdater = fingerprintUpdater;
|
||||
final Resources res = application.getResources();
|
||||
mMessageDisplayController =
|
||||
res.getBoolean(R.bool.enrollment_message_display_controller_flag)
|
||||
? new MessageDisplayController(
|
||||
application.getMainThreadHandler(),
|
||||
mEnrollmentCallback,
|
||||
SystemClock.elapsedRealtimeClock(),
|
||||
res.getInteger(R.integer.enrollment_help_minimum_time_display),
|
||||
res.getInteger(R.integer.enrollment_progress_minimum_time_display),
|
||||
res.getBoolean(R.bool.enrollment_progress_priority_over_help),
|
||||
res.getBoolean(R.bool.enrollment_prioritize_acquire_messages),
|
||||
res.getInteger(R.integer.enrollment_collect_time)) : null;
|
||||
}
|
||||
|
||||
public void setToken(byte[] token) {
|
||||
mToken = token;
|
||||
}
|
||||
|
||||
public void setUserId(int userId) {
|
||||
mUserId = userId;
|
||||
}
|
||||
|
||||
/**
|
||||
* clear progress
|
||||
*/
|
||||
public void clearProgressLiveData() {
|
||||
mProgressLiveData.setValue(new EnrollmentProgress(INITIAL_STEPS, INITIAL_REMAINING));
|
||||
}
|
||||
|
||||
public LiveData<EnrollmentProgress> getProgressLiveData() {
|
||||
return mProgressLiveData;
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts enrollment and return latest isEnrolling() result
|
||||
*/
|
||||
public boolean startEnrollment(@EnrollReason int reason) {
|
||||
if (mToken == null) {
|
||||
Log.e(TAG, "Null hardware auth token for enroll");
|
||||
return false;
|
||||
}
|
||||
if (isEnrolling()) {
|
||||
Log.w(TAG, "Enrolling has started, shall not start again");
|
||||
return true;
|
||||
}
|
||||
|
||||
mEnrollmentHelper = new EnrollmentHelper(
|
||||
mMessageDisplayController != null
|
||||
? mMessageDisplayController
|
||||
: mEnrollmentCallback);
|
||||
mEnrollmentHelper.startEnrollment(mFingerprintUpdater, mToken, mUserId, reason);
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Cancels enrollment and return latest isEnrolling result
|
||||
*/
|
||||
public boolean cancelEnrollment() {
|
||||
if (!isEnrolling() || mEnrollmentHelper == null) {
|
||||
Log.e(TAG, "Fail to cancel enrollment, enrollmentController exist:"
|
||||
+ (mEnrollmentHelper != null));
|
||||
return false;
|
||||
}
|
||||
|
||||
mEnrollmentHelper.cancelEnrollment();
|
||||
mEnrollmentHelper = null;
|
||||
return true;
|
||||
}
|
||||
|
||||
public boolean isEnrolling() {
|
||||
return (mEnrollmentHelper != null);
|
||||
}
|
||||
|
||||
private int getSteps() {
|
||||
return mProgressLiveData.getValue().getSteps();
|
||||
}
|
||||
|
||||
/**
|
||||
* This class is used to stop latest message from onEnrollmentError() after user cancelled
|
||||
* enrollment. This class will not forward message anymore after mCancellationSignal is sent.
|
||||
*/
|
||||
private static class EnrollmentHelper extends EnrollmentCallback {
|
||||
|
||||
@NonNull private final EnrollmentCallback mEnrollmentCallback;
|
||||
@Nullable private CancellationSignal mCancellationSignal = new CancellationSignal();
|
||||
|
||||
EnrollmentHelper(@NonNull EnrollmentCallback enrollmentCallback) {
|
||||
mEnrollmentCallback = enrollmentCallback;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onEnrollmentError(int errMsgId, CharSequence errString) {
|
||||
if (mCancellationSignal == null) {
|
||||
return;
|
||||
}
|
||||
mEnrollmentCallback.onEnrollmentError(errMsgId, errString);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onEnrollmentHelp(int helpMsgId, CharSequence helpString) {
|
||||
if (mCancellationSignal == null) {
|
||||
return;
|
||||
}
|
||||
mEnrollmentCallback.onEnrollmentHelp(helpMsgId, helpString);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onEnrollmentProgress(int remaining) {
|
||||
if (mCancellationSignal == null) {
|
||||
return;
|
||||
}
|
||||
mEnrollmentCallback.onEnrollmentProgress(remaining);
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts enrollment
|
||||
*/
|
||||
public boolean startEnrollment(@NonNull FingerprintUpdater fingerprintUpdater,
|
||||
@NonNull byte[] token, int userId, @EnrollReason int reason) {
|
||||
if (mCancellationSignal == null) {
|
||||
// Not allow enrolling twice as same instance. Allocate a new instance for second
|
||||
// enrollment.
|
||||
return false;
|
||||
}
|
||||
fingerprintUpdater.enroll(token, mCancellationSignal, userId, this, reason);
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Cancels current enrollment
|
||||
*/
|
||||
public void cancelEnrollment() {
|
||||
final CancellationSignal cancellationSignal = mCancellationSignal;
|
||||
mCancellationSignal = null;
|
||||
|
||||
if (cancellationSignal != null) {
|
||||
cancellationSignal.cancel();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -124,8 +124,6 @@ public class FingerprintEnrollmentViewModel extends AndroidViewModel implements
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
private boolean isKeyguardSecure() {
|
||||
return mKeyguardManager != null && mKeyguardManager.isKeyguardSecure();
|
||||
}
|
||||
@@ -182,4 +180,18 @@ public class FingerprintEnrollmentViewModel extends AndroidViewModel implements
|
||||
public void onSaveInstanceState(@NonNull Bundle outState) {
|
||||
outState.putBoolean(SAVED_STATE_IS_WAITING_ACTIVITY_RESULT, mIsWaitingActivityResult.get());
|
||||
}
|
||||
|
||||
/**
|
||||
* The first sensor type is UDFPS sensor or not
|
||||
*/
|
||||
public boolean canAssumeUdfps() {
|
||||
return mFingerprintRepository.canAssumeUdfps();
|
||||
}
|
||||
|
||||
/**
|
||||
* The first sensor type is side fps sensor or not
|
||||
*/
|
||||
public boolean canAssumeSfps() {
|
||||
return mFingerprintRepository.canAssumeSfps();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,6 +19,7 @@ package com.android.settings.bluetooth;
|
||||
import static com.android.settings.bluetooth.BluetoothDeviceDetailsFragment.FEATURE_HEARING_DEVICE_CONTROLS_ORDER;
|
||||
|
||||
import android.content.Context;
|
||||
import android.text.TextUtils;
|
||||
import android.util.FeatureFlagUtils;
|
||||
|
||||
import androidx.preference.Preference;
|
||||
@@ -27,16 +28,22 @@ import androidx.preference.PreferenceFragmentCompat;
|
||||
import androidx.preference.PreferenceScreen;
|
||||
|
||||
import com.android.settings.R;
|
||||
import com.android.settings.accessibility.AccessibilityHearingAidsFragment;
|
||||
import com.android.settings.core.SubSettingLauncher;
|
||||
import com.android.settingslib.bluetooth.CachedBluetoothDevice;
|
||||
import com.android.settingslib.core.lifecycle.Lifecycle;
|
||||
|
||||
import com.google.common.annotations.VisibleForTesting;
|
||||
|
||||
/**
|
||||
* The controller of the hearing device controls in the bluetooth detail settings.
|
||||
*/
|
||||
public class BluetoothDetailsHearingDeviceControlsController extends BluetoothDetailsController {
|
||||
public class BluetoothDetailsHearingDeviceControlsController extends BluetoothDetailsController
|
||||
implements Preference.OnPreferenceClickListener {
|
||||
|
||||
private static final String KEY_FEATURE_CONTROLS_GROUP = "feature_controls_group";
|
||||
private static final String KEY_HEARING_DEVICE_CONTROLS = "hearing_device_controls";
|
||||
@VisibleForTesting
|
||||
static final String KEY_HEARING_DEVICE_CONTROLS = "hearing_device_controls";
|
||||
|
||||
public BluetoothDetailsHearingDeviceControlsController(Context context,
|
||||
PreferenceFragmentCompat fragment, CachedBluetoothDevice device, Lifecycle lifecycle) {
|
||||
@@ -70,12 +77,30 @@ public class BluetoothDetailsHearingDeviceControlsController extends BluetoothDe
|
||||
return KEY_FEATURE_CONTROLS_GROUP;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onPreferenceClick(Preference preference) {
|
||||
if (TextUtils.equals(preference.getKey(), KEY_HEARING_DEVICE_CONTROLS)) {
|
||||
launchAccessibilityHearingDeviceSettings();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private Preference createHearingDeviceControlsPreference(Context context) {
|
||||
final Preference preference = new Preference(context);
|
||||
preference.setKey(KEY_HEARING_DEVICE_CONTROLS);
|
||||
preference.setTitle(context.getString(R.string.bluetooth_device_controls_title));
|
||||
preference.setSummary(context.getString(R.string.bluetooth_device_controls_summary));
|
||||
preference.setOnPreferenceClickListener(this);
|
||||
|
||||
return preference;
|
||||
}
|
||||
|
||||
private void launchAccessibilityHearingDeviceSettings() {
|
||||
new SubSettingLauncher(mContext)
|
||||
.setDestination(AccessibilityHearingAidsFragment.class.getName())
|
||||
.setSourceMetricsCategory(
|
||||
((BluetoothDeviceDetailsFragment) mFragment).getMetricsCategory())
|
||||
.launch();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
package com.android.settings.communal;
|
||||
|
||||
import android.content.Context;
|
||||
import android.os.UserManager;
|
||||
|
||||
import com.android.settings.R;
|
||||
import com.android.settings.core.BasePreferenceController;
|
||||
@@ -31,7 +32,14 @@ public class CommunalPreferenceController extends BasePreferenceController {
|
||||
|
||||
@Override
|
||||
public int getAvailabilityStatus() {
|
||||
return mContext.getResources().getBoolean(R.bool.config_show_communal_settings)
|
||||
// TODO(b/257333623): Allow the communal user to be non-SystemUser user in HSUM.
|
||||
return (mContext.getResources().getBoolean(R.bool.config_show_communal_settings)
|
||||
&& isSystemUser())
|
||||
? AVAILABLE : UNSUPPORTED_ON_DEVICE;
|
||||
}
|
||||
|
||||
private boolean isSystemUser() {
|
||||
final UserManager userManager = mContext.getSystemService(UserManager.class);
|
||||
return userManager != null && userManager.isSystemUser();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -85,16 +85,24 @@ public class StylusDevicesController extends AbstractPreferenceController implem
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private Preference createDefaultNotesPreference() {
|
||||
private Preference createOrUpdateDefaultNotesPreference(@Nullable Preference preference) {
|
||||
RoleManager rm = mContext.getSystemService(RoleManager.class);
|
||||
if (rm == null) {
|
||||
if (rm == null || !rm.isRoleAvailable(RoleManager.ROLE_NOTES)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
Preference pref = preference == null ? new Preference(mContext) : preference;
|
||||
pref.setKey(KEY_DEFAULT_NOTES);
|
||||
pref.setTitle(mContext.getString(R.string.stylus_default_notes_app));
|
||||
pref.setIcon(R.drawable.ic_article);
|
||||
pref.setOnPreferenceClickListener(this);
|
||||
pref.setEnabled(true);
|
||||
|
||||
List<String> roleHolders = rm.getRoleHoldersAsUser(RoleManager.ROLE_NOTES,
|
||||
mContext.getUser());
|
||||
if (roleHolders.isEmpty()) {
|
||||
return null;
|
||||
pref.setSummary(R.string.default_app_none);
|
||||
return pref;
|
||||
}
|
||||
|
||||
String packageName = roleHolders.get(0);
|
||||
@@ -107,13 +115,6 @@ public class StylusDevicesController extends AbstractPreferenceController implem
|
||||
} catch (PackageManager.NameNotFoundException e) {
|
||||
Log.e(TAG, "Notes role package not found.");
|
||||
}
|
||||
|
||||
Preference pref = new Preference(mContext);
|
||||
pref.setKey(KEY_DEFAULT_NOTES);
|
||||
pref.setTitle(mContext.getString(R.string.stylus_default_notes_app));
|
||||
pref.setIcon(R.drawable.ic_article);
|
||||
pref.setOnPreferenceClickListener(this);
|
||||
pref.setEnabled(true);
|
||||
pref.setSummary(appName);
|
||||
return pref;
|
||||
}
|
||||
@@ -187,12 +188,10 @@ public class StylusDevicesController extends AbstractPreferenceController implem
|
||||
private void refresh() {
|
||||
if (!isAvailable()) return;
|
||||
|
||||
Preference notesPref = mPreferencesContainer.findPreference(KEY_DEFAULT_NOTES);
|
||||
if (notesPref == null) {
|
||||
notesPref = createDefaultNotesPreference();
|
||||
if (notesPref != null) {
|
||||
mPreferencesContainer.addPreference(notesPref);
|
||||
}
|
||||
Preference currNotesPref = mPreferencesContainer.findPreference(KEY_DEFAULT_NOTES);
|
||||
Preference notesPref = createOrUpdateDefaultNotesPreference(currNotesPref);
|
||||
if (currNotesPref == null && notesPref != null) {
|
||||
mPreferencesContainer.addPreference(notesPref);
|
||||
}
|
||||
|
||||
Preference handwritingPref = mPreferencesContainer.findPreference(KEY_HANDWRITING);
|
||||
|
||||
@@ -164,6 +164,7 @@ import com.android.settings.print.PrintJobSettingsFragment;
|
||||
import com.android.settings.print.PrintSettingsFragment;
|
||||
import com.android.settings.privacy.PrivacyControlsFragment;
|
||||
import com.android.settings.privacy.PrivacyDashboardFragment;
|
||||
import com.android.settings.regionalpreferences.RegionalPreferencesEntriesFragment;
|
||||
import com.android.settings.security.LockscreenDashboardFragment;
|
||||
import com.android.settings.security.MemtagPage;
|
||||
import com.android.settings.security.SecurityAdvancedSettings;
|
||||
@@ -367,7 +368,8 @@ public class SettingsGateway {
|
||||
TurnScreenOnDetails.class.getName(),
|
||||
NfcAndPaymentFragment.class.getName(),
|
||||
ColorAndMotionFragment.class.getName(),
|
||||
LongBackgroundTasksDetails.class.getName()
|
||||
LongBackgroundTasksDetails.class.getName(),
|
||||
RegionalPreferencesEntriesFragment.class.getName()
|
||||
};
|
||||
|
||||
public static final String[] SETTINGS_FOR_RESTRICTED = {
|
||||
|
||||
@@ -55,6 +55,7 @@ import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
@SearchIndexable
|
||||
public class MyDeviceInfoFragment extends DashboardFragment
|
||||
@@ -79,7 +80,6 @@ public class MyDeviceInfoFragment extends DashboardFragment
|
||||
@Override
|
||||
public void onAttach(Context context) {
|
||||
super.onAttach(context);
|
||||
use(ImeiInfoPreferenceController.class).setHost(this /* parent */);
|
||||
use(DeviceNamePreferenceController.class).setHost(this /* parent */);
|
||||
mBuildNumberPreferenceController = use(BuildNumberPreferenceController.class);
|
||||
mBuildNumberPreferenceController.setHost(this /* parent */);
|
||||
@@ -126,12 +126,27 @@ public class MyDeviceInfoFragment extends DashboardFragment
|
||||
controllers.add(new FccEquipmentIdPreferenceController(context));
|
||||
controllers.add(new UptimePreferenceController(context, lifecycle));
|
||||
|
||||
Consumer<String> imeiInfoList = imeiKey -> {
|
||||
ImeiInfoPreferenceController imeiRecord =
|
||||
new ImeiInfoPreferenceController(context, imeiKey);
|
||||
imeiRecord.init(fragment, slotSimStatus);
|
||||
controllers.add(imeiRecord);
|
||||
};
|
||||
|
||||
if (fragment != null) {
|
||||
imeiInfoList.accept(ImeiInfoPreferenceController.DEFAULT_KEY);
|
||||
}
|
||||
|
||||
for (int slotIndex = 0; slotIndex < slotSimStatus.size(); slotIndex ++) {
|
||||
SimStatusPreferenceController slotRecord =
|
||||
new SimStatusPreferenceController(context,
|
||||
slotSimStatus.getPreferenceKey(slotIndex));
|
||||
slotRecord.init(fragment, slotSimStatus);
|
||||
controllers.add(slotRecord);
|
||||
|
||||
if (fragment != null) {
|
||||
imeiInfoList.accept(ImeiInfoPreferenceController.DEFAULT_KEY + (1 + slotIndex));
|
||||
}
|
||||
}
|
||||
|
||||
EidStatus eidStatus = new EidStatus(slotSimStatus, context, executor);
|
||||
|
||||
@@ -23,6 +23,7 @@ import android.os.UserManager;
|
||||
import android.telephony.SubscriptionInfo;
|
||||
import android.telephony.SubscriptionManager;
|
||||
import android.telephony.TelephonyManager;
|
||||
import android.util.Log;
|
||||
|
||||
import androidx.annotation.VisibleForTesting;
|
||||
import androidx.fragment.app.Fragment;
|
||||
@@ -33,70 +34,80 @@ import androidx.preference.PreferenceScreen;
|
||||
import com.android.settings.R;
|
||||
import com.android.settings.core.BasePreferenceController;
|
||||
import com.android.settings.deviceinfo.PhoneNumberSummaryPreference;
|
||||
import com.android.settings.deviceinfo.simstatus.SlotSimStatus;
|
||||
import com.android.settings.network.SubscriptionUtil;
|
||||
import com.android.settingslib.Utils;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Controller that manages preference for single and multi sim devices.
|
||||
*/
|
||||
public class ImeiInfoPreferenceController extends BasePreferenceController {
|
||||
|
||||
private static final String KEY_PREFERENCE_CATEGORY = "device_detail_category";
|
||||
private static final String TAG = "ImeiInfoPreferenceController";
|
||||
|
||||
private final boolean mIsMultiSim;
|
||||
private final TelephonyManager mTelephonyManager;
|
||||
private final List<Preference> mPreferenceList = new ArrayList<>();
|
||||
private static final String KEY_PREFERENCE_CATEGORY = "device_detail_category";
|
||||
public static final String DEFAULT_KEY = "imei_info";
|
||||
|
||||
private TelephonyManager mTelephonyManager;
|
||||
private Fragment mFragment;
|
||||
private SlotSimStatus mSlotSimStatus;
|
||||
|
||||
public ImeiInfoPreferenceController(Context context, String key) {
|
||||
super(context, key);
|
||||
mTelephonyManager = (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);
|
||||
mIsMultiSim = mTelephonyManager.getPhoneCount() > 1;
|
||||
}
|
||||
|
||||
public void setHost(Fragment fragment) {
|
||||
public void init(Fragment fragment, SlotSimStatus slotSimStatus) {
|
||||
mFragment = fragment;
|
||||
mSlotSimStatus = slotSimStatus;
|
||||
}
|
||||
|
||||
private boolean isMultiSim() {
|
||||
return (mSlotSimStatus != null) && (mSlotSimStatus.size() > 1);
|
||||
}
|
||||
|
||||
private int keyToSlotIndex(String key) {
|
||||
int simSlot = SubscriptionManager.INVALID_SIM_SLOT_INDEX;
|
||||
try {
|
||||
simSlot = Integer.valueOf(key.replace(DEFAULT_KEY, "")) - 1;
|
||||
} catch (Exception exception) {
|
||||
Log.i(TAG, "Invalid key : " + key);
|
||||
}
|
||||
return simSlot;
|
||||
}
|
||||
|
||||
private SubscriptionInfo getSubscriptionInfo(int simSlot) {
|
||||
return (mSlotSimStatus == null) ? null : mSlotSimStatus.getSubscriptionInfo(simSlot);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void displayPreference(PreferenceScreen screen) {
|
||||
super.displayPreference(screen);
|
||||
if (!SubscriptionUtil.isSimHardwareVisible(mContext)) {
|
||||
if ((!SubscriptionUtil.isSimHardwareVisible(mContext)) || (mSlotSimStatus == null)) {
|
||||
return;
|
||||
}
|
||||
final Preference preference = screen.findPreference(getPreferenceKey());
|
||||
final PreferenceCategory category = screen.findPreference(KEY_PREFERENCE_CATEGORY);
|
||||
|
||||
mPreferenceList.add(preference);
|
||||
updatePreference(preference, 0 /* simSlot */);
|
||||
|
||||
final int imeiPreferenceOrder = preference.getOrder();
|
||||
// Add additional preferences for each sim in the device
|
||||
for (int simSlotNumber = 1; simSlotNumber < mTelephonyManager.getPhoneCount();
|
||||
simSlotNumber++) {
|
||||
final Preference multiSimPreference = createNewPreference(screen.getContext());
|
||||
multiSimPreference.setCopyingEnabled(true);
|
||||
multiSimPreference.setOrder(imeiPreferenceOrder + simSlotNumber);
|
||||
multiSimPreference.setKey(getPreferenceKey() + simSlotNumber);
|
||||
category.addPreference(multiSimPreference);
|
||||
mPreferenceList.add(multiSimPreference);
|
||||
updatePreference(multiSimPreference, simSlotNumber);
|
||||
mTelephonyManager = mContext.getSystemService(TelephonyManager.class);
|
||||
Preference preference = screen.findPreference(DEFAULT_KEY);
|
||||
if (!isAvailable() || preference == null || !preference.isVisible()) {
|
||||
return;
|
||||
}
|
||||
PreferenceCategory category = screen.findPreference(KEY_PREFERENCE_CATEGORY);
|
||||
|
||||
int imeiPreferenceOrder = preference.getOrder();
|
||||
screen.removePreference(preference);
|
||||
preference.setVisible(false);
|
||||
|
||||
// Add additional preferences for each imei slot in the device
|
||||
for (int simSlotNumber = 0; simSlotNumber < mSlotSimStatus.size(); simSlotNumber++) {
|
||||
Preference multiImeiPreference = createNewPreference(screen.getContext());
|
||||
multiImeiPreference.setOrder(imeiPreferenceOrder + 1 + simSlotNumber);
|
||||
multiImeiPreference.setKey(DEFAULT_KEY + (1 + simSlotNumber));
|
||||
category.addPreference(multiImeiPreference);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateState(Preference preference) {
|
||||
if (preference == null) {
|
||||
return;
|
||||
}
|
||||
int size = mPreferenceList.size();
|
||||
for (int i = 0; i < size; i++) {
|
||||
Preference pref = mPreferenceList.get(i);
|
||||
updatePreference(pref, i);
|
||||
}
|
||||
updatePreference(preference, keyToSlotIndex(preference.getKey()));
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -112,8 +123,8 @@ public class ImeiInfoPreferenceController extends BasePreferenceController {
|
||||
|
||||
@Override
|
||||
public boolean handlePreferenceTreeClick(Preference preference) {
|
||||
final int simSlot = mPreferenceList.indexOf(preference);
|
||||
if (simSlot == -1) {
|
||||
final int simSlot = keyToSlotIndex(preference.getKey());
|
||||
if (simSlot < 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -124,9 +135,10 @@ public class ImeiInfoPreferenceController extends BasePreferenceController {
|
||||
|
||||
@Override
|
||||
public int getAvailabilityStatus() {
|
||||
return SubscriptionUtil.isSimHardwareVisible(mContext) &&
|
||||
mContext.getSystemService(UserManager.class).isAdminUser()
|
||||
&& !Utils.isWifiOnly(mContext) ? AVAILABLE : UNSUPPORTED_ON_DEVICE;
|
||||
boolean isAvailable = SubscriptionUtil.isSimHardwareVisible(mContext) &&
|
||||
mContext.getSystemService(UserManager.class).isAdminUser() &&
|
||||
!Utils.isWifiOnly(mContext);
|
||||
return isAvailable ? AVAILABLE : UNSUPPORTED_ON_DEVICE;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -135,29 +147,48 @@ public class ImeiInfoPreferenceController extends BasePreferenceController {
|
||||
}
|
||||
|
||||
private void updatePreference(Preference preference, int simSlot) {
|
||||
SubscriptionInfo subInfo = getSubscriptionInfo(simSlot);
|
||||
preference.setEnabled(subInfo != null);
|
||||
preference.setCopyingEnabled(subInfo != null);
|
||||
preference.setTitle(getTitle(simSlot));
|
||||
preference.setSummary(getSummary());
|
||||
}
|
||||
|
||||
private CharSequence getTitleForGsmPhone(int simSlot) {
|
||||
return mIsMultiSim ? mContext.getString(R.string.imei_multi_sim, simSlot + 1)
|
||||
private CharSequence getTitleForGsmPhone(int simSlot, boolean isPrimaryImei) {
|
||||
int titleId = isPrimaryImei ? R.string.imei_multi_sim_primary : R.string.imei_multi_sim;
|
||||
return isMultiSim() ? mContext.getString(titleId, simSlot + 1)
|
||||
: mContext.getString(R.string.status_imei);
|
||||
}
|
||||
|
||||
private CharSequence getTitleForCdmaPhone(int simSlot) {
|
||||
return mIsMultiSim ? mContext.getString(R.string.meid_multi_sim, simSlot + 1)
|
||||
private CharSequence getTitleForCdmaPhone(int simSlot, boolean isPrimaryImei) {
|
||||
int titleId = isPrimaryImei ? R.string.meid_multi_sim_primary : R.string.meid_multi_sim;
|
||||
return isMultiSim() ? mContext.getString(titleId, simSlot + 1)
|
||||
: mContext.getString(R.string.status_meid_number);
|
||||
}
|
||||
|
||||
private CharSequence getTitle(int simSlot) {
|
||||
final int phoneType = getPhoneType(simSlot);
|
||||
return phoneType == PHONE_TYPE_CDMA ? getTitleForCdmaPhone(simSlot)
|
||||
: getTitleForGsmPhone(simSlot);
|
||||
protected boolean isPrimaryImei(int simSlot) {
|
||||
CharSequence imei = getSummary(simSlot);
|
||||
if (imei == null) {
|
||||
return false;
|
||||
}
|
||||
String primaryImei = null;
|
||||
try {
|
||||
primaryImei = mTelephonyManager.getPrimaryImei();
|
||||
} catch (Exception exception) {
|
||||
Log.i(TAG, "PrimaryImei not available. " + exception);
|
||||
}
|
||||
return (primaryImei != null) && primaryImei.equals(imei.toString());
|
||||
}
|
||||
|
||||
private int getPhoneType(int slotIndex) {
|
||||
SubscriptionInfo subInfo = SubscriptionManager.from(mContext)
|
||||
.getActiveSubscriptionInfoForSimSlotIndex(slotIndex);
|
||||
private CharSequence getTitle(int simSlot) {
|
||||
boolean isPrimaryImei = isMultiSim() && isPrimaryImei(simSlot);
|
||||
final int phoneType = getPhoneType(simSlot);
|
||||
return phoneType == PHONE_TYPE_CDMA ? getTitleForCdmaPhone(simSlot, isPrimaryImei)
|
||||
: getTitleForGsmPhone(simSlot, isPrimaryImei);
|
||||
}
|
||||
|
||||
public int getPhoneType(int slotIndex) {
|
||||
SubscriptionInfo subInfo = getSubscriptionInfo(slotIndex);
|
||||
return mTelephonyManager.getCurrentPhoneType(subInfo != null ? subInfo.getSubscriptionId()
|
||||
: SubscriptionManager.DEFAULT_SUBSCRIPTION_ID);
|
||||
}
|
||||
|
||||
@@ -17,7 +17,9 @@
|
||||
package com.android.settings.display;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.Point;
|
||||
import android.hardware.display.DisplayManager;
|
||||
import android.util.Log;
|
||||
import android.view.Display;
|
||||
|
||||
import androidx.annotation.VisibleForTesting;
|
||||
@@ -25,32 +27,63 @@ import androidx.annotation.VisibleForTesting;
|
||||
import com.android.settings.R;
|
||||
import com.android.settings.core.BasePreferenceController;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
/** Controller that switch the screen resolution. */
|
||||
public class ScreenResolutionController extends BasePreferenceController {
|
||||
|
||||
static final int FHD_WIDTH = 1080;
|
||||
static final int QHD_WIDTH = 1440;
|
||||
private static final String TAG = "ScreenResolutionController";
|
||||
static final int HIGHRESOLUTION_IDX = 0;
|
||||
static final int FULLRESOLUTION_IDX = 1;
|
||||
|
||||
private Display mDisplay;
|
||||
private Set<Point> mSupportedResolutions = null;
|
||||
private int mHighWidth = 0;
|
||||
private int mFullWidth = 0;
|
||||
private int mHighHeight = 0;
|
||||
private int mFullHeight = 0;
|
||||
|
||||
public ScreenResolutionController(Context context, String key) {
|
||||
super(context, key);
|
||||
|
||||
mDisplay =
|
||||
mContext.getSystemService(DisplayManager.class).getDisplay(Display.DEFAULT_DISPLAY);
|
||||
|
||||
initSupportedResolutionData();
|
||||
}
|
||||
|
||||
/** Check if the width is supported by the display. */
|
||||
private boolean isSupportedMode(int width) {
|
||||
/**
|
||||
* Initialize the resolution data. So far, we support two resolution switching. Save the width
|
||||
* and the height for high resolution and full resolution.
|
||||
*/
|
||||
private void initSupportedResolutionData() {
|
||||
// Collect and filter the resolutions
|
||||
Set<Point> resolutions = new HashSet<>();
|
||||
for (Display.Mode mode : getSupportedModes()) {
|
||||
if (mode.getPhysicalWidth() == width) return true;
|
||||
resolutions.add(new Point(mode.getPhysicalWidth(), mode.getPhysicalHeight()));
|
||||
}
|
||||
return false;
|
||||
mSupportedResolutions = resolutions;
|
||||
|
||||
// Get the width and height for high resolution and full resolution
|
||||
List<Point> resolutionList = new ArrayList<>(resolutions);
|
||||
if (resolutionList == null || resolutionList.size() != 2) {
|
||||
Log.e(TAG, "No support");
|
||||
return;
|
||||
}
|
||||
|
||||
Collections.sort(resolutionList, (p1, p2) -> p1.x * p1.y - p2.x * p2.y);
|
||||
mHighWidth = resolutionList.get(HIGHRESOLUTION_IDX).x;
|
||||
mHighHeight = resolutionList.get(HIGHRESOLUTION_IDX).y;
|
||||
mFullWidth = resolutionList.get(FULLRESOLUTION_IDX).x;
|
||||
mFullHeight = resolutionList.get(FULLRESOLUTION_IDX).y;
|
||||
}
|
||||
|
||||
/** Return true if the device contains two (or more) resolutions. */
|
||||
protected boolean checkSupportedResolutions() {
|
||||
return isSupportedMode(FHD_WIDTH) && isSupportedMode(QHD_WIDTH);
|
||||
return getHighWidth() != 0 && getFullWidth() != 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -61,20 +94,43 @@ public class ScreenResolutionController extends BasePreferenceController {
|
||||
@Override
|
||||
public CharSequence getSummary() {
|
||||
String summary = null;
|
||||
switch (getDisplayWidth()) {
|
||||
case FHD_WIDTH:
|
||||
summary = mContext.getString(R.string.screen_resolution_summary_high);
|
||||
break;
|
||||
case QHD_WIDTH:
|
||||
summary = mContext.getString(R.string.screen_resolution_summary_highest);
|
||||
break;
|
||||
default:
|
||||
summary = mContext.getString(R.string.screen_resolution_title);
|
||||
int width = getDisplayWidth();
|
||||
if (width == mHighWidth) {
|
||||
summary = mContext.getString(R.string.screen_resolution_option_high);
|
||||
} else if (width == mFullWidth) {
|
||||
summary = mContext.getString(R.string.screen_resolution_option_full);
|
||||
} else {
|
||||
summary = mContext.getString(R.string.screen_resolution_title);
|
||||
}
|
||||
|
||||
return summary;
|
||||
}
|
||||
|
||||
/** Return all supported resolutions of the device. */
|
||||
public Set<Point> getAllSupportedResolutions() {
|
||||
return this.mSupportedResolutions;
|
||||
}
|
||||
|
||||
/** Return the high resolution width of the device. */
|
||||
public int getHighWidth() {
|
||||
return this.mHighWidth;
|
||||
}
|
||||
|
||||
/** Return the full resolution width of the device. */
|
||||
public int getFullWidth() {
|
||||
return this.mFullWidth;
|
||||
}
|
||||
|
||||
/** Return the high resolution height of the device. */
|
||||
public int getHighHeight() {
|
||||
return this.mHighHeight;
|
||||
}
|
||||
|
||||
/** Return the full resolution height of the device. */
|
||||
public int getFullHeight() {
|
||||
return this.mFullHeight;
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
public int getDisplayWidth() {
|
||||
return mDisplay.getMode().getPhysicalWidth();
|
||||
|
||||
@@ -16,9 +16,6 @@
|
||||
|
||||
package com.android.settings.display;
|
||||
|
||||
import static com.android.settings.display.ScreenResolutionController.FHD_WIDTH;
|
||||
import static com.android.settings.display.ScreenResolutionController.QHD_WIDTH;
|
||||
|
||||
import android.annotation.Nullable;
|
||||
import android.app.settings.SettingsEnums;
|
||||
import android.content.Context;
|
||||
@@ -48,7 +45,6 @@ import com.android.settingslib.widget.IllustrationPreference;
|
||||
import com.android.settingslib.widget.SelectorWithWidgetPreference;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
@@ -59,8 +55,6 @@ public class ScreenResolutionFragment extends RadioButtonPickerFragment {
|
||||
private static final String TAG = "ScreenResolution";
|
||||
|
||||
private Resources mResources;
|
||||
private static final int FHD_INDEX = 0;
|
||||
private static final int QHD_INDEX = 1;
|
||||
private static final String SCREEN_RESOLUTION = "user_selected_resolution";
|
||||
private Display mDefaultDisplay;
|
||||
private String[] mScreenResolutionOptions;
|
||||
@@ -71,6 +65,9 @@ public class ScreenResolutionFragment extends RadioButtonPickerFragment {
|
||||
private DisplayObserver mDisplayObserver;
|
||||
private AccessibilityManager mAccessibilityManager;
|
||||
|
||||
private int mHighWidth;
|
||||
private int mFullWidth;
|
||||
|
||||
@Override
|
||||
public void onAttach(Context context) {
|
||||
super.onAttach(context);
|
||||
@@ -81,11 +78,18 @@ public class ScreenResolutionFragment extends RadioButtonPickerFragment {
|
||||
mResources = context.getResources();
|
||||
mScreenResolutionOptions =
|
||||
mResources.getStringArray(R.array.config_screen_resolution_options_strings);
|
||||
mScreenResolutionSummaries =
|
||||
mResources.getStringArray(R.array.config_screen_resolution_summaries_strings);
|
||||
mResolutions = getAllSupportedResolution();
|
||||
mImagePreference = new IllustrationPreference(context);
|
||||
mDisplayObserver = new DisplayObserver(context);
|
||||
ScreenResolutionController mController =
|
||||
new ScreenResolutionController(context, "fragment");
|
||||
mResolutions = mController.getAllSupportedResolutions();
|
||||
mHighWidth = mController.getHighWidth();
|
||||
mFullWidth = mController.getFullWidth();
|
||||
mScreenResolutionSummaries =
|
||||
new String[] {
|
||||
mHighWidth + " x " + mController.getHighHeight(),
|
||||
mFullWidth + " x " + mController.getFullHeight()
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -133,16 +137,6 @@ public class ScreenResolutionFragment extends RadioButtonPickerFragment {
|
||||
return candidates;
|
||||
}
|
||||
|
||||
/** Get all supported resolutions on the device. */
|
||||
private Set<Point> getAllSupportedResolution() {
|
||||
Set<Point> resolutions = new HashSet<>();
|
||||
for (Display.Mode mode : mDefaultDisplay.getSupportedModes()) {
|
||||
resolutions.add(new Point(mode.getPhysicalWidth(), mode.getPhysicalHeight()));
|
||||
}
|
||||
|
||||
return resolutions;
|
||||
}
|
||||
|
||||
/** Get prefer display mode. */
|
||||
private Display.Mode getPreferMode(int width) {
|
||||
for (Point resolution : mResolutions) {
|
||||
@@ -177,6 +171,7 @@ public class ScreenResolutionFragment extends RadioButtonPickerFragment {
|
||||
|
||||
try {
|
||||
/** Apply the resolution change. */
|
||||
Log.i(TAG, "setUserPreferredDisplayMode: " + mode);
|
||||
mDefaultDisplay.setUserPreferredDisplayMode(mode);
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, "setUserPreferredDisplayMode() failed", e);
|
||||
@@ -194,16 +189,19 @@ public class ScreenResolutionFragment extends RadioButtonPickerFragment {
|
||||
/** Get the key corresponding to the resolution. */
|
||||
@VisibleForTesting
|
||||
String getKeyForResolution(int width) {
|
||||
return width == FHD_WIDTH
|
||||
? mScreenResolutionOptions[FHD_INDEX]
|
||||
: width == QHD_WIDTH ? mScreenResolutionOptions[QHD_INDEX] : null;
|
||||
return width == mHighWidth
|
||||
? mScreenResolutionOptions[ScreenResolutionController.HIGHRESOLUTION_IDX]
|
||||
: width == mFullWidth
|
||||
? mScreenResolutionOptions[ScreenResolutionController.FULLRESOLUTION_IDX]
|
||||
: null;
|
||||
}
|
||||
|
||||
/** Get the width corresponding to the resolution key. */
|
||||
int getWidthForResoluitonKey(String key) {
|
||||
return mScreenResolutionOptions[FHD_INDEX].equals(key)
|
||||
? FHD_WIDTH
|
||||
: mScreenResolutionOptions[QHD_INDEX].equals(key) ? QHD_WIDTH : -1;
|
||||
return mScreenResolutionOptions[ScreenResolutionController.HIGHRESOLUTION_IDX].equals(key)
|
||||
? mHighWidth
|
||||
: mScreenResolutionOptions[ScreenResolutionController.FULLRESOLUTION_IDX].equals(
|
||||
key) ? mFullWidth : -1;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -248,9 +246,11 @@ public class ScreenResolutionFragment extends RadioButtonPickerFragment {
|
||||
private void updateIllustrationImage(IllustrationPreference preference) {
|
||||
String key = getDefaultKey();
|
||||
|
||||
if (TextUtils.equals(mScreenResolutionOptions[FHD_INDEX], key)) {
|
||||
if (TextUtils.equals(
|
||||
mScreenResolutionOptions[ScreenResolutionController.HIGHRESOLUTION_IDX], key)) {
|
||||
preference.setLottieAnimationResId(R.drawable.screen_resolution_1080p);
|
||||
} else if (TextUtils.equals(mScreenResolutionOptions[QHD_INDEX], key)) {
|
||||
} else if (TextUtils.equals(
|
||||
mScreenResolutionOptions[ScreenResolutionController.FULLRESOLUTION_IDX], key)) {
|
||||
preference.setLottieAnimationResId(R.drawable.screen_resolution_1440p);
|
||||
}
|
||||
}
|
||||
@@ -407,6 +407,8 @@ public class ScreenResolutionFragment extends RadioButtonPickerFragment {
|
||||
return false;
|
||||
}
|
||||
|
||||
Log.i(TAG,
|
||||
"resolution changed from " + mPreviousWidth.get() + " to " + getCurrentWidth());
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -233,6 +233,7 @@ public class BatteryChartPreferenceController extends AbstractPreferenceControll
|
||||
batteryCallbackData -> {
|
||||
mBatteryUsageMap = batteryCallbackData.getBatteryUsageMap();
|
||||
mScreenOnTimeMap = batteryCallbackData.getDeviceScreenOnTime();
|
||||
logScreenUsageTime();
|
||||
refreshUi();
|
||||
});
|
||||
Log.d(TAG, "getBatteryLevelData: " + batteryLevelData);
|
||||
@@ -495,6 +496,42 @@ public class BatteryChartPreferenceController extends AbstractPreferenceControll
|
||||
};
|
||||
}
|
||||
|
||||
private void logScreenUsageTime() {
|
||||
if (mBatteryUsageMap == null || mScreenOnTimeMap == null) {
|
||||
return;
|
||||
}
|
||||
final long totalScreenOnTime =
|
||||
mScreenOnTimeMap
|
||||
.get(BatteryChartViewModel.SELECTED_INDEX_ALL)
|
||||
.get(BatteryChartViewModel.SELECTED_INDEX_ALL);
|
||||
mMetricsFeatureProvider.action(
|
||||
mPrefContext,
|
||||
SettingsEnums.ACTION_BATTERY_USAGE_SCREEN_ON_TIME,
|
||||
(int) totalScreenOnTime);
|
||||
mMetricsFeatureProvider.action(
|
||||
mPrefContext,
|
||||
SettingsEnums.ACTION_BATTERY_USAGE_FOREGROUND_USAGE_TIME,
|
||||
(int) getTotalForegroundUsageTime());
|
||||
}
|
||||
|
||||
private long getTotalForegroundUsageTime() {
|
||||
if (mBatteryUsageMap == null) {
|
||||
return 0;
|
||||
}
|
||||
final BatteryDiffData totalBatteryUsageDiffData =
|
||||
mBatteryUsageMap
|
||||
.get(BatteryChartViewModel.SELECTED_INDEX_ALL)
|
||||
.get(BatteryChartViewModel.SELECTED_INDEX_ALL);
|
||||
if (totalBatteryUsageDiffData == null) {
|
||||
return 0;
|
||||
}
|
||||
long totalValue = 0;
|
||||
for (final BatteryDiffEntry entry : totalBatteryUsageDiffData.getAppDiffEntryList()) {
|
||||
totalValue += entry.mForegroundUsageTimeInMs;
|
||||
}
|
||||
return totalValue;
|
||||
}
|
||||
|
||||
private boolean isBatteryLevelDataInOneDay() {
|
||||
return mHourlyViewModels != null && mHourlyViewModels.size() == 1;
|
||||
}
|
||||
|
||||
@@ -1792,6 +1792,8 @@ public final class DataProcessor {
|
||||
systemAppsDiffEntry = new BatteryDiffEntry.SystemAppsBatteryDiffEntry(context);
|
||||
}
|
||||
systemAppsDiffEntry.mConsumePower += batteryDiffEntry.mConsumePower;
|
||||
systemAppsDiffEntry.mForegroundUsageTimeInMs +=
|
||||
batteryDiffEntry.mForegroundUsageTimeInMs;
|
||||
systemAppsDiffEntry.setTotalConsumePower(
|
||||
batteryDiffEntry.getTotalConsumePower());
|
||||
appListIterator.remove();
|
||||
|
||||
@@ -19,7 +19,6 @@ package com.android.settings.spa.app.specialaccess
|
||||
import android.Manifest
|
||||
import android.app.AppGlobals
|
||||
import android.app.AppOpsManager.MODE_DEFAULT
|
||||
import android.app.AppOpsManager.MODE_ERRORED
|
||||
import android.app.AppOpsManager.OP_REQUEST_INSTALL_PACKAGES
|
||||
import android.content.Context
|
||||
import android.content.pm.ApplicationInfo
|
||||
@@ -65,7 +64,6 @@ class InstallUnknownAppsListModel(private val context: Context) :
|
||||
context = context,
|
||||
app = app,
|
||||
op = OP_REQUEST_INSTALL_PACKAGES,
|
||||
modeForNotAllowed = MODE_ERRORED
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
@@ -16,7 +16,6 @@
|
||||
|
||||
package com.android.settings.spa.app.specialaccess
|
||||
|
||||
import android.app.AppOpsManager.MODE_ERRORED
|
||||
import android.app.AppOpsManager.OP_PICTURE_IN_PICTURE
|
||||
import android.content.Context
|
||||
import android.content.pm.ActivityInfo
|
||||
@@ -85,7 +84,6 @@ class PictureInPictureListModel(private val context: Context) :
|
||||
context = context,
|
||||
app = app,
|
||||
op = OP_PICTURE_IN_PICTURE,
|
||||
modeForNotAllowed = MODE_ERRORED,
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
@@ -32,8 +32,8 @@ object WifiControlAppListProvider : TogglePermissionAppListProvider {
|
||||
}
|
||||
|
||||
class WifiControlAppListModel(
|
||||
private val context: Context,
|
||||
private val packageManagers: IPackageManagers = PackageManagers
|
||||
context: Context,
|
||||
packageManagers: IPackageManagers = PackageManagers
|
||||
) : AppOpPermissionListModel(context, packageManagers) {
|
||||
override val pageTitleResId = R.string.change_wifi_state_title
|
||||
override val switchTitleResId = R.string.change_wifi_state_app_detail_switch
|
||||
@@ -44,6 +44,6 @@ class WifiControlAppListModel(
|
||||
|
||||
/** NETWORK_SETTINGS permission trumps CHANGE_WIFI_CONFIG. */
|
||||
override val broaderPermission = Manifest.permission.NETWORK_SETTINGS
|
||||
override val permissionHasAppopFlag = false
|
||||
override val permissionHasAppOpFlag = false
|
||||
override val modeForNotAllowed = MODE_IGNORED
|
||||
}
|
||||
|
||||
@@ -18,13 +18,24 @@ package com.android.settings.bluetooth;
|
||||
|
||||
import static com.google.common.truth.Truth.assertThat;
|
||||
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.util.FeatureFlagUtils;
|
||||
|
||||
import androidx.preference.Preference;
|
||||
|
||||
import com.android.settings.SettingsActivity;
|
||||
import com.android.settings.accessibility.AccessibilityHearingAidsFragment;
|
||||
import com.android.settings.testutils.FakeFeatureFactory;
|
||||
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
import org.mockito.Captor;
|
||||
import org.mockito.junit.MockitoJUnit;
|
||||
import org.mockito.junit.MockitoRule;
|
||||
import org.robolectric.RobolectricTestRunner;
|
||||
@@ -36,14 +47,18 @@ public class BluetoothDetailsHearingDeviceControlsControllerTest extends
|
||||
@Rule
|
||||
public final MockitoRule mockito = MockitoJUnit.rule();
|
||||
|
||||
@Captor
|
||||
private ArgumentCaptor<Intent> mIntentArgumentCaptor;
|
||||
private BluetoothDetailsHearingDeviceControlsController mController;
|
||||
|
||||
@Override
|
||||
public void setUp() {
|
||||
super.setUp();
|
||||
|
||||
mController = new BluetoothDetailsHearingDeviceControlsController(mContext, mFragment,
|
||||
FakeFeatureFactory.setupForTest();
|
||||
mController = new BluetoothDetailsHearingDeviceControlsController(mActivity, mFragment,
|
||||
mCachedDevice, mLifecycle);
|
||||
when(mCachedDevice.isHearingAidDevice()).thenReturn(true);
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -63,4 +78,23 @@ public class BluetoothDetailsHearingDeviceControlsControllerTest extends
|
||||
|
||||
assertThat(mController.isAvailable()).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void onPreferenceClick_hearingDeviceControlsKey_LaunchExpectedFragment() {
|
||||
final Preference hearingControlsKeyPreference = new Preference(mContext);
|
||||
hearingControlsKeyPreference.setKey(
|
||||
BluetoothDetailsHearingDeviceControlsController.KEY_HEARING_DEVICE_CONTROLS);
|
||||
|
||||
mController.onPreferenceClick(hearingControlsKeyPreference);
|
||||
|
||||
assertStartActivityWithExpectedFragment(mActivity,
|
||||
AccessibilityHearingAidsFragment.class.getName());
|
||||
}
|
||||
|
||||
private void assertStartActivityWithExpectedFragment(Context mockContext, String fragmentName) {
|
||||
verify(mockContext).startActivity(mIntentArgumentCaptor.capture());
|
||||
assertThat(mIntentArgumentCaptor.getValue()
|
||||
.getStringExtra(SettingsActivity.EXTRA_SHOW_FRAGMENT))
|
||||
.isEqualTo(fragmentName);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -101,6 +101,7 @@ public class StylusDevicesControllerTest {
|
||||
|
||||
when(mRm.getRoleHoldersAsUser(eq(RoleManager.ROLE_NOTES), any(UserHandle.class)))
|
||||
.thenReturn(Collections.singletonList(NOTES_PACKAGE_NAME));
|
||||
when(mRm.isRoleAvailable(RoleManager.ROLE_NOTES)).thenReturn(true);
|
||||
when(mContext.getPackageManager()).thenReturn(mPm);
|
||||
when(mPm.getApplicationInfo(eq(NOTES_PACKAGE_NAME),
|
||||
any(PackageManager.ApplicationInfoFlags.class))).thenReturn(new ApplicationInfo());
|
||||
@@ -235,16 +236,26 @@ public class StylusDevicesControllerTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void defaultNotesPreference_noRoleHolder_hidesNotesRoleApp() {
|
||||
public void defaultNotesPreference_roleHolderChanges_updatesPreference() {
|
||||
showScreen(mController);
|
||||
Preference defaultNotesPref = mPreferenceContainer.getPreference(0);
|
||||
when(mRm.getRoleHoldersAsUser(eq(RoleManager.ROLE_NOTES), any(UserHandle.class)))
|
||||
.thenReturn(Collections.emptyList());
|
||||
showScreen(mController);
|
||||
|
||||
for (int i = 0; i < mPreferenceContainer.getPreferenceCount(); i++) {
|
||||
Preference pref = mPreferenceContainer.getPreference(i);
|
||||
assertThat(pref.getTitle().toString()).isNotEqualTo(
|
||||
mContext.getString(R.string.stylus_default_notes_app));
|
||||
}
|
||||
assertThat(defaultNotesPref.getSummary().toString()).isEqualTo(
|
||||
mContext.getString(R.string.default_app_none));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void defaultNotesPreference_noRoleHolder_showNoneInSummary() {
|
||||
when(mRm.getRoleHoldersAsUser(eq(RoleManager.ROLE_NOTES), any(UserHandle.class)))
|
||||
.thenReturn(Collections.emptyList());
|
||||
showScreen(mController);
|
||||
|
||||
Preference defaultNotesPref = mPreferenceContainer.getPreference(0);
|
||||
assertThat(defaultNotesPref.getSummary().toString()).isEqualTo(
|
||||
mContext.getString(R.string.default_app_none));
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
@@ -18,10 +18,9 @@ package com.android.settings.deviceinfo.imei;
|
||||
|
||||
import static android.telephony.TelephonyManager.PHONE_TYPE_CDMA;
|
||||
import static android.telephony.TelephonyManager.PHONE_TYPE_GSM;
|
||||
|
||||
import static android.telephony.TelephonyManager.PHONE_TYPE_NONE;
|
||||
import static com.android.settings.core.BasePreferenceController.AVAILABLE;
|
||||
|
||||
import static org.mockito.ArgumentMatchers.anyInt;
|
||||
import static org.mockito.Mockito.anyInt;
|
||||
import static org.mockito.Mockito.doReturn;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.spy;
|
||||
@@ -32,15 +31,13 @@ import android.content.Context;
|
||||
import android.content.res.Resources;
|
||||
import android.os.UserManager;
|
||||
import android.telephony.TelephonyManager;
|
||||
|
||||
import androidx.fragment.app.Fragment;
|
||||
import androidx.fragment.app.FragmentManager;
|
||||
import androidx.preference.Preference;
|
||||
import androidx.preference.PreferenceCategory;
|
||||
import androidx.preference.PreferenceScreen;
|
||||
|
||||
import com.android.settings.R;
|
||||
|
||||
import com.android.settings.deviceinfo.simstatus.SlotSimStatus;
|
||||
import org.junit.Before;
|
||||
import org.junit.Ignore;
|
||||
import org.junit.Test;
|
||||
@@ -50,7 +47,6 @@ import org.mockito.Mock;
|
||||
import org.mockito.MockitoAnnotations;
|
||||
import org.robolectric.RobolectricTestRunner;
|
||||
import org.robolectric.RuntimeEnvironment;
|
||||
import org.robolectric.util.ReflectionHelpers;
|
||||
|
||||
@RunWith(RobolectricTestRunner.class)
|
||||
public class ImeiInfoPreferenceControllerTest {
|
||||
@@ -72,7 +68,9 @@ public class ImeiInfoPreferenceControllerTest {
|
||||
|
||||
private Context mContext;
|
||||
private Resources mResources;
|
||||
private ImeiInfoPreferenceController mDefaultController;
|
||||
private ImeiInfoPreferenceController mController;
|
||||
private ImeiInfoPreferenceController mSecondController;
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
@@ -83,56 +81,77 @@ public class ImeiInfoPreferenceControllerTest {
|
||||
when(mContext.getResources()).thenReturn(mResources);
|
||||
when(mResources.getBoolean(R.bool.config_show_sim_info)).thenReturn(true);
|
||||
|
||||
doReturn(mUserManager).when(mContext).getSystemService(UserManager.class);
|
||||
mController = spy(new ImeiInfoPreferenceController(mContext, "imei_info"));
|
||||
mController.setHost(mFragment);
|
||||
doReturn(AVAILABLE).when(mController).getAvailabilityStatus();
|
||||
mockService(Context.TELEPHONY_SERVICE, TelephonyManager.class, mTelephonyManager);
|
||||
mockService(Context.USER_SERVICE, UserManager.class, mUserManager);
|
||||
|
||||
when(mScreen.getContext()).thenReturn(mContext);
|
||||
doReturn(mSecondSimPreference).when(mController).createNewPreference(mContext);
|
||||
final String categoryKey = "device_detail_category";
|
||||
when(mScreen.findPreference(categoryKey)).thenReturn(mCategory);
|
||||
ReflectionHelpers.setField(mController, "mTelephonyManager", mTelephonyManager);
|
||||
when(mScreen.findPreference(mController.getPreferenceKey())).thenReturn(mPreference);
|
||||
final String prefKey = mController.getPreferenceKey();
|
||||
when(mPreference.getKey()).thenReturn(prefKey);
|
||||
when(mPreference.isVisible()).thenReturn(true);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void displayPreference_multiSimGsm_shouldAddSecondPreference() {
|
||||
ReflectionHelpers.setField(mController, "mIsMultiSim", true);
|
||||
when(mTelephonyManager.getPhoneCount()).thenReturn(2);
|
||||
when(mTelephonyManager.getCurrentPhoneType(anyInt())).thenReturn(PHONE_TYPE_GSM);
|
||||
private ImeiInfoPreferenceController createPreferenceController(SlotSimStatus slotSimStatus,
|
||||
String key, Preference preference, int phoneType) {
|
||||
ImeiInfoPreferenceController controller =
|
||||
spy(new ImeiInfoPreferenceController(mContext, key) {
|
||||
public int getPhoneType(int slotId) {
|
||||
return phoneType;
|
||||
}
|
||||
});
|
||||
controller.init(mFragment, slotSimStatus);
|
||||
doReturn(AVAILABLE).when(controller).getAvailabilityStatus();
|
||||
doReturn(preference).when(controller).createNewPreference(mContext);
|
||||
|
||||
mController.displayPreference(mScreen);
|
||||
when(mScreen.findPreference(key)).thenReturn(preference);
|
||||
when(preference.getKey()).thenReturn(key);
|
||||
when(preference.isVisible()).thenReturn(true);
|
||||
return controller;
|
||||
}
|
||||
|
||||
verify(mCategory).addPreference(mSecondSimPreference);
|
||||
private void setupPhoneCount(int count, int phoneType1, int phoneType2) {
|
||||
when(mTelephonyManager.getPhoneCount()).thenReturn(count);
|
||||
|
||||
SlotSimStatus slotSimStatus = new SlotSimStatus(mContext);
|
||||
mController = createPreferenceController(slotSimStatus,
|
||||
"imei_info1", mPreference, phoneType1);
|
||||
mSecondController = createPreferenceController(slotSimStatus,
|
||||
"imei_info2", mSecondSimPreference, phoneType2);
|
||||
}
|
||||
|
||||
@Ignore
|
||||
@Test
|
||||
public void displayPreference_multiSimGsm_shouldAddSecondPreference() {
|
||||
setupPhoneCount(2, PHONE_TYPE_GSM, PHONE_TYPE_GSM);
|
||||
|
||||
mDefaultController.displayPreference(mScreen);
|
||||
|
||||
verify(mCategory).addPreference(mSecondSimPreference);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void displayPreference_singleSimCdmaPhone_shouldSetSingleSimCdmaTitleAndMeid() {
|
||||
ReflectionHelpers.setField(mController, "mIsMultiSim", false);
|
||||
final String meid = "125132215123";
|
||||
when(mTelephonyManager.getCurrentPhoneType(anyInt())).thenReturn(PHONE_TYPE_CDMA);
|
||||
setupPhoneCount(1, PHONE_TYPE_CDMA, PHONE_TYPE_NONE);
|
||||
|
||||
final String meid = "Tap to show info";
|
||||
when(mTelephonyManager.getMeid(anyInt())).thenReturn(meid);
|
||||
|
||||
mController.displayPreference(mScreen);
|
||||
mController.updateState(mPreference);
|
||||
|
||||
verify(mPreference).setTitle(mContext.getString(R.string.status_meid_number));
|
||||
verify(mPreference).setSummary(meid);
|
||||
}
|
||||
|
||||
@Ignore
|
||||
@Test
|
||||
public void displayPreference_multiSimCdmaPhone_shouldSetMultiSimCdmaTitleAndMeid() {
|
||||
ReflectionHelpers.setField(mController, "mIsMultiSim", true);
|
||||
final String meid = "125132215123";
|
||||
when(mTelephonyManager.getPhoneCount()).thenReturn(2);
|
||||
when(mTelephonyManager.getCurrentPhoneType(anyInt())).thenReturn(PHONE_TYPE_CDMA);
|
||||
setupPhoneCount(2, PHONE_TYPE_CDMA, PHONE_TYPE_CDMA);
|
||||
|
||||
final String meid = "Tap to show info";
|
||||
when(mTelephonyManager.getMeid(anyInt())).thenReturn(meid);
|
||||
|
||||
mController.displayPreference(mScreen);
|
||||
mController.updateState(mPreference);
|
||||
mSecondController.displayPreference(mScreen);
|
||||
mSecondController.updateState(mSecondSimPreference);
|
||||
|
||||
verify(mPreference).setTitle(mContext.getString(R.string.meid_multi_sim, 1 /* sim slot */));
|
||||
verify(mSecondSimPreference).setTitle(
|
||||
@@ -141,30 +160,31 @@ public class ImeiInfoPreferenceControllerTest {
|
||||
verify(mSecondSimPreference).setSummary(meid);
|
||||
}
|
||||
|
||||
@Ignore
|
||||
@Test
|
||||
public void displayPreference_singleSimGsmPhone_shouldSetSingleSimGsmTitleAndImei() {
|
||||
ReflectionHelpers.setField(mController, "mIsMultiSim", false);
|
||||
final String imei = "125132215123";
|
||||
when(mTelephonyManager.getCurrentPhoneType(anyInt())).thenReturn(PHONE_TYPE_GSM);
|
||||
setupPhoneCount(1, PHONE_TYPE_GSM, PHONE_TYPE_NONE);
|
||||
|
||||
final String imei = "Tap to show info";
|
||||
when(mTelephonyManager.getImei(anyInt())).thenReturn(imei);
|
||||
|
||||
mController.displayPreference(mScreen);
|
||||
mController.updateState(mPreference);
|
||||
|
||||
verify(mPreference).setTitle(mContext.getString(R.string.status_imei));
|
||||
verify(mPreference).setSummary(imei);
|
||||
}
|
||||
|
||||
@Ignore
|
||||
@Test
|
||||
public void displayPreference_multiSimGsmPhone_shouldSetMultiSimGsmTitleAndImei() {
|
||||
ReflectionHelpers.setField(mController, "mIsMultiSim", true);
|
||||
final String imei = "125132215123";
|
||||
when(mTelephonyManager.getPhoneCount()).thenReturn(2);
|
||||
when(mTelephonyManager.getCurrentPhoneType(anyInt())).thenReturn(PHONE_TYPE_GSM);
|
||||
setupPhoneCount(2, PHONE_TYPE_GSM, PHONE_TYPE_GSM);
|
||||
|
||||
final String imei = "Tap to show info";
|
||||
when(mTelephonyManager.getImei(anyInt())).thenReturn(imei);
|
||||
|
||||
mController.displayPreference(mScreen);
|
||||
mController.updateState(mPreference);
|
||||
mSecondController.displayPreference(mScreen);
|
||||
mSecondController.updateState(mSecondSimPreference);
|
||||
|
||||
verify(mPreference).setTitle(mContext.getString(R.string.imei_multi_sim, 1 /* sim slot */));
|
||||
verify(mSecondSimPreference).setTitle(
|
||||
@@ -175,6 +195,8 @@ public class ImeiInfoPreferenceControllerTest {
|
||||
|
||||
@Test
|
||||
public void handlePreferenceTreeClick_shouldStartDialogFragment() {
|
||||
setupPhoneCount(1, PHONE_TYPE_GSM, PHONE_TYPE_NONE);
|
||||
|
||||
when(mFragment.getChildFragmentManager())
|
||||
.thenReturn(mock(FragmentManager.class, Answers.RETURNS_DEEP_STUBS));
|
||||
when(mPreference.getTitle()).thenReturn("SomeTitle");
|
||||
@@ -184,4 +206,9 @@ public class ImeiInfoPreferenceControllerTest {
|
||||
|
||||
verify(mFragment).getChildFragmentManager();
|
||||
}
|
||||
|
||||
private <T> void mockService(String serviceName, Class<T> serviceClass, T service) {
|
||||
when(mContext.getSystemServiceName(serviceClass)).thenReturn(serviceName);
|
||||
when(mContext.getSystemService(serviceName)).thenReturn(service);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -58,26 +58,30 @@ public class FingerprintEnrollmentActivityTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void lunchWithoutCredential() {
|
||||
public void launchWithoutCredential() {
|
||||
launchFingerprintEnrollActivity(true);
|
||||
Assert.assertNotNull(mDevice.wait(Until.hasObject(
|
||||
By.text("Choose your backup screen lock method")), IDLE_TIMEOUT));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void lunchWithCredential() {
|
||||
public void launchWithCredential() {
|
||||
LockScreenUtil.setLockscreen(LockScreenUtil.LockscreenType.PIN, "1234", true);
|
||||
launchFingerprintEnrollActivity(true);
|
||||
Assert.assertNotNull(mDevice.wait(Until.hasObject(
|
||||
By.text("More")), IDLE_TIMEOUT));
|
||||
for (long i = 0; i < IDLE_TIMEOUT; i += 100L) {
|
||||
if (mDevice.wait(Until.hasObject(By.text("More")), 50L) != null) {
|
||||
break;
|
||||
} else if (mDevice.wait(Until.hasObject(By.text("I agree")), 50L) != null) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
//click more btn twice and the introduction should stay in the last page
|
||||
UiObject2 moreBtn = mDevice.findObject(By.text("More"));
|
||||
moreBtn.click();
|
||||
Assert.assertNotNull(mDevice.wait(Until.hasObject(
|
||||
By.text("More")), IDLE_TIMEOUT));
|
||||
moreBtn = mDevice.findObject(By.text("More"));
|
||||
moreBtn.click();
|
||||
//click more btn at most twice and the introduction should stay in the last page
|
||||
UiObject2 moreBtn;
|
||||
for (int i = 0; i < 2 && (moreBtn = mDevice.findObject(By.text("More"))) != null; ++i) {
|
||||
moreBtn.click();
|
||||
mDevice.wait(Until.hasObject(By.text("More")), IDLE_TIMEOUT);
|
||||
}
|
||||
|
||||
Assert.assertNotNull(mDevice.wait(Until.hasObject(
|
||||
By.text("I agree")), IDLE_TIMEOUT));
|
||||
@@ -114,5 +118,4 @@ public class FingerprintEnrollmentActivityTest {
|
||||
intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||
mContext.startActivity(intent);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -23,16 +23,17 @@ import static android.hardware.fingerprint.FingerprintSensorProperties.TYPE_UDFP
|
||||
import static android.hardware.fingerprint.FingerprintSensorProperties.TYPE_UDFPS_ULTRASONIC;
|
||||
import static android.hardware.fingerprint.FingerprintSensorProperties.TYPE_UNKNOWN;
|
||||
|
||||
import static com.android.settings.biometrics2.util.FingerprintManagerUtil.setupFingerprintEnrolledFingerprints;
|
||||
import static com.android.settings.biometrics2.util.FingerprintManagerUtil.setupFingerprintFirstSensor;
|
||||
|
||||
import static com.google.common.truth.Truth.assertThat;
|
||||
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.res.Resources;
|
||||
import android.hardware.biometrics.SensorProperties;
|
||||
import android.hardware.fingerprint.Fingerprint;
|
||||
import android.hardware.fingerprint.FingerprintManager;
|
||||
import android.hardware.fingerprint.FingerprintSensorProperties;
|
||||
import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.test.core.app.ApplicationProvider;
|
||||
@@ -48,6 +49,8 @@ import org.mockito.Mock;
|
||||
import org.mockito.junit.MockitoJUnit;
|
||||
import org.mockito.junit.MockitoRule;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
public class FingerprintRepositoryTest {
|
||||
|
||||
@@ -66,31 +69,49 @@ public class FingerprintRepositoryTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCanAssumeSensorType() {
|
||||
public void testCanAssumeSensorType_forUnknownSensor() {
|
||||
setupFingerprintFirstSensor(mFingerprintManager, TYPE_UNKNOWN, 1);
|
||||
assertThat(mFingerprintRepository.canAssumeUdfps()).isFalse();
|
||||
assertThat(mFingerprintRepository.canAssumeSfps()).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCanAssumeSensorType_forRearSensor() {
|
||||
setupFingerprintFirstSensor(mFingerprintManager, TYPE_REAR, 1);
|
||||
assertThat(mFingerprintRepository.canAssumeUdfps()).isFalse();
|
||||
assertThat(mFingerprintRepository.canAssumeSfps()).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCanAssumeSensorType_forUdfpsUltrasonicSensor() {
|
||||
setupFingerprintFirstSensor(mFingerprintManager, TYPE_UDFPS_ULTRASONIC, 1);
|
||||
assertThat(mFingerprintRepository.canAssumeUdfps()).isTrue();
|
||||
assertThat(mFingerprintRepository.canAssumeSfps()).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCanAssumeSensorType_forUdfpsOpticalSensor() {
|
||||
setupFingerprintFirstSensor(mFingerprintManager, TYPE_UDFPS_OPTICAL, 1);
|
||||
assertThat(mFingerprintRepository.canAssumeUdfps()).isTrue();
|
||||
assertThat(mFingerprintRepository.canAssumeSfps()).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCanAssumeSensorType_forPowerButtonSensor() {
|
||||
setupFingerprintFirstSensor(mFingerprintManager, TYPE_POWER_BUTTON, 1);
|
||||
assertThat(mFingerprintRepository.canAssumeUdfps()).isFalse();
|
||||
assertThat(mFingerprintRepository.canAssumeSfps()).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCanAssumeSensorType_forHomeButtonSensor() {
|
||||
setupFingerprintFirstSensor(mFingerprintManager, TYPE_HOME_BUTTON, 1);
|
||||
assertThat(mFingerprintRepository.canAssumeUdfps()).isFalse();
|
||||
assertThat(mFingerprintRepository.canAssumeSfps()).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetMaxFingerprints() {
|
||||
setupFingerprintFirstSensor(mFingerprintManager, TYPE_UNKNOWN, 44);
|
||||
assertThat(mFingerprintRepository.getMaxFingerprints()).isEqualTo(44);
|
||||
|
||||
setupFingerprintFirstSensor(mFingerprintManager, TYPE_UNKNOWN, 999);
|
||||
assertThat(mFingerprintRepository.getMaxFingerprints()).isEqualTo(999);
|
||||
}
|
||||
@@ -122,4 +143,31 @@ public class FingerprintRepositoryTest {
|
||||
"suw_max_fingerprints_enrollable");
|
||||
when(mockedResources.getInteger(resId)).thenReturn(numOfFp);
|
||||
}
|
||||
|
||||
public static void setupFingerprintFirstSensor(
|
||||
@NonNull FingerprintManager mockedFingerprintManager,
|
||||
@FingerprintSensorProperties.SensorType int sensorType,
|
||||
int maxEnrollmentsPerUser) {
|
||||
|
||||
final ArrayList<FingerprintSensorPropertiesInternal> props = new ArrayList<>();
|
||||
props.add(new FingerprintSensorPropertiesInternal(
|
||||
0 /* sensorId */,
|
||||
SensorProperties.STRENGTH_STRONG,
|
||||
maxEnrollmentsPerUser,
|
||||
new ArrayList<>() /* componentInfo */,
|
||||
sensorType,
|
||||
true /* resetLockoutRequiresHardwareAuthToken */));
|
||||
when(mockedFingerprintManager.getSensorPropertiesInternal()).thenReturn(props);
|
||||
}
|
||||
|
||||
public static void setupFingerprintEnrolledFingerprints(
|
||||
@NonNull FingerprintManager mockedFingerprintManager,
|
||||
int userId,
|
||||
int enrolledFingerprints) {
|
||||
final ArrayList<Fingerprint> ret = new ArrayList<>();
|
||||
for (int i = 0; i < enrolledFingerprints; ++i) {
|
||||
ret.add(new Fingerprint("name", 0, 0, 0L));
|
||||
}
|
||||
when(mockedFingerprintManager.getEnrolledFingerprints(userId)).thenReturn(ret);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -45,6 +45,7 @@ import static com.android.settings.password.ChooseLockSettingsHelper.EXTRA_KEY_G
|
||||
import static com.google.common.truth.Truth.assertThat;
|
||||
import static com.google.common.truth.Truth.assertWithMessage;
|
||||
|
||||
import static org.mockito.Mockito.doAnswer;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import android.annotation.NonNull;
|
||||
@@ -71,6 +72,8 @@ import org.mockito.Mock;
|
||||
import org.mockito.junit.MockitoJUnit;
|
||||
import org.mockito.junit.MockitoRule;
|
||||
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
public class AutoCredentialViewModelTest {
|
||||
|
||||
@@ -299,13 +302,18 @@ public class AutoCredentialViewModelTest {
|
||||
when(mLockPatternUtils.verifyGatekeeperPasswordHandle(gkPwHandle, newChallenge, userId))
|
||||
.thenReturn(newGoodCredential(gkPwHandle, new byte[] { 1 }));
|
||||
|
||||
final AtomicBoolean hasCalledRemoveGkPwHandle = new AtomicBoolean();
|
||||
doAnswer(invocation -> {
|
||||
hasCalledRemoveGkPwHandle.set(true);
|
||||
return null;
|
||||
}).when(mLockPatternUtils).removeGatekeeperPasswordHandle(gkPwHandle);
|
||||
|
||||
// Run credential check
|
||||
@CredentialAction final int action = mViewModel.checkCredential();
|
||||
|
||||
// Check viewModel behavior
|
||||
assertThat(action).isEqualTo(CREDENTIAL_IS_GENERATING_CHALLENGE);
|
||||
assertThat(mViewModel.getGenerateChallengeFailedLiveData().getValue()).isNull();
|
||||
assertThat(mChallengeGenerator.mCallbackRunCount).isEqualTo(1);
|
||||
|
||||
// Check data inside CredentialModel
|
||||
final Bundle extras = mViewModel.createCredentialIntentExtra();
|
||||
@@ -314,6 +322,8 @@ public class AutoCredentialViewModelTest {
|
||||
assertThat(extras.getByteArray(EXTRA_KEY_CHALLENGE_TOKEN)).isNotNull();
|
||||
assertThat(extras.getLong(EXTRA_KEY_GK_PW_HANDLE)).isEqualTo(INVALID_GK_PW_HANDLE);
|
||||
assertThat(extras.getLong(EXTRA_KEY_CHALLENGE)).isNotEqualTo(INVALID_CHALLENGE);
|
||||
assertThat(mChallengeGenerator.mCallbackRunCount).isEqualTo(1);
|
||||
assertThat(hasCalledRemoveGkPwHandle.get()).isFalse();
|
||||
|
||||
// Check createGeneratingChallengeExtras()
|
||||
final Bundle generatingChallengeExtras = mViewModel.createGeneratingChallengeExtras();
|
||||
@@ -511,6 +521,12 @@ public class AutoCredentialViewModelTest {
|
||||
when(mLockPatternUtils.verifyGatekeeperPasswordHandle(gkPwHandle, newChallenge, userId))
|
||||
.thenReturn(newGoodCredential(gkPwHandle, new byte[] { 1 }));
|
||||
|
||||
final AtomicBoolean hasCalledRemoveGkPwHandle = new AtomicBoolean();
|
||||
doAnswer(invocation -> {
|
||||
hasCalledRemoveGkPwHandle.set(true);
|
||||
return null;
|
||||
}).when(mLockPatternUtils).removeGatekeeperPasswordHandle(gkPwHandle);
|
||||
|
||||
// Run checkNewCredentialFromActivityResult()
|
||||
final Intent intent = new Intent().putExtra(EXTRA_KEY_GK_PW_HANDLE, gkPwHandle);
|
||||
final boolean ret = mViewModel.checkNewCredentialFromActivityResult(true,
|
||||
@@ -524,6 +540,7 @@ public class AutoCredentialViewModelTest {
|
||||
assertThat(extras.getByteArray(EXTRA_KEY_CHALLENGE_TOKEN)).isNotNull();
|
||||
assertThat(extras.getLong(EXTRA_KEY_GK_PW_HANDLE)).isEqualTo(INVALID_GK_PW_HANDLE);
|
||||
assertThat(mChallengeGenerator.mCallbackRunCount).isEqualTo(1);
|
||||
assertThat(hasCalledRemoveGkPwHandle.get()).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -541,6 +558,12 @@ public class AutoCredentialViewModelTest {
|
||||
when(mLockPatternUtils.verifyGatekeeperPasswordHandle(gkPwHandle, newChallenge, userId))
|
||||
.thenReturn(newGoodCredential(gkPwHandle, new byte[] { 1 }));
|
||||
|
||||
final AtomicBoolean hasCalledRemoveGkPwHandle = new AtomicBoolean();
|
||||
doAnswer(invocation -> {
|
||||
hasCalledRemoveGkPwHandle.set(true);
|
||||
return null;
|
||||
}).when(mLockPatternUtils).removeGatekeeperPasswordHandle(gkPwHandle);
|
||||
|
||||
// Run checkNewCredentialFromActivityResult()
|
||||
final Intent intent = new Intent().putExtra(EXTRA_KEY_GK_PW_HANDLE, gkPwHandle);
|
||||
final boolean ret = mViewModel.checkNewCredentialFromActivityResult(false,
|
||||
@@ -554,6 +577,7 @@ public class AutoCredentialViewModelTest {
|
||||
assertThat(extras.getByteArray(EXTRA_KEY_CHALLENGE_TOKEN)).isNotNull();
|
||||
assertThat(extras.getLong(EXTRA_KEY_GK_PW_HANDLE)).isEqualTo(INVALID_GK_PW_HANDLE);
|
||||
assertThat(mChallengeGenerator.mCallbackRunCount).isEqualTo(1);
|
||||
assertThat(hasCalledRemoveGkPwHandle.get()).isTrue();
|
||||
}
|
||||
|
||||
public static class TestChallengeGenerator implements ChallengeGenerator {
|
||||
|
||||
@@ -20,6 +20,8 @@ import static android.hardware.fingerprint.FingerprintSensorProperties.TYPE_REAR
|
||||
import static android.hardware.fingerprint.FingerprintSensorProperties.TYPE_UDFPS_OPTICAL;
|
||||
import static android.hardware.fingerprint.FingerprintSensorProperties.TYPE_UDFPS_ULTRASONIC;
|
||||
|
||||
import static com.android.settings.biometrics2.data.repository.FingerprintRepositoryTest.setupFingerprintEnrolledFingerprints;
|
||||
import static com.android.settings.biometrics2.data.repository.FingerprintRepositoryTest.setupFingerprintFirstSensor;
|
||||
import static com.android.settings.biometrics2.data.repository.FingerprintRepositoryTest.setupSuwMaxFingerprintsEnrollable;
|
||||
import static com.android.settings.biometrics2.ui.model.FingerprintEnrollIntroStatus.FINGERPRINT_ENROLLABLE_ERROR_REACH_MAX;
|
||||
import static com.android.settings.biometrics2.ui.model.FingerprintEnrollIntroStatus.FINGERPRINT_ENROLLABLE_OK;
|
||||
@@ -32,8 +34,6 @@ import static com.android.settings.biometrics2.util.EnrollmentRequestUtil.newIsS
|
||||
import static com.android.settings.biometrics2.util.EnrollmentRequestUtil.newIsSuwPortalRequest;
|
||||
import static com.android.settings.biometrics2.util.EnrollmentRequestUtil.newIsSuwRequest;
|
||||
import static com.android.settings.biometrics2.util.EnrollmentRequestUtil.newIsSuwSuggestedActionFlowRequest;
|
||||
import static com.android.settings.biometrics2.util.FingerprintManagerUtil.setupFingerprintEnrolledFingerprints;
|
||||
import static com.android.settings.biometrics2.util.FingerprintManagerUtil.setupFingerprintFirstSensor;
|
||||
|
||||
import static com.google.common.truth.Truth.assertThat;
|
||||
|
||||
@@ -134,26 +134,47 @@ public class FingerprintEnrollIntroViewModelTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOnStartToUpdateEnrollableStatus_isNotSuw() {
|
||||
testOnStartToUpdateEnrollableStatus(newAllFalseRequest(mApplication));
|
||||
public void testOnStartToUpdateEnrollableStatusOk_isNotSuw() {
|
||||
testOnStartToUpdateEnrollableStatusOk(newAllFalseRequest(mApplication));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOnStartToUpdateEnrollableStatus_isSuwDeferred() {
|
||||
testOnStartToUpdateEnrollableStatus(newIsSuwDeferredRequest(mApplication));
|
||||
public void testOnStartToUpdateEnrollableStatusReachMax_isNotSuw() {
|
||||
testOnStartToUpdateEnrollableStatusReachMax(newAllFalseRequest(mApplication));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOnStartToUpdateEnrollableStatus_isSuwPortal() {
|
||||
testOnStartToUpdateEnrollableStatus(newIsSuwPortalRequest(mApplication));
|
||||
public void testOnStartToUpdateEnrollableStatusOk_isSuwDeferred() {
|
||||
testOnStartToUpdateEnrollableStatusOk(newIsSuwDeferredRequest(mApplication));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOnStartToUpdateEnrollableStatus_isSuwSuggestedActionFlow() {
|
||||
testOnStartToUpdateEnrollableStatus(newIsSuwSuggestedActionFlowRequest(mApplication));
|
||||
public void testOnStartToUpdateEnrollableStatusReachMax_isSuwDeferred() {
|
||||
testOnStartToUpdateEnrollableStatusReachMax(newIsSuwDeferredRequest(mApplication));
|
||||
}
|
||||
|
||||
private void testOnStartToUpdateEnrollableStatus(@NonNull EnrollmentRequest request) {
|
||||
@Test
|
||||
public void testOnStartToUpdateEnrollableStatusOk_isSuwPortal() {
|
||||
testOnStartToUpdateEnrollableStatusOk(newIsSuwPortalRequest(mApplication));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOnStartToUpdateEnrollableStatusReachMax_isSuwPortal() {
|
||||
testOnStartToUpdateEnrollableStatusReachMax(newIsSuwPortalRequest(mApplication));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOnStartToUpdateEnrollableStatusOk_isSuwSuggestedActionFlow() {
|
||||
testOnStartToUpdateEnrollableStatusOk(newIsSuwSuggestedActionFlowRequest(mApplication));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOnStartToUpdateEnrollableStatusReachMax_isSuwSuggestedActionFlow() {
|
||||
testOnStartToUpdateEnrollableStatusReachMax(
|
||||
newIsSuwSuggestedActionFlowRequest(mApplication));
|
||||
}
|
||||
|
||||
private void testOnStartToUpdateEnrollableStatusOk(@NonNull EnrollmentRequest request) {
|
||||
final int userId = 45;
|
||||
mViewModel.setUserId(userId);
|
||||
mViewModel.setEnrollmentRequest(request);
|
||||
@@ -163,19 +184,28 @@ public class FingerprintEnrollIntroViewModelTest {
|
||||
mViewModel.onStart(mLifecycleOwner);
|
||||
FingerprintEnrollIntroStatus status = mViewModel.getPageStatusLiveData().getValue();
|
||||
assertThat(status.getEnrollableStatus()).isEqualTo(FINGERPRINT_ENROLLABLE_OK);
|
||||
}
|
||||
|
||||
private void testOnStartToUpdateEnrollableStatusReachMax(@NonNull EnrollmentRequest request) {
|
||||
final int userId = 45;
|
||||
mViewModel.setUserId(userId);
|
||||
mViewModel.setEnrollmentRequest(request);
|
||||
|
||||
setupFingerprintEnrolledFingerprints(mFingerprintManager, userId, 5);
|
||||
setupFingerprintFirstSensor(mFingerprintManager, TYPE_UDFPS_OPTICAL, 5);
|
||||
mViewModel.onStart(mLifecycleOwner);
|
||||
status = mViewModel.getPageStatusLiveData().getValue();
|
||||
FingerprintEnrollIntroStatus status = mViewModel.getPageStatusLiveData().getValue();
|
||||
assertThat(status.getEnrollableStatus()).isEqualTo(FINGERPRINT_ENROLLABLE_ERROR_REACH_MAX);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void textCanAssumeUdfps() {
|
||||
public void textCanAssumeUdfps_forUdfpsUltrasonicSensor() {
|
||||
setupFingerprintFirstSensor(mFingerprintManager, TYPE_UDFPS_ULTRASONIC, 1);
|
||||
assertThat(mViewModel.canAssumeUdfps()).isEqualTo(true);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void textCanAssumeUdfps_forRearSensor() {
|
||||
setupFingerprintFirstSensor(mFingerprintManager, TYPE_REAR, 1);
|
||||
assertThat(mViewModel.canAssumeUdfps()).isEqualTo(false);
|
||||
}
|
||||
@@ -238,7 +268,7 @@ public class FingerprintEnrollIntroViewModelTest {
|
||||
assertThat(status.getEnrollableStatus()).isEqualTo(FINGERPRINT_ENROLLABLE_OK);
|
||||
|
||||
// Perform click on `next`
|
||||
mViewModel.onNextButtonClick(null);
|
||||
mViewModel.onNextButtonClick();
|
||||
|
||||
assertThat(mViewModel.getActionLiveData().getValue())
|
||||
.isEqualTo(FINGERPRINT_ENROLL_INTRO_ACTION_CONTINUE_ENROLL);
|
||||
@@ -258,7 +288,7 @@ public class FingerprintEnrollIntroViewModelTest {
|
||||
assertThat(status.getEnrollableStatus()).isEqualTo(FINGERPRINT_ENROLLABLE_ERROR_REACH_MAX);
|
||||
|
||||
// Perform click on `next`
|
||||
mViewModel.onNextButtonClick(null);
|
||||
mViewModel.onNextButtonClick();
|
||||
|
||||
assertThat(mViewModel.getActionLiveData().getValue())
|
||||
.isEqualTo(FINGERPRINT_ENROLL_INTRO_ACTION_DONE_AND_FINISH);
|
||||
@@ -266,7 +296,7 @@ public class FingerprintEnrollIntroViewModelTest {
|
||||
|
||||
@Test
|
||||
public void testOnSkipOrCancelButtonClick() {
|
||||
mViewModel.onSkipOrCancelButtonClick(null);
|
||||
mViewModel.onSkipOrCancelButtonClick();
|
||||
|
||||
assertThat(mViewModel.getActionLiveData().getValue())
|
||||
.isEqualTo(FINGERPRINT_ENROLL_INTRO_ACTION_SKIP_OR_CANCEL);
|
||||
|
||||
@@ -20,10 +20,10 @@ import static com.android.settings.biometrics.BiometricEnrollBase.RESULT_FINISHE
|
||||
import static com.android.settings.biometrics.BiometricEnrollBase.RESULT_SKIP;
|
||||
import static com.android.settings.biometrics.BiometricEnrollBase.RESULT_TIMEOUT;
|
||||
import static com.android.settings.biometrics.fingerprint.SetupFingerprintEnrollIntroduction.EXTRA_FINGERPRINT_ENROLLED_COUNT;
|
||||
import static com.android.settings.biometrics2.data.repository.FingerprintRepositoryTest.setupFingerprintEnrolledFingerprints;
|
||||
import static com.android.settings.biometrics2.ui.viewmodel.FingerprintEnrollmentViewModel.SAVED_STATE_IS_WAITING_ACTIVITY_RESULT;
|
||||
import static com.android.settings.biometrics2.util.EnrollmentRequestUtil.newAllFalseRequest;
|
||||
import static com.android.settings.biometrics2.util.EnrollmentRequestUtil.newIsSuwRequest;
|
||||
import static com.android.settings.biometrics2.util.FingerprintManagerUtil.setupFingerprintEnrolledFingerprints;
|
||||
|
||||
import static com.google.common.truth.Truth.assertThat;
|
||||
|
||||
|
||||
@@ -1,58 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2022 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.biometrics2.util;
|
||||
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import android.hardware.biometrics.SensorProperties;
|
||||
import android.hardware.fingerprint.Fingerprint;
|
||||
import android.hardware.fingerprint.FingerprintManager;
|
||||
import android.hardware.fingerprint.FingerprintSensorProperties;
|
||||
import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
public class FingerprintManagerUtil {
|
||||
|
||||
public static void setupFingerprintFirstSensor(
|
||||
@NonNull FingerprintManager mockedFingerprintManager,
|
||||
@FingerprintSensorProperties.SensorType int sensorType,
|
||||
int maxEnrollmentsPerUser) {
|
||||
final ArrayList<FingerprintSensorPropertiesInternal> props = new ArrayList<>();
|
||||
props.add(new FingerprintSensorPropertiesInternal(
|
||||
0 /* sensorId */,
|
||||
SensorProperties.STRENGTH_STRONG,
|
||||
maxEnrollmentsPerUser,
|
||||
new ArrayList<>() /* componentInfo */,
|
||||
sensorType,
|
||||
true /* resetLockoutRequiresHardwareAuthToken */));
|
||||
when(mockedFingerprintManager.getSensorPropertiesInternal()).thenReturn(props);
|
||||
}
|
||||
|
||||
public static void setupFingerprintEnrolledFingerprints(
|
||||
@NonNull FingerprintManager mockedFingerprintManager,
|
||||
int userId,
|
||||
int enrolledFingerprints) {
|
||||
final ArrayList<Fingerprint> ret = new ArrayList<>();
|
||||
for (int i = 0; i < enrolledFingerprints; ++i) {
|
||||
ret.add(new Fingerprint("name", 0, 0, 0L));
|
||||
}
|
||||
when(mockedFingerprintManager.getEnrolledFingerprints(userId)).thenReturn(ret);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
/*
|
||||
* Copyright (C) 2023 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.settings.communal;
|
||||
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.mockito.Mockito.spy;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.res.Resources;
|
||||
import android.os.UserManager;
|
||||
|
||||
import androidx.test.core.app.ApplicationProvider;
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||
|
||||
import com.android.settings.testutils.ResourcesUtils;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.MockitoAnnotations;
|
||||
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
public class CommunalPreferenceControllerTest {
|
||||
@Mock
|
||||
private UserManager mUserManager;
|
||||
|
||||
private Resources mResources;
|
||||
private CommunalPreferenceController mController;
|
||||
|
||||
private static final String PREF_KEY = "test_key";
|
||||
|
||||
@Before
|
||||
public void setup() {
|
||||
MockitoAnnotations.initMocks(this);
|
||||
final Context context = spy(ApplicationProvider.getApplicationContext());
|
||||
mResources = spy(context.getResources());
|
||||
|
||||
mController = new CommunalPreferenceController(context, PREF_KEY);
|
||||
|
||||
when(context.getResources()).thenReturn(mResources);
|
||||
when(context.getSystemService(UserManager.class)).thenReturn(mUserManager);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void isAvailable_communalEnabled_shouldBeTrueForSystemUser() {
|
||||
setCommunalEnabled(true);
|
||||
when(mUserManager.isSystemUser()).thenReturn(true);
|
||||
assertTrue(mController.isAvailable());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void isAvailable_communalEnabled_shouldBeFalseForNonSystemUser() {
|
||||
setCommunalEnabled(true);
|
||||
when(mUserManager.isSystemUser()).thenReturn(false);
|
||||
assertFalse(mController.isAvailable());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void isAvailable_communalDisabled_shouldBeFalseForSystemUser() {
|
||||
setCommunalEnabled(false);
|
||||
when(mUserManager.isSystemUser()).thenReturn(true);
|
||||
assertFalse(mController.isAvailable());
|
||||
}
|
||||
|
||||
private void setCommunalEnabled(boolean enabled) {
|
||||
final int boolId = ResourcesUtils.getResourcesId(
|
||||
ApplicationProvider.getApplicationContext(), "bool",
|
||||
"config_show_communal_settings");
|
||||
when(mResources.getBoolean(boolId)).thenReturn(enabled);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user