Snap for 9475114 from 22fc2f387b to udc-release

Change-Id: I080ca2e0f74fafd3e965dab4a5256d1d902ffd54
This commit is contained in:
Android Build Coastguard Worker
2023-01-11 02:32:03 +00:00
62 changed files with 3818 additions and 372 deletions

View File

@@ -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" />

View 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>

View 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>

View File

@@ -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"

View 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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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 */ }

View File

@@ -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) {

View File

@@ -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

View File

@@ -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);

View File

@@ -91,6 +91,11 @@ public class FingerprintUpdater {
BiometricsSafetySource.onBiometricsChanged(mContext); // biometrics data changed
}
}
@Override
public void onAcquired(boolean isAcquiredGood) {
mCallback.onAcquired(isAcquiredGood);
}
}
/**

View File

@@ -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();
}
}

View File

@@ -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);
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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;

View File

@@ -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;
}
}

View File

@@ -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);
}

View File

@@ -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 + "}";
}
}

View File

@@ -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);
}
}

View File

@@ -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);
}
}

View File

@@ -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);
}
}

View File

@@ -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

View File

@@ -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) {

View File

@@ -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);
}
}
}

View File

@@ -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);
}
}

View File

@@ -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 {

View File

@@ -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();
}
}

View File

@@ -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();
}
}

View File

@@ -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();
}
}

View File

@@ -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);
}

View File

@@ -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();
}
}
}
}

View File

@@ -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();
}
}

View File

@@ -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();
}
}

View File

@@ -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();
}
}

View File

@@ -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);

View File

@@ -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 = {

View File

@@ -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);

View File

@@ -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);
}

View File

@@ -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();

View File

@@ -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;
}
}

View File

@@ -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;
}

View File

@@ -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();

View File

@@ -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
),
)

View File

@@ -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,
),
)

View File

@@ -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
}

View File

@@ -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);
}
}

View File

@@ -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

View File

@@ -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);
}
}

View File

@@ -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);
}
}

View File

@@ -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);
}
}

View File

@@ -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 {

View File

@@ -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);

View File

@@ -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;

View File

@@ -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);
}
}

View File

@@ -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);
}
}