Add a flag for moving UdfpsEnroll* from SystemUI to settings.
The added files in this CL are mostly copied from SystemUI. Enabling the flag SETTINGS_SHOW_UDFPS_ENROLL_IN_SETTINGS with this CL, the udfps enrollment icons and progress bar are shown in settings. Turn this flag on via adb: adb shell setprop sys.fflag.override.settings_show_udfps_enroll_in_settings true There are some known issues that will be fixed in the following CLs, including: - When the finger is down on the screen and the lighting circle on the sensor is shown, the fingerprint icon is not hidden. - When rotating the screen, fingerprint location is not right. - Currently the scale factor is hard coded for pixel 7 pro, we should update the scale factor based on the device, etc. Test: manually tested on device Bug: 260617060 Change-Id: I5aede070eb1de9eb3b5e1400d6e51a8523079852
This commit is contained in:
39
res/drawable/ic_enrollment_fingerprint.xml
Normal file
39
res/drawable/ic_enrollment_fingerprint.xml
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
<!--
|
||||||
|
Copyright (C) 2020 The Android Open Source Project
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License
|
||||||
|
-->
|
||||||
|
|
||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="72dp"
|
||||||
|
android:height="72dp"
|
||||||
|
android:tint="@*android:color/primary_text_material_dark"
|
||||||
|
android:viewportWidth="72"
|
||||||
|
android:viewportHeight="72">
|
||||||
|
<path
|
||||||
|
android:pathData=
|
||||||
|
"M25.5,16.3283C28.47,14.8433 31.9167,14 35.5834,14C39.2501,14 42.6968,
|
||||||
|
14.8433 45.6668,16.3283
|
||||||
|
M20,28.6669C22.7683,24.3402 28.7084,21.3335 35.5834,21.3335C42.4585,
|
||||||
|
21.3335 48.3985,24.3402 51.1669,28.6669
|
||||||
|
M22.8607,47.0002C21.834,44.3235 21.834,41.5002 21.834,41.5002C21.834,
|
||||||
|
34.4051 27.7374,28.6667 35.5841,28.6667C43.4308,28.6667 49.3341,34.4051 49.3341,41.5002
|
||||||
|
M49.3344,41.5003V42.0319C49.3344,44.7636 47.1161,47.0003 44.3661,47.0003C41.9461,
|
||||||
|
47.0003 39.8744,45.2403 39.471,42.857L38.9577,39.7769C38.591,37.5953 36.7027,
|
||||||
|
36.0002 34.5027,36.0002C26.5826,36.0002 29.846,49.1087 35.291,50.6487
|
||||||
|
M44.9713,54.6267C42.5513,56.7167 39.2879,58.0001 35.5846,58.0001C32.2296,
|
||||||
|
58.0001 29.2229,56.9551 26.8945,55.195"
|
||||||
|
android:strokeWidth="3"
|
||||||
|
android:strokeColor="#ffffff"
|
||||||
|
android:strokeLineCap="round"/>
|
||||||
|
</vector>
|
35
res/drawable/udfps_enroll_checkmark.xml
Normal file
35
res/drawable/udfps_enroll_checkmark.xml
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!--
|
||||||
|
~ Copyright (C) 2021 The Android Open Source Project
|
||||||
|
~
|
||||||
|
~ Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
~ you may not use this file except in compliance with the License.
|
||||||
|
~ You may obtain a copy of the License at
|
||||||
|
~
|
||||||
|
~ http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
~
|
||||||
|
~ Unless required by applicable law or agreed to in writing, software
|
||||||
|
~ distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
~ See the License for the specific language governing permissions and
|
||||||
|
~ limitations under the License.
|
||||||
|
-->
|
||||||
|
|
||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="54dp"
|
||||||
|
android:height="54dp"
|
||||||
|
android:viewportWidth="54"
|
||||||
|
android:viewportHeight="54">
|
||||||
|
<path
|
||||||
|
android:pathData="M26.9999,3.9619C39.7029,3.9619 50.0369,14.2969 50.0369,26.9999C50.0369,39.7029 39.7029,50.0379 26.9999,50.0379C14.2969,50.0379 3.9629,39.7029 3.9629,26.9999C3.9629,14.2969 14.2969,3.9619 26.9999,3.9619Z"
|
||||||
|
android:fillColor="?android:colorBackground"
|
||||||
|
android:fillType="evenOdd"/>
|
||||||
|
<path
|
||||||
|
android:pathData="M27,0C12.088,0 0,12.088 0,27C0,41.912 12.088,54 27,54C41.912,54 54,41.912 54,27C54,12.088 41.912,0 27,0ZM27,3.962C39.703,3.962 50.037,14.297 50.037,27C50.037,39.703 39.703,50.038 27,50.038C14.297,50.038 3.963,39.703 3.963,27C3.963,14.297 14.297,3.962 27,3.962Z"
|
||||||
|
android:fillColor="@color/udfps_enroll_progress"
|
||||||
|
android:fillType="evenOdd"/>
|
||||||
|
<path
|
||||||
|
android:pathData="M23.0899,38.8534L10.4199,26.1824L13.2479,23.3544L23.0899,33.1974L41.2389,15.0474L44.0679,17.8754L23.0899,38.8534Z"
|
||||||
|
android:fillColor="@color/udfps_enroll_progress"
|
||||||
|
android:fillType="evenOdd"/>
|
||||||
|
</vector>
|
@@ -39,6 +39,7 @@
|
|||||||
android:orientation="vertical">
|
android:orientation="vertical">
|
||||||
|
|
||||||
<FrameLayout
|
<FrameLayout
|
||||||
|
android:id="@+id/layout_container"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:clipChildren="false"
|
android:clipChildren="false"
|
||||||
|
42
res/layout/udfps_enroll_view.xml
Normal file
42
res/layout/udfps_enroll_view.xml
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!--
|
||||||
|
~ Copyright (C) 2021 The Android Open Source Project
|
||||||
|
~
|
||||||
|
~ Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
~ you may not use this file except in compliance with the License.
|
||||||
|
~ You may obtain a copy of the License at
|
||||||
|
~
|
||||||
|
~ http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
~
|
||||||
|
~ Unless required by applicable law or agreed to in writing, software
|
||||||
|
~ distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
~ See the License for the specific language governing permissions and
|
||||||
|
~ limitations under the License.
|
||||||
|
-->
|
||||||
|
<com.android.settings.biometrics.fingerprint.UdfpsEnrollView
|
||||||
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:id="@+id/udfps_animation_view"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent">
|
||||||
|
|
||||||
|
<!-- The layout height/width are placeholders, which will be overwritten by
|
||||||
|
FingerprintSensorPropertiesInternal. -->
|
||||||
|
<View
|
||||||
|
android:id="@+id/udfps_enroll_accessibility_view"
|
||||||
|
android:layout_gravity="center"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:contentDescription="@string/accessibility_fingerprint_label"/>
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/udfps_enroll_animation_fp_progress_view"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"/>
|
||||||
|
|
||||||
|
<!-- Fingerprint -->
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/udfps_enroll_animation_fp_view"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"/>
|
||||||
|
</com.android.settings.biometrics.fingerprint.UdfpsEnrollView>
|
@@ -57,5 +57,15 @@
|
|||||||
|
|
||||||
<!-- Icon tint color for battery usage system icon -->
|
<!-- Icon tint color for battery usage system icon -->
|
||||||
<color name="battery_usage_system_icon_color">@android:color/white</color>
|
<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>
|
</resources>
|
||||||
|
|
||||||
|
@@ -184,4 +184,14 @@
|
|||||||
<attr name="ic_menu_moreoverflow" format="reference" />
|
<attr name="ic_menu_moreoverflow" format="reference" />
|
||||||
<attr name="side_margin" format="reference|dimension" />
|
<attr name="side_margin" format="reference|dimension" />
|
||||||
<attr name="wifi_signal_color" format="reference" />
|
<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>
|
</resources>
|
||||||
|
@@ -172,4 +172,13 @@
|
|||||||
|
|
||||||
<!-- Icon tint color for battery usage system icon -->
|
<!-- Icon tint color for battery usage system icon -->
|
||||||
<color name="battery_usage_system_icon_color">?android:attr/textColorPrimary</color>
|
<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>
|
</resources>
|
||||||
|
@@ -636,4 +636,25 @@
|
|||||||
|
|
||||||
<!-- Whether the toggle for Auto-rotate with Face Detection should be shown. -->
|
<!-- Whether the toggle for Auto-rotate with Face Detection should be shown. -->
|
||||||
<bool name="config_auto_rotate_face_detection_available">true</bool>
|
<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>
|
</resources>
|
||||||
|
@@ -11490,4 +11490,7 @@
|
|||||||
=1 {Apps installed more than # month ago}
|
=1 {Apps installed more than # month ago}
|
||||||
other {Apps installed more than # months ago}
|
other {Apps installed more than # months ago}
|
||||||
}</string>
|
}</string>
|
||||||
|
|
||||||
|
<!-- Accessibility label for fingerprint sensor [CHAR LIMIT=NONE] -->
|
||||||
|
<string name="accessibility_fingerprint_label">Fingerprint sensor</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
@@ -905,4 +905,13 @@
|
|||||||
<item name="android:textAllCaps">false</item>
|
<item name="android:textAllCaps">false</item>
|
||||||
<item name="android:singleLine">true</item>
|
<item name="android:singleLine">true</item>
|
||||||
</style>
|
</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>
|
</resources>
|
||||||
|
@@ -40,6 +40,11 @@ public abstract class BiometricEnrollSidecar extends InstrumentedFragment {
|
|||||||
void onEnrollmentHelp(int helpMsgId, CharSequence helpString);
|
void onEnrollmentHelp(int helpMsgId, CharSequence helpString);
|
||||||
void onEnrollmentError(int errMsgId, CharSequence errString);
|
void onEnrollmentError(int errMsgId, CharSequence errString);
|
||||||
void onEnrollmentProgressChange(int steps, int remaining);
|
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;
|
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() {
|
private final Runnable mTimeoutRunnable = new Runnable() {
|
||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
@@ -189,6 +207,14 @@ public abstract class BiometricEnrollSidecar extends InstrumentedFragment {
|
|||||||
mEnrolling = false;
|
mEnrolling = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected void onAcquired(boolean isAcquiredGood) {
|
||||||
|
if (mListener != null) {
|
||||||
|
mListener.onAcquired(isAcquiredGood);
|
||||||
|
} else {
|
||||||
|
mQueuedEvents.add(new QueuedAcquired(isAcquiredGood));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public void setListener(Listener listener) {
|
public void setListener(Listener listener) {
|
||||||
mListener = listener;
|
mListener = listener;
|
||||||
if (mListener != null) {
|
if (mListener != null) {
|
||||||
|
@@ -34,11 +34,13 @@ import android.content.res.Configuration;
|
|||||||
import android.content.res.Resources;
|
import android.content.res.Resources;
|
||||||
import android.graphics.PorterDuff;
|
import android.graphics.PorterDuff;
|
||||||
import android.graphics.PorterDuffColorFilter;
|
import android.graphics.PorterDuffColorFilter;
|
||||||
|
import android.graphics.Rect;
|
||||||
import android.graphics.drawable.Animatable2;
|
import android.graphics.drawable.Animatable2;
|
||||||
import android.graphics.drawable.AnimatedVectorDrawable;
|
import android.graphics.drawable.AnimatedVectorDrawable;
|
||||||
import android.graphics.drawable.Drawable;
|
import android.graphics.drawable.Drawable;
|
||||||
import android.graphics.drawable.LayerDrawable;
|
import android.graphics.drawable.LayerDrawable;
|
||||||
import android.hardware.fingerprint.FingerprintManager;
|
import android.hardware.fingerprint.FingerprintManager;
|
||||||
|
import android.hardware.fingerprint.FingerprintSensorProperties;
|
||||||
import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
|
import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.os.Process;
|
import android.os.Process;
|
||||||
@@ -46,15 +48,21 @@ import android.os.VibrationAttributes;
|
|||||||
import android.os.VibrationEffect;
|
import android.os.VibrationEffect;
|
||||||
import android.os.Vibrator;
|
import android.os.Vibrator;
|
||||||
import android.text.TextUtils;
|
import android.text.TextUtils;
|
||||||
|
import android.util.DisplayUtils;
|
||||||
|
import android.util.FeatureFlagUtils;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
|
import android.view.Display;
|
||||||
|
import android.view.DisplayInfo;
|
||||||
import android.view.MotionEvent;
|
import android.view.MotionEvent;
|
||||||
import android.view.OrientationEventListener;
|
import android.view.OrientationEventListener;
|
||||||
import android.view.Surface;
|
import android.view.Surface;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
|
import android.view.ViewGroup;
|
||||||
import android.view.accessibility.AccessibilityEvent;
|
import android.view.accessibility.AccessibilityEvent;
|
||||||
import android.view.accessibility.AccessibilityManager;
|
import android.view.accessibility.AccessibilityManager;
|
||||||
import android.view.animation.AnimationUtils;
|
import android.view.animation.AnimationUtils;
|
||||||
import android.view.animation.Interpolator;
|
import android.view.animation.Interpolator;
|
||||||
|
import android.widget.FrameLayout;
|
||||||
import android.widget.LinearLayout;
|
import android.widget.LinearLayout;
|
||||||
import android.widget.ProgressBar;
|
import android.widget.ProgressBar;
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
@@ -157,6 +165,9 @@ public class FingerprintEnrollEnrolling extends BiometricsEnrollEnrolling {
|
|||||||
private boolean mCanAssumeUdfps;
|
private boolean mCanAssumeUdfps;
|
||||||
private boolean mCanAssumeSfps;
|
private boolean mCanAssumeSfps;
|
||||||
@Nullable private ProgressBar mProgressBar;
|
@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 ObjectAnimator mProgressAnim;
|
||||||
private TextView mErrorText;
|
private TextView mErrorText;
|
||||||
private Interpolator mFastOutSlowInInterpolator;
|
private Interpolator mFastOutSlowInInterpolator;
|
||||||
@@ -245,7 +256,8 @@ public class FingerprintEnrollEnrolling extends BiometricsEnrollEnrolling {
|
|||||||
listenOrientationEvent();
|
listenOrientationEvent();
|
||||||
|
|
||||||
if (mCanAssumeUdfps) {
|
if (mCanAssumeUdfps) {
|
||||||
switch(getApplicationContext().getDisplay().getRotation()) {
|
int rotation = getApplicationContext().getDisplay().getRotation();
|
||||||
|
switch (rotation) {
|
||||||
case Surface.ROTATION_90:
|
case Surface.ROTATION_90:
|
||||||
final GlifLayout layout = (GlifLayout) getLayoutInflater().inflate(
|
final GlifLayout layout = (GlifLayout) getLayoutInflater().inflate(
|
||||||
R.layout.udfps_enroll_enrolling, null, false);
|
R.layout.udfps_enroll_enrolling, null, false);
|
||||||
@@ -260,8 +272,12 @@ public class FingerprintEnrollEnrolling extends BiometricsEnrollEnrolling {
|
|||||||
layoutContainer.setPaddingRelative((int) getResources().getDimension(
|
layoutContainer.setPaddingRelative((int) getResources().getDimension(
|
||||||
R.dimen.rotation_90_enroll_padding_start), 0, isLayoutRtl
|
R.dimen.rotation_90_enroll_padding_start), 0, isLayoutRtl
|
||||||
? 0 : (int) getResources().getDimension(
|
? 0 : (int) getResources().getDimension(
|
||||||
R.dimen.rotation_90_enroll_padding_end), 0);
|
R.dimen.rotation_90_enroll_padding_end), 0);
|
||||||
layoutContainer.setLayoutParams(lp);
|
layoutContainer.setLayoutParams(lp);
|
||||||
|
if (FeatureFlagUtils.isEnabled(getApplicationContext(),
|
||||||
|
FeatureFlagUtils.SETTINGS_SHOW_UDFPS_ENROLL_IN_SETTINGS)) {
|
||||||
|
layout.addView(addUdfpsEnrollView(props.get(0)));
|
||||||
|
}
|
||||||
setContentView(layout, lp);
|
setContentView(layout, lp);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
@@ -269,7 +285,31 @@ public class FingerprintEnrollEnrolling extends BiometricsEnrollEnrolling {
|
|||||||
case Surface.ROTATION_180:
|
case Surface.ROTATION_180:
|
||||||
case Surface.ROTATION_270:
|
case Surface.ROTATION_270:
|
||||||
default:
|
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;
|
break;
|
||||||
}
|
}
|
||||||
setDescriptionText(R.string.security_settings_udfps_enroll_start_message);
|
setDescriptionText(R.string.security_settings_udfps_enroll_start_message);
|
||||||
@@ -766,6 +806,8 @@ public class FingerprintEnrollEnrolling extends BiometricsEnrollEnrolling {
|
|||||||
mErrorText.removeCallbacks(mTouchAgainRunnable);
|
mErrorText.removeCallbacks(mTouchAgainRunnable);
|
||||||
}
|
}
|
||||||
showError(helpString);
|
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) {
|
private void updateProgress(boolean animate) {
|
||||||
if (mSidecar == null || !mSidecar.isEnrolling()) {
|
if (mSidecar == null || !mSidecar.isEnrolling()) {
|
||||||
Log.d(TAG, "Enrollment not started yet");
|
Log.d(TAG, "Enrollment not started yet");
|
||||||
@@ -826,6 +875,12 @@ public class FingerprintEnrollEnrolling extends BiometricsEnrollEnrolling {
|
|||||||
if (mProgressBar != null && mProgressBar.getProgress() < progress) {
|
if (mProgressBar != null && mProgressBar.getProgress() < progress) {
|
||||||
clearError();
|
clearError();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (mUdfpsEnrollHelper != null) {
|
||||||
|
mUdfpsEnrollHelper.onEnrollmentProgress(mSidecar.getEnrollmentSteps(),
|
||||||
|
mSidecar.getEnrollmentRemaining());
|
||||||
|
}
|
||||||
|
|
||||||
if (animate) {
|
if (animate) {
|
||||||
animateProgress(progress);
|
animateProgress(progress);
|
||||||
} else {
|
} 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 {
|
public static class IconTouchDialog extends InstrumentedDialogFragment {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@@ -108,6 +108,11 @@ public class FingerprintEnrollSidecar extends BiometricEnrollSidecar {
|
|||||||
FingerprintEnrollSidecar.super.onEnrollmentProgress(remaining);
|
FingerprintEnrollSidecar.super.onEnrollmentProgress(remaining);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onAcquired(boolean isAcquiredGood) {
|
||||||
|
FingerprintEnrollSidecar.super.onAcquired(isAcquiredGood);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onEnrollmentHelp(int helpMsgId, CharSequence helpString) {
|
public void onEnrollmentHelp(int helpMsgId, CharSequence helpString) {
|
||||||
FingerprintEnrollSidecar.super.onEnrollmentHelp(helpMsgId, helpString);
|
FingerprintEnrollSidecar.super.onEnrollmentHelp(helpMsgId, helpString);
|
||||||
|
@@ -91,6 +91,11 @@ public class FingerprintUpdater {
|
|||||||
BiometricsSafetySource.onBiometricsChanged(mContext); // biometrics data changed
|
BiometricsSafetySource.onBiometricsChanged(mContext); // biometrics data changed
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onAcquired(boolean isAcquiredGood) {
|
||||||
|
mCallback.onAcquired(isAcquiredGood);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@@ -0,0 +1,309 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2022 The Android Open Source Project
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.android.settings.biometrics.fingerprint;
|
||||||
|
|
||||||
|
|
||||||
|
import android.animation.Animator;
|
||||||
|
import android.animation.AnimatorSet;
|
||||||
|
import android.animation.ValueAnimator;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.res.TypedArray;
|
||||||
|
import android.graphics.Canvas;
|
||||||
|
import android.graphics.ColorFilter;
|
||||||
|
import android.graphics.Paint;
|
||||||
|
import android.graphics.PointF;
|
||||||
|
import android.graphics.Rect;
|
||||||
|
import android.graphics.RectF;
|
||||||
|
import android.graphics.drawable.Drawable;
|
||||||
|
import android.graphics.drawable.ShapeDrawable;
|
||||||
|
import android.graphics.drawable.shapes.PathShape;
|
||||||
|
import android.util.AttributeSet;
|
||||||
|
import android.util.PathParser;
|
||||||
|
import android.view.animation.AccelerateDecelerateInterpolator;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
|
||||||
|
import com.android.settings.R;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* UDFPS fingerprint drawable that is shown when enrolling
|
||||||
|
*/
|
||||||
|
public class UdfpsEnrollDrawable extends Drawable {
|
||||||
|
private static final String TAG = "UdfpsAnimationEnroll";
|
||||||
|
|
||||||
|
private static final long TARGET_ANIM_DURATION_LONG = 800L;
|
||||||
|
private static final long TARGET_ANIM_DURATION_SHORT = 600L;
|
||||||
|
// 1 + SCALE_MAX is the maximum that the moving target will animate to
|
||||||
|
private static final float SCALE_MAX = 0.25f;
|
||||||
|
private static final float DEFAULT_STROKE_WIDTH = 3f;
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
private final Drawable mMovingTargetFpIcon;
|
||||||
|
@NonNull
|
||||||
|
private final Paint mSensorOutlinePaint;
|
||||||
|
@NonNull
|
||||||
|
private final Paint mBlueFill;
|
||||||
|
@NonNull
|
||||||
|
private final ShapeDrawable mFingerprintDrawable;
|
||||||
|
|
||||||
|
private int mAlpha;
|
||||||
|
private boolean mSkipDraw = false;
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
private RectF mSensorRect;
|
||||||
|
@Nullable
|
||||||
|
private UdfpsEnrollHelper mEnrollHelper;
|
||||||
|
|
||||||
|
// Moving target animator set
|
||||||
|
@Nullable
|
||||||
|
AnimatorSet mTargetAnimatorSet;
|
||||||
|
// Moving target location
|
||||||
|
float mCurrentX;
|
||||||
|
float mCurrentY;
|
||||||
|
// Moving target size
|
||||||
|
float mCurrentScale = 1.f;
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
private final Animator.AnimatorListener mTargetAnimListener;
|
||||||
|
|
||||||
|
private boolean mShouldShowTipHint = false;
|
||||||
|
private boolean mShouldShowEdgeHint = false;
|
||||||
|
|
||||||
|
private int mEnrollIcon;
|
||||||
|
private int mMovingTargetFill;
|
||||||
|
|
||||||
|
UdfpsEnrollDrawable(@NonNull Context context, @Nullable AttributeSet attrs) {
|
||||||
|
mFingerprintDrawable = defaultFactory(context);
|
||||||
|
|
||||||
|
loadResources(context, attrs);
|
||||||
|
mSensorOutlinePaint = new Paint(0 /* flags */);
|
||||||
|
mSensorOutlinePaint.setAntiAlias(true);
|
||||||
|
mSensorOutlinePaint.setColor(mMovingTargetFill);
|
||||||
|
mSensorOutlinePaint.setStyle(Paint.Style.FILL);
|
||||||
|
|
||||||
|
mBlueFill = new Paint(0 /* flags */);
|
||||||
|
mBlueFill.setAntiAlias(true);
|
||||||
|
mBlueFill.setColor(mMovingTargetFill);
|
||||||
|
mBlueFill.setStyle(Paint.Style.FILL);
|
||||||
|
|
||||||
|
mMovingTargetFpIcon = context.getResources()
|
||||||
|
.getDrawable(R.drawable.ic_enrollment_fingerprint, null);
|
||||||
|
mMovingTargetFpIcon.setTint(mEnrollIcon);
|
||||||
|
mMovingTargetFpIcon.mutate();
|
||||||
|
|
||||||
|
mFingerprintDrawable.setTint(mEnrollIcon);
|
||||||
|
|
||||||
|
setAlpha(255);
|
||||||
|
mTargetAnimListener = new Animator.AnimatorListener() {
|
||||||
|
@Override
|
||||||
|
public void onAnimationStart(Animator animation) {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onAnimationEnd(Animator animation) {
|
||||||
|
updateTipHintVisibility();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onAnimationCancel(Animator animation) {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onAnimationRepeat(Animator animation) {
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/** The [sensorRect] coordinates for the sensor area. */
|
||||||
|
void onSensorRectUpdated(@NonNull RectF sensorRect) {
|
||||||
|
int margin = ((int) sensorRect.height()) / 8;
|
||||||
|
Rect bounds = new Rect((int) (sensorRect.left) + margin, (int) (sensorRect.top) + margin,
|
||||||
|
(int) (sensorRect.right) - margin, (int) (sensorRect.bottom) - margin);
|
||||||
|
updateFingerprintIconBounds(bounds);
|
||||||
|
mSensorRect = sensorRect;
|
||||||
|
}
|
||||||
|
|
||||||
|
void setEnrollHelper(@NonNull UdfpsEnrollHelper helper) {
|
||||||
|
mEnrollHelper = helper;
|
||||||
|
}
|
||||||
|
|
||||||
|
void setShouldSkipDraw(boolean skipDraw) {
|
||||||
|
if (mSkipDraw == skipDraw) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
mSkipDraw = skipDraw;
|
||||||
|
invalidateSelf();
|
||||||
|
}
|
||||||
|
|
||||||
|
void updateFingerprintIconBounds(@NonNull Rect bounds) {
|
||||||
|
mFingerprintDrawable.setBounds(bounds);
|
||||||
|
invalidateSelf();
|
||||||
|
mMovingTargetFpIcon.setBounds(bounds);
|
||||||
|
invalidateSelf();
|
||||||
|
}
|
||||||
|
|
||||||
|
void onEnrollmentProgress(int remaining, int totalSteps) {
|
||||||
|
if (mEnrollHelper == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!mEnrollHelper.isCenterEnrollmentStage()) {
|
||||||
|
if (mTargetAnimatorSet != null && mTargetAnimatorSet.isRunning()) {
|
||||||
|
mTargetAnimatorSet.end();
|
||||||
|
}
|
||||||
|
|
||||||
|
final PointF point = mEnrollHelper.getNextGuidedEnrollmentPoint();
|
||||||
|
if (mCurrentX != point.x || mCurrentY != point.y) {
|
||||||
|
final ValueAnimator x = ValueAnimator.ofFloat(mCurrentX, point.x);
|
||||||
|
x.addUpdateListener(animation -> {
|
||||||
|
mCurrentX = (float) animation.getAnimatedValue();
|
||||||
|
invalidateSelf();
|
||||||
|
});
|
||||||
|
|
||||||
|
final ValueAnimator y = ValueAnimator.ofFloat(mCurrentY, point.y);
|
||||||
|
y.addUpdateListener(animation -> {
|
||||||
|
mCurrentY = (float) animation.getAnimatedValue();
|
||||||
|
invalidateSelf();
|
||||||
|
});
|
||||||
|
|
||||||
|
final boolean isMovingToCenter = point.x == 0f && point.y == 0f;
|
||||||
|
final long duration = isMovingToCenter
|
||||||
|
? TARGET_ANIM_DURATION_SHORT
|
||||||
|
: TARGET_ANIM_DURATION_LONG;
|
||||||
|
|
||||||
|
final ValueAnimator scale = ValueAnimator.ofFloat(0, (float) Math.PI);
|
||||||
|
scale.setDuration(duration);
|
||||||
|
scale.addUpdateListener(animation -> {
|
||||||
|
// Grow then shrink
|
||||||
|
mCurrentScale = 1
|
||||||
|
+ SCALE_MAX * (float) Math.sin((float) animation.getAnimatedValue());
|
||||||
|
invalidateSelf();
|
||||||
|
});
|
||||||
|
|
||||||
|
mTargetAnimatorSet = new AnimatorSet();
|
||||||
|
|
||||||
|
mTargetAnimatorSet.setInterpolator(new AccelerateDecelerateInterpolator());
|
||||||
|
mTargetAnimatorSet.setDuration(duration);
|
||||||
|
mTargetAnimatorSet.addListener(mTargetAnimListener);
|
||||||
|
mTargetAnimatorSet.playTogether(x, y, scale);
|
||||||
|
mTargetAnimatorSet.start();
|
||||||
|
} else {
|
||||||
|
updateTipHintVisibility();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
updateTipHintVisibility();
|
||||||
|
}
|
||||||
|
|
||||||
|
updateEdgeHintVisibility();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void draw(@NonNull Canvas canvas) {
|
||||||
|
if (mSkipDraw) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Draw moving target
|
||||||
|
if (mEnrollHelper != null && !mEnrollHelper.isCenterEnrollmentStage()) {
|
||||||
|
canvas.save();
|
||||||
|
canvas.translate(mCurrentX, mCurrentY);
|
||||||
|
|
||||||
|
if (mSensorRect != null) {
|
||||||
|
canvas.scale(mCurrentScale, mCurrentScale,
|
||||||
|
mSensorRect.centerX(), mSensorRect.centerY());
|
||||||
|
canvas.drawOval(mSensorRect, mBlueFill);
|
||||||
|
}
|
||||||
|
|
||||||
|
mMovingTargetFpIcon.draw(canvas);
|
||||||
|
canvas.restore();
|
||||||
|
} else {
|
||||||
|
if (mSensorRect != null) {
|
||||||
|
canvas.drawOval(mSensorRect, mSensorOutlinePaint);
|
||||||
|
}
|
||||||
|
mFingerprintDrawable.draw(canvas);
|
||||||
|
mFingerprintDrawable.setAlpha(getAlpha());
|
||||||
|
mSensorOutlinePaint.setAlpha(getAlpha());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setAlpha(int alpha) {
|
||||||
|
mAlpha = alpha;
|
||||||
|
mFingerprintDrawable.setAlpha(alpha);
|
||||||
|
mSensorOutlinePaint.setAlpha(alpha);
|
||||||
|
mBlueFill.setAlpha(alpha);
|
||||||
|
mMovingTargetFpIcon.setAlpha(alpha);
|
||||||
|
invalidateSelf();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getAlpha() {
|
||||||
|
return mAlpha;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setColorFilter(@Nullable ColorFilter colorFilter) {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getOpacity() {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateTipHintVisibility() {
|
||||||
|
final boolean shouldShow = mEnrollHelper != null && mEnrollHelper.isTipEnrollmentStage();
|
||||||
|
// With the new update, we will git rid of most of this code, and instead
|
||||||
|
// we will change the fingerprint icon.
|
||||||
|
if (mShouldShowTipHint == shouldShow) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
mShouldShowTipHint = shouldShow;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateEdgeHintVisibility() {
|
||||||
|
final boolean shouldShow = mEnrollHelper != null && mEnrollHelper.isEdgeEnrollmentStage();
|
||||||
|
if (mShouldShowEdgeHint == shouldShow) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
mShouldShowEdgeHint = shouldShow;
|
||||||
|
}
|
||||||
|
|
||||||
|
private ShapeDrawable defaultFactory(Context context) {
|
||||||
|
String fpPath = context.getResources().getString(R.string.config_udfpsIcon);
|
||||||
|
ShapeDrawable drawable = new ShapeDrawable(
|
||||||
|
new PathShape(PathParser.createPathFromPathData(fpPath), 72f, 72f)
|
||||||
|
);
|
||||||
|
drawable.mutate();
|
||||||
|
drawable.getPaint().setStyle(Paint.Style.STROKE);
|
||||||
|
drawable.getPaint().setStrokeCap(Paint.Cap.ROUND);
|
||||||
|
drawable.getPaint().setStrokeWidth(DEFAULT_STROKE_WIDTH);
|
||||||
|
return drawable;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void loadResources(Context context, @Nullable AttributeSet attrs) {
|
||||||
|
final TypedArray ta = context.obtainStyledAttributes(attrs,
|
||||||
|
R.styleable.BiometricsEnrollView, R.attr.biometricsEnrollStyle,
|
||||||
|
R.style.BiometricsEnrollStyle);
|
||||||
|
mEnrollIcon = ta.getColor(R.styleable.BiometricsEnrollView_biometricsEnrollIcon, 0);
|
||||||
|
mMovingTargetFill = ta.getColor(
|
||||||
|
R.styleable.BiometricsEnrollView_biometricsMovingTargetFill, 0);
|
||||||
|
ta.recycle();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@@ -0,0 +1,231 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2022 The Android Open Source Project
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.android.settings.biometrics.fingerprint;
|
||||||
|
|
||||||
|
import android.annotation.NonNull;
|
||||||
|
import android.annotation.Nullable;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.graphics.PointF;
|
||||||
|
import android.hardware.fingerprint.FingerprintManager;
|
||||||
|
import android.os.Build;
|
||||||
|
import android.os.UserHandle;
|
||||||
|
import android.provider.Settings;
|
||||||
|
import android.util.Log;
|
||||||
|
import android.util.TypedValue;
|
||||||
|
import android.view.accessibility.AccessibilityManager;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helps keep track of enrollment state and animates the progress bar accordingly.
|
||||||
|
*/
|
||||||
|
public class UdfpsEnrollHelper {
|
||||||
|
private static final String TAG = "UdfpsEnrollHelper";
|
||||||
|
|
||||||
|
private static final String SCALE_OVERRIDE =
|
||||||
|
"com.android.systemui.biometrics.UdfpsEnrollHelper.scale";
|
||||||
|
private static final float SCALE = 0.5f;
|
||||||
|
|
||||||
|
private static final String NEW_COORDS_OVERRIDE =
|
||||||
|
"com.android.systemui.biometrics.UdfpsNewCoords";
|
||||||
|
|
||||||
|
interface Listener {
|
||||||
|
void onEnrollmentProgress(int remaining, int totalSteps);
|
||||||
|
|
||||||
|
void onEnrollmentHelp(int remaining, int totalSteps);
|
||||||
|
|
||||||
|
void onAcquired(boolean animateIfLastStepGood);
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
private final Context mContext;
|
||||||
|
@NonNull
|
||||||
|
private final FingerprintManager mFingerprintManager;
|
||||||
|
private final boolean mAccessibilityEnabled;
|
||||||
|
@NonNull
|
||||||
|
private final List<PointF> mGuidedEnrollmentPoints;
|
||||||
|
|
||||||
|
private int mTotalSteps = -1;
|
||||||
|
private int mRemainingSteps = -1;
|
||||||
|
|
||||||
|
// Note that this is actually not equal to "mTotalSteps - mRemainingSteps", because the
|
||||||
|
// interface makes no promises about monotonically increasing by one each time.
|
||||||
|
private int mLocationsEnrolled = 0;
|
||||||
|
|
||||||
|
private int mCenterTouchCount = 0;
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
UdfpsEnrollHelper.Listener mListener;
|
||||||
|
|
||||||
|
public UdfpsEnrollHelper(@NonNull Context context,
|
||||||
|
@NonNull FingerprintManager fingerprintManager) {
|
||||||
|
|
||||||
|
mContext = context;
|
||||||
|
mFingerprintManager = fingerprintManager;
|
||||||
|
|
||||||
|
final AccessibilityManager am = context.getSystemService(AccessibilityManager.class);
|
||||||
|
mAccessibilityEnabled = am.isEnabled();
|
||||||
|
|
||||||
|
mGuidedEnrollmentPoints = new ArrayList<>();
|
||||||
|
|
||||||
|
// Number of pixels per mm
|
||||||
|
float px = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_MM, 1,
|
||||||
|
context.getResources().getDisplayMetrics());
|
||||||
|
boolean useNewCoords = Settings.Secure.getIntForUser(mContext.getContentResolver(),
|
||||||
|
NEW_COORDS_OVERRIDE, 0,
|
||||||
|
UserHandle.USER_CURRENT) != 0;
|
||||||
|
if (useNewCoords && (Build.IS_ENG || Build.IS_USERDEBUG)) {
|
||||||
|
Log.v(TAG, "Using new coordinates");
|
||||||
|
mGuidedEnrollmentPoints.add(new PointF(-0.15f * px, -1.02f * px));
|
||||||
|
mGuidedEnrollmentPoints.add(new PointF(-0.15f * px, 1.02f * px));
|
||||||
|
mGuidedEnrollmentPoints.add(new PointF(0.29f * px, 0.00f * px));
|
||||||
|
mGuidedEnrollmentPoints.add(new PointF(2.17f * px, -2.35f * px));
|
||||||
|
mGuidedEnrollmentPoints.add(new PointF(1.07f * px, -3.96f * px));
|
||||||
|
mGuidedEnrollmentPoints.add(new PointF(-0.37f * px, -4.31f * px));
|
||||||
|
mGuidedEnrollmentPoints.add(new PointF(-1.69f * px, -3.29f * px));
|
||||||
|
mGuidedEnrollmentPoints.add(new PointF(-2.48f * px, -1.23f * px));
|
||||||
|
mGuidedEnrollmentPoints.add(new PointF(-2.48f * px, 1.23f * px));
|
||||||
|
mGuidedEnrollmentPoints.add(new PointF(-1.69f * px, 3.29f * px));
|
||||||
|
mGuidedEnrollmentPoints.add(new PointF(-0.37f * px, 4.31f * px));
|
||||||
|
mGuidedEnrollmentPoints.add(new PointF(1.07f * px, 3.96f * px));
|
||||||
|
mGuidedEnrollmentPoints.add(new PointF(2.17f * px, 2.35f * px));
|
||||||
|
mGuidedEnrollmentPoints.add(new PointF(2.58f * px, 0.00f * px));
|
||||||
|
} else {
|
||||||
|
Log.v(TAG, "Using old coordinates");
|
||||||
|
mGuidedEnrollmentPoints.add(new PointF(2.00f * px, 0.00f * px));
|
||||||
|
mGuidedEnrollmentPoints.add(new PointF(0.87f * px, -2.70f * px));
|
||||||
|
mGuidedEnrollmentPoints.add(new PointF(-1.80f * px, -1.31f * px));
|
||||||
|
mGuidedEnrollmentPoints.add(new PointF(-1.80f * px, 1.31f * px));
|
||||||
|
mGuidedEnrollmentPoints.add(new PointF(0.88f * px, 2.70f * px));
|
||||||
|
mGuidedEnrollmentPoints.add(new PointF(3.94f * px, -1.06f * px));
|
||||||
|
mGuidedEnrollmentPoints.add(new PointF(2.90f * px, -4.14f * px));
|
||||||
|
mGuidedEnrollmentPoints.add(new PointF(-0.52f * px, -5.95f * px));
|
||||||
|
mGuidedEnrollmentPoints.add(new PointF(-3.33f * px, -3.33f * px));
|
||||||
|
mGuidedEnrollmentPoints.add(new PointF(-3.99f * px, -0.35f * px));
|
||||||
|
mGuidedEnrollmentPoints.add(new PointF(-3.62f * px, 2.54f * px));
|
||||||
|
mGuidedEnrollmentPoints.add(new PointF(-1.49f * px, 5.57f * px));
|
||||||
|
mGuidedEnrollmentPoints.add(new PointF(2.29f * px, 4.92f * px));
|
||||||
|
mGuidedEnrollmentPoints.add(new PointF(3.82f * px, 1.78f * px));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void onEnrollmentProgress(int totalSteps, int remaining) {
|
||||||
|
if (mTotalSteps == -1) {
|
||||||
|
mTotalSteps = totalSteps;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (remaining != mRemainingSteps) {
|
||||||
|
mLocationsEnrolled++;
|
||||||
|
if (isCenterEnrollmentStage()) {
|
||||||
|
mCenterTouchCount++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mRemainingSteps = remaining;
|
||||||
|
|
||||||
|
if (mListener != null && mTotalSteps != -1) {
|
||||||
|
mListener.onEnrollmentProgress(remaining, mTotalSteps);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void onEnrollmentHelp() {
|
||||||
|
if (mListener != null && mTotalSteps != -1) {
|
||||||
|
mListener.onEnrollmentHelp(mRemainingSteps, mTotalSteps);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void onAcquired(boolean isAcquiredGood) {
|
||||||
|
if (mListener != null && mTotalSteps != -1) {
|
||||||
|
mListener.onAcquired(isAcquiredGood && animateIfLastStep());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void setListener(UdfpsEnrollHelper.Listener listener) {
|
||||||
|
mListener = listener;
|
||||||
|
|
||||||
|
// Only notify during setListener if enrollment is already in progress, so the progress
|
||||||
|
// bar can be updated. If enrollment has not started yet, the progress bar will be empty
|
||||||
|
// anyway.
|
||||||
|
if (mListener != null && mTotalSteps != -1) {
|
||||||
|
mListener.onEnrollmentProgress(mRemainingSteps, mTotalSteps);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean isCenterEnrollmentStage() {
|
||||||
|
if (mTotalSteps == -1 || mRemainingSteps == -1) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return mTotalSteps - mRemainingSteps < getStageThresholdSteps(mTotalSteps, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean isTipEnrollmentStage() {
|
||||||
|
if (mTotalSteps == -1 || mRemainingSteps == -1) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
final int progressSteps = mTotalSteps - mRemainingSteps;
|
||||||
|
return progressSteps >= getStageThresholdSteps(mTotalSteps, 1)
|
||||||
|
&& progressSteps < getStageThresholdSteps(mTotalSteps, 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean isEdgeEnrollmentStage() {
|
||||||
|
if (mTotalSteps == -1 || mRemainingSteps == -1) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return mTotalSteps - mRemainingSteps >= getStageThresholdSteps(mTotalSteps, 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
PointF getNextGuidedEnrollmentPoint() {
|
||||||
|
if (mAccessibilityEnabled || !isGuidedEnrollmentStage()) {
|
||||||
|
return new PointF(0f, 0f);
|
||||||
|
}
|
||||||
|
|
||||||
|
float scale = SCALE;
|
||||||
|
if (Build.IS_ENG || Build.IS_USERDEBUG) {
|
||||||
|
scale = Settings.Secure.getFloatForUser(mContext.getContentResolver(),
|
||||||
|
SCALE_OVERRIDE, SCALE,
|
||||||
|
UserHandle.USER_CURRENT);
|
||||||
|
}
|
||||||
|
final int index = mLocationsEnrolled - mCenterTouchCount;
|
||||||
|
final PointF originalPoint = mGuidedEnrollmentPoints
|
||||||
|
.get(index % mGuidedEnrollmentPoints.size());
|
||||||
|
return new PointF(originalPoint.x * scale, originalPoint.y * scale);
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean animateIfLastStep() {
|
||||||
|
if (mListener == null) {
|
||||||
|
Log.e(TAG, "animateIfLastStep, null listener");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return mRemainingSteps <= 2 && mRemainingSteps >= 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
private int getStageThresholdSteps(int totalSteps, int stageIndex) {
|
||||||
|
return Math.round(totalSteps * mFingerprintManager.getEnrollStageThreshold(stageIndex));
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isGuidedEnrollmentStage() {
|
||||||
|
if (mAccessibilityEnabled || mTotalSteps == -1 || mRemainingSteps == -1) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
final int progressSteps = mTotalSteps - mRemainingSteps;
|
||||||
|
return progressSteps >= getStageThresholdSteps(mTotalSteps, 0)
|
||||||
|
&& progressSteps < getStageThresholdSteps(mTotalSteps, 1);
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,419 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2021 The Android Open Source Project
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.android.settings.biometrics.fingerprint;
|
||||||
|
|
||||||
|
import android.animation.ValueAnimator;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.res.TypedArray;
|
||||||
|
import android.graphics.Canvas;
|
||||||
|
import android.graphics.ColorFilter;
|
||||||
|
import android.graphics.Paint;
|
||||||
|
import android.graphics.drawable.Drawable;
|
||||||
|
import android.os.Process;
|
||||||
|
import android.os.VibrationAttributes;
|
||||||
|
import android.os.VibrationEffect;
|
||||||
|
import android.os.Vibrator;
|
||||||
|
import android.util.AttributeSet;
|
||||||
|
import android.util.DisplayMetrics;
|
||||||
|
import android.view.accessibility.AccessibilityManager;
|
||||||
|
import android.view.animation.DecelerateInterpolator;
|
||||||
|
import android.view.animation.Interpolator;
|
||||||
|
import android.view.animation.OvershootInterpolator;
|
||||||
|
|
||||||
|
import androidx.annotation.ColorInt;
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
|
||||||
|
import com.android.settings.R;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* UDFPS enrollment progress bar.
|
||||||
|
*/
|
||||||
|
public class UdfpsEnrollProgressBarDrawable extends Drawable {
|
||||||
|
private static final String TAG = "UdfpsProgressBar";
|
||||||
|
|
||||||
|
private static final long CHECKMARK_ANIMATION_DELAY_MS = 200L;
|
||||||
|
private static final long CHECKMARK_ANIMATION_DURATION_MS = 300L;
|
||||||
|
private static final long FILL_COLOR_ANIMATION_DURATION_MS = 350L;
|
||||||
|
private static final long PROGRESS_ANIMATION_DURATION_MS = 400L;
|
||||||
|
private static final float STROKE_WIDTH_DP = 12f;
|
||||||
|
private static final Interpolator DEACCEL = new DecelerateInterpolator();
|
||||||
|
|
||||||
|
private static final VibrationEffect VIBRATE_EFFECT_ERROR =
|
||||||
|
VibrationEffect.createWaveform(new long[]{0, 5, 55, 60}, -1);
|
||||||
|
private static final VibrationAttributes FINGERPRINT_ENROLLING_SONFICATION_ATTRIBUTES =
|
||||||
|
VibrationAttributes.createForUsage(VibrationAttributes.USAGE_ACCESSIBILITY);
|
||||||
|
|
||||||
|
private static final VibrationAttributes HARDWARE_FEEDBACK_VIBRATION_ATTRIBUTES =
|
||||||
|
VibrationAttributes.createForUsage(VibrationAttributes.USAGE_HARDWARE_FEEDBACK);
|
||||||
|
|
||||||
|
private static final VibrationEffect SUCCESS_VIBRATION_EFFECT =
|
||||||
|
VibrationEffect.get(VibrationEffect.EFFECT_CLICK);
|
||||||
|
|
||||||
|
private final float mStrokeWidthPx;
|
||||||
|
@ColorInt
|
||||||
|
private final int mProgressColor;
|
||||||
|
@ColorInt
|
||||||
|
private final int mHelpColor;
|
||||||
|
@ColorInt
|
||||||
|
private final int mOnFirstBucketFailedColor;
|
||||||
|
@NonNull
|
||||||
|
private final Drawable mCheckmarkDrawable;
|
||||||
|
@NonNull
|
||||||
|
private final Interpolator mCheckmarkInterpolator;
|
||||||
|
@NonNull
|
||||||
|
private final Paint mBackgroundPaint;
|
||||||
|
@NonNull
|
||||||
|
private final Paint mFillPaint;
|
||||||
|
@NonNull
|
||||||
|
private final Vibrator mVibrator;
|
||||||
|
@NonNull
|
||||||
|
private final boolean mIsAccessibilityEnabled;
|
||||||
|
@NonNull
|
||||||
|
private final Context mContext;
|
||||||
|
|
||||||
|
private boolean mAfterFirstTouch;
|
||||||
|
|
||||||
|
private int mRemainingSteps = 0;
|
||||||
|
private int mTotalSteps = 0;
|
||||||
|
private float mProgress = 0f;
|
||||||
|
@Nullable
|
||||||
|
private ValueAnimator mProgressAnimator;
|
||||||
|
@NonNull
|
||||||
|
private final ValueAnimator.AnimatorUpdateListener mProgressUpdateListener;
|
||||||
|
|
||||||
|
private boolean mShowingHelp = false;
|
||||||
|
@Nullable
|
||||||
|
private ValueAnimator mFillColorAnimator;
|
||||||
|
@NonNull
|
||||||
|
private final ValueAnimator.AnimatorUpdateListener mFillColorUpdateListener;
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
private ValueAnimator mBackgroundColorAnimator;
|
||||||
|
@NonNull
|
||||||
|
private final ValueAnimator.AnimatorUpdateListener mBackgroundColorUpdateListener;
|
||||||
|
|
||||||
|
private boolean mComplete = false;
|
||||||
|
private float mCheckmarkScale = 0f;
|
||||||
|
@Nullable
|
||||||
|
private ValueAnimator mCheckmarkAnimator;
|
||||||
|
@NonNull
|
||||||
|
private final ValueAnimator.AnimatorUpdateListener mCheckmarkUpdateListener;
|
||||||
|
|
||||||
|
private int mMovingTargetFill;
|
||||||
|
private int mMovingTargetFillError;
|
||||||
|
private int mEnrollProgress;
|
||||||
|
private int mEnrollProgressHelp;
|
||||||
|
private int mEnrollProgressHelpWithTalkback;
|
||||||
|
|
||||||
|
public UdfpsEnrollProgressBarDrawable(@NonNull Context context, @Nullable AttributeSet attrs) {
|
||||||
|
mContext = context;
|
||||||
|
|
||||||
|
loadResources(context, attrs);
|
||||||
|
float density = context.getResources().getDisplayMetrics().densityDpi;
|
||||||
|
mStrokeWidthPx = STROKE_WIDTH_DP * (density / DisplayMetrics.DENSITY_DEFAULT);
|
||||||
|
mProgressColor = mEnrollProgress;
|
||||||
|
final AccessibilityManager am = context.getSystemService(AccessibilityManager.class);
|
||||||
|
mIsAccessibilityEnabled = am.isTouchExplorationEnabled();
|
||||||
|
mOnFirstBucketFailedColor = mMovingTargetFillError;
|
||||||
|
if (!mIsAccessibilityEnabled) {
|
||||||
|
mHelpColor = mEnrollProgressHelp;
|
||||||
|
} else {
|
||||||
|
mHelpColor = mEnrollProgressHelpWithTalkback;
|
||||||
|
}
|
||||||
|
mCheckmarkDrawable = context.getDrawable(R.drawable.udfps_enroll_checkmark);
|
||||||
|
mCheckmarkDrawable.mutate();
|
||||||
|
mCheckmarkInterpolator = new OvershootInterpolator();
|
||||||
|
|
||||||
|
mBackgroundPaint = new Paint();
|
||||||
|
mBackgroundPaint.setStrokeWidth(mStrokeWidthPx);
|
||||||
|
mBackgroundPaint.setColor(mMovingTargetFill);
|
||||||
|
mBackgroundPaint.setAntiAlias(true);
|
||||||
|
mBackgroundPaint.setStyle(Paint.Style.STROKE);
|
||||||
|
mBackgroundPaint.setStrokeCap(Paint.Cap.ROUND);
|
||||||
|
|
||||||
|
// Progress fill should *not* use the extracted system color.
|
||||||
|
mFillPaint = new Paint();
|
||||||
|
mFillPaint.setStrokeWidth(mStrokeWidthPx);
|
||||||
|
mFillPaint.setColor(mProgressColor);
|
||||||
|
mFillPaint.setAntiAlias(true);
|
||||||
|
mFillPaint.setStyle(Paint.Style.STROKE);
|
||||||
|
mFillPaint.setStrokeCap(Paint.Cap.ROUND);
|
||||||
|
|
||||||
|
mVibrator = mContext.getSystemService(Vibrator.class);
|
||||||
|
|
||||||
|
mProgressUpdateListener = animation -> {
|
||||||
|
mProgress = (float) animation.getAnimatedValue();
|
||||||
|
invalidateSelf();
|
||||||
|
};
|
||||||
|
|
||||||
|
mFillColorUpdateListener = animation -> {
|
||||||
|
mFillPaint.setColor((int) animation.getAnimatedValue());
|
||||||
|
invalidateSelf();
|
||||||
|
};
|
||||||
|
|
||||||
|
mCheckmarkUpdateListener = animation -> {
|
||||||
|
mCheckmarkScale = (float) animation.getAnimatedValue();
|
||||||
|
invalidateSelf();
|
||||||
|
};
|
||||||
|
|
||||||
|
mBackgroundColorUpdateListener = animation -> {
|
||||||
|
mBackgroundPaint.setColor((int) animation.getAnimatedValue());
|
||||||
|
invalidateSelf();
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
void onEnrollmentProgress(int remaining, int totalSteps) {
|
||||||
|
mAfterFirstTouch = true;
|
||||||
|
updateState(remaining, totalSteps, false /* showingHelp */);
|
||||||
|
}
|
||||||
|
|
||||||
|
void onEnrollmentHelp(int remaining, int totalSteps) {
|
||||||
|
updateState(remaining, totalSteps, true /* showingHelp */);
|
||||||
|
}
|
||||||
|
|
||||||
|
void onLastStepAcquired() {
|
||||||
|
updateState(0, mTotalSteps, false /* showingHelp */);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateState(int remainingSteps, int totalSteps, boolean showingHelp) {
|
||||||
|
updateProgress(remainingSteps, totalSteps, showingHelp);
|
||||||
|
updateFillColor(showingHelp);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateProgress(int remainingSteps, int totalSteps, boolean showingHelp) {
|
||||||
|
if (mRemainingSteps == remainingSteps && mTotalSteps == totalSteps) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mShowingHelp) {
|
||||||
|
if (mVibrator != null && mIsAccessibilityEnabled) {
|
||||||
|
mVibrator.vibrate(Process.myUid(), mContext.getOpPackageName(),
|
||||||
|
VIBRATE_EFFECT_ERROR, getClass().getSimpleName() + "::onEnrollmentHelp",
|
||||||
|
FINGERPRINT_ENROLLING_SONFICATION_ATTRIBUTES);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// If the first touch is an error, remainingSteps will be -1 and the callback
|
||||||
|
// doesn't come from onEnrollmentHelp. If we are in the accessibility flow,
|
||||||
|
// we still would like to vibrate.
|
||||||
|
if (mVibrator != null) {
|
||||||
|
if (remainingSteps == -1 && mIsAccessibilityEnabled) {
|
||||||
|
mVibrator.vibrate(Process.myUid(), mContext.getOpPackageName(),
|
||||||
|
VIBRATE_EFFECT_ERROR,
|
||||||
|
getClass().getSimpleName() + "::onFirstTouchError",
|
||||||
|
FINGERPRINT_ENROLLING_SONFICATION_ATTRIBUTES);
|
||||||
|
} else if (remainingSteps != -1 && !mIsAccessibilityEnabled) {
|
||||||
|
mVibrator.vibrate(Process.myUid(),
|
||||||
|
mContext.getOpPackageName(),
|
||||||
|
SUCCESS_VIBRATION_EFFECT,
|
||||||
|
getClass().getSimpleName() + "::OnEnrollmentProgress",
|
||||||
|
HARDWARE_FEEDBACK_VIBRATION_ATTRIBUTES);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mShowingHelp = showingHelp;
|
||||||
|
mRemainingSteps = remainingSteps;
|
||||||
|
mTotalSteps = totalSteps;
|
||||||
|
|
||||||
|
final int progressSteps = Math.max(0, totalSteps - remainingSteps);
|
||||||
|
|
||||||
|
// If needed, add 1 to progress and total steps to account for initial touch.
|
||||||
|
final int adjustedSteps = mAfterFirstTouch ? progressSteps + 1 : progressSteps;
|
||||||
|
final int adjustedTotal = mAfterFirstTouch ? mTotalSteps + 1 : mTotalSteps;
|
||||||
|
|
||||||
|
final float targetProgress = Math.min(1f, (float) adjustedSteps / (float) adjustedTotal);
|
||||||
|
|
||||||
|
if (mProgressAnimator != null && mProgressAnimator.isRunning()) {
|
||||||
|
mProgressAnimator.cancel();
|
||||||
|
}
|
||||||
|
|
||||||
|
mProgressAnimator = ValueAnimator.ofFloat(mProgress, targetProgress);
|
||||||
|
mProgressAnimator.setDuration(PROGRESS_ANIMATION_DURATION_MS);
|
||||||
|
mProgressAnimator.addUpdateListener(mProgressUpdateListener);
|
||||||
|
mProgressAnimator.start();
|
||||||
|
|
||||||
|
if (remainingSteps == 0) {
|
||||||
|
startCompletionAnimation();
|
||||||
|
} else if (remainingSteps > 0) {
|
||||||
|
rollBackCompletionAnimation();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void animateBackgroundColor() {
|
||||||
|
if (mBackgroundColorAnimator != null && mBackgroundColorAnimator.isRunning()) {
|
||||||
|
mBackgroundColorAnimator.end();
|
||||||
|
}
|
||||||
|
mBackgroundColorAnimator = ValueAnimator.ofArgb(mBackgroundPaint.getColor(),
|
||||||
|
mOnFirstBucketFailedColor);
|
||||||
|
mBackgroundColorAnimator.setDuration(FILL_COLOR_ANIMATION_DURATION_MS);
|
||||||
|
mBackgroundColorAnimator.setRepeatCount(1);
|
||||||
|
mBackgroundColorAnimator.setRepeatMode(ValueAnimator.REVERSE);
|
||||||
|
mBackgroundColorAnimator.setInterpolator(DEACCEL);
|
||||||
|
mBackgroundColorAnimator.addUpdateListener(mBackgroundColorUpdateListener);
|
||||||
|
mBackgroundColorAnimator.start();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateFillColor(boolean showingHelp) {
|
||||||
|
if (!mAfterFirstTouch && showingHelp) {
|
||||||
|
// If we are on the first touch, animate the background color
|
||||||
|
// instead of the progress color.
|
||||||
|
animateBackgroundColor();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mFillColorAnimator != null && mFillColorAnimator.isRunning()) {
|
||||||
|
mFillColorAnimator.end();
|
||||||
|
}
|
||||||
|
|
||||||
|
@ColorInt final int targetColor = showingHelp ? mHelpColor : mProgressColor;
|
||||||
|
mFillColorAnimator = ValueAnimator.ofArgb(mFillPaint.getColor(), targetColor);
|
||||||
|
mFillColorAnimator.setDuration(FILL_COLOR_ANIMATION_DURATION_MS);
|
||||||
|
mFillColorAnimator.setRepeatCount(1);
|
||||||
|
mFillColorAnimator.setRepeatMode(ValueAnimator.REVERSE);
|
||||||
|
mFillColorAnimator.setInterpolator(DEACCEL);
|
||||||
|
mFillColorAnimator.addUpdateListener(mFillColorUpdateListener);
|
||||||
|
mFillColorAnimator.start();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void startCompletionAnimation() {
|
||||||
|
if (mComplete) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
mComplete = true;
|
||||||
|
|
||||||
|
if (mCheckmarkAnimator != null && mCheckmarkAnimator.isRunning()) {
|
||||||
|
mCheckmarkAnimator.cancel();
|
||||||
|
}
|
||||||
|
|
||||||
|
mCheckmarkAnimator = ValueAnimator.ofFloat(mCheckmarkScale, 1f);
|
||||||
|
mCheckmarkAnimator.setStartDelay(CHECKMARK_ANIMATION_DELAY_MS);
|
||||||
|
mCheckmarkAnimator.setDuration(CHECKMARK_ANIMATION_DURATION_MS);
|
||||||
|
mCheckmarkAnimator.setInterpolator(mCheckmarkInterpolator);
|
||||||
|
mCheckmarkAnimator.addUpdateListener(mCheckmarkUpdateListener);
|
||||||
|
mCheckmarkAnimator.start();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void rollBackCompletionAnimation() {
|
||||||
|
if (!mComplete) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
mComplete = false;
|
||||||
|
|
||||||
|
// Adjust duration based on how much of the completion animation has played.
|
||||||
|
final float animatedFraction = mCheckmarkAnimator != null
|
||||||
|
? mCheckmarkAnimator.getAnimatedFraction()
|
||||||
|
: 0f;
|
||||||
|
final long durationMs = Math.round(CHECKMARK_ANIMATION_DELAY_MS * animatedFraction);
|
||||||
|
|
||||||
|
if (mCheckmarkAnimator != null && mCheckmarkAnimator.isRunning()) {
|
||||||
|
mCheckmarkAnimator.cancel();
|
||||||
|
}
|
||||||
|
|
||||||
|
mCheckmarkAnimator = ValueAnimator.ofFloat(mCheckmarkScale, 0f);
|
||||||
|
mCheckmarkAnimator.setDuration(durationMs);
|
||||||
|
mCheckmarkAnimator.addUpdateListener(mCheckmarkUpdateListener);
|
||||||
|
mCheckmarkAnimator.start();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void loadResources(Context context, @Nullable AttributeSet attrs) {
|
||||||
|
final TypedArray ta = context.obtainStyledAttributes(attrs,
|
||||||
|
R.styleable.BiometricsEnrollView, R.attr.biometricsEnrollStyle,
|
||||||
|
R.style.BiometricsEnrollStyle);
|
||||||
|
mMovingTargetFill = ta.getColor(
|
||||||
|
R.styleable.BiometricsEnrollView_biometricsMovingTargetFill, 0);
|
||||||
|
mMovingTargetFillError = ta.getColor(
|
||||||
|
R.styleable.BiometricsEnrollView_biometricsMovingTargetFillError, 0);
|
||||||
|
mEnrollProgress = ta.getColor(
|
||||||
|
R.styleable.BiometricsEnrollView_biometricsEnrollProgress, 0);
|
||||||
|
mEnrollProgressHelp = ta.getColor(
|
||||||
|
R.styleable.BiometricsEnrollView_biometricsEnrollProgressHelp, 0);
|
||||||
|
mEnrollProgressHelpWithTalkback = ta.getColor(
|
||||||
|
R.styleable.BiometricsEnrollView_biometricsEnrollProgressHelpWithTalkback, 0);
|
||||||
|
ta.recycle();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void draw(@NonNull Canvas canvas) {
|
||||||
|
canvas.save();
|
||||||
|
|
||||||
|
// Progress starts from the top, instead of the right
|
||||||
|
canvas.rotate(-90f, getBounds().centerX(), getBounds().centerY());
|
||||||
|
|
||||||
|
final float halfPaddingPx = mStrokeWidthPx / 2f;
|
||||||
|
|
||||||
|
if (mProgress < 1f) {
|
||||||
|
// Draw the background color of the progress circle.
|
||||||
|
canvas.drawArc(
|
||||||
|
halfPaddingPx,
|
||||||
|
halfPaddingPx,
|
||||||
|
getBounds().right - halfPaddingPx,
|
||||||
|
getBounds().bottom - halfPaddingPx,
|
||||||
|
0f /* startAngle */,
|
||||||
|
360f /* sweepAngle */,
|
||||||
|
false /* useCenter */,
|
||||||
|
mBackgroundPaint);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mProgress > 0f) {
|
||||||
|
// Draw the filled portion of the progress circle.
|
||||||
|
canvas.drawArc(
|
||||||
|
halfPaddingPx,
|
||||||
|
halfPaddingPx,
|
||||||
|
getBounds().right - halfPaddingPx,
|
||||||
|
getBounds().bottom - halfPaddingPx,
|
||||||
|
0f /* startAngle */,
|
||||||
|
360f * mProgress /* sweepAngle */,
|
||||||
|
false /* useCenter */,
|
||||||
|
mFillPaint);
|
||||||
|
}
|
||||||
|
|
||||||
|
canvas.restore();
|
||||||
|
|
||||||
|
if (mCheckmarkScale > 0f) {
|
||||||
|
final float offsetScale = (float) Math.sqrt(2) / 2f;
|
||||||
|
final float centerXOffset = (getBounds().width() - mStrokeWidthPx) / 2f * offsetScale;
|
||||||
|
final float centerYOffset = (getBounds().height() - mStrokeWidthPx) / 2f * offsetScale;
|
||||||
|
final float centerX = getBounds().centerX() + centerXOffset;
|
||||||
|
final float centerY = getBounds().centerY() + centerYOffset;
|
||||||
|
|
||||||
|
final float boundsXOffset =
|
||||||
|
mCheckmarkDrawable.getIntrinsicWidth() / 2f * mCheckmarkScale;
|
||||||
|
final float boundsYOffset =
|
||||||
|
mCheckmarkDrawable.getIntrinsicHeight() / 2f * mCheckmarkScale;
|
||||||
|
|
||||||
|
final int left = Math.round(centerX - boundsXOffset);
|
||||||
|
final int top = Math.round(centerY - boundsYOffset);
|
||||||
|
final int right = Math.round(centerX + boundsXOffset);
|
||||||
|
final int bottom = Math.round(centerY + boundsYOffset);
|
||||||
|
mCheckmarkDrawable.setBounds(left, top, right, bottom);
|
||||||
|
mCheckmarkDrawable.draw(canvas);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setAlpha(int alpha) {}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setColorFilter(@Nullable ColorFilter colorFilter) {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getOpacity() {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,204 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2022 The Android Open Source Project
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.android.settings.biometrics.fingerprint;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.graphics.Rect;
|
||||||
|
import android.graphics.RectF;
|
||||||
|
import android.os.Handler;
|
||||||
|
import android.os.Looper;
|
||||||
|
import android.util.AttributeSet;
|
||||||
|
import android.util.RotationUtils;
|
||||||
|
import android.view.Gravity;
|
||||||
|
import android.view.Surface;
|
||||||
|
import android.view.View;
|
||||||
|
import android.view.ViewGroup;
|
||||||
|
import android.widget.FrameLayout;
|
||||||
|
import android.widget.ImageView;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
|
||||||
|
import com.android.settings.R;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* View corresponding with udfps_enroll_view.xml
|
||||||
|
*/
|
||||||
|
public class UdfpsEnrollView extends FrameLayout implements UdfpsEnrollHelper.Listener {
|
||||||
|
@NonNull
|
||||||
|
private final UdfpsEnrollDrawable mFingerprintDrawable;
|
||||||
|
@NonNull
|
||||||
|
private final UdfpsEnrollProgressBarDrawable mFingerprintProgressDrawable;
|
||||||
|
@NonNull
|
||||||
|
private final Handler mHandler;
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
private ImageView mFingerprintProgressView;
|
||||||
|
|
||||||
|
private int mProgressBarRadius;
|
||||||
|
|
||||||
|
// sensorRect may be bigger than the sensor. True sensor dimensions are defined in
|
||||||
|
// overlayParams.sensorBounds
|
||||||
|
private Rect mSensorRect;
|
||||||
|
private UdfpsOverlayParams mOverlayParams;
|
||||||
|
|
||||||
|
public UdfpsEnrollView(Context context, @Nullable AttributeSet attrs) {
|
||||||
|
super(context, attrs);
|
||||||
|
mFingerprintDrawable = new UdfpsEnrollDrawable(mContext, attrs);
|
||||||
|
mFingerprintProgressDrawable = new UdfpsEnrollProgressBarDrawable(context, attrs);
|
||||||
|
mHandler = new Handler(Looper.getMainLooper());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onFinishInflate() {
|
||||||
|
ImageView fingerprintView = findViewById(R.id.udfps_enroll_animation_fp_view);
|
||||||
|
fingerprintView.setImageDrawable(mFingerprintDrawable);
|
||||||
|
mFingerprintProgressView = findViewById(R.id.udfps_enroll_animation_fp_progress_view);
|
||||||
|
mFingerprintProgressView.setImageDrawable(mFingerprintProgressDrawable);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Implements UdfpsEnrollHelper.Listener
|
||||||
|
@Override
|
||||||
|
public void onEnrollmentProgress(int remaining, int totalSteps) {
|
||||||
|
mHandler.post(() -> {
|
||||||
|
mFingerprintProgressDrawable.onEnrollmentProgress(remaining, totalSteps);
|
||||||
|
mFingerprintDrawable.onEnrollmentProgress(remaining, totalSteps);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onEnrollmentHelp(int remaining, int totalSteps) {
|
||||||
|
mHandler.post(() -> mFingerprintProgressDrawable.onEnrollmentHelp(remaining, totalSteps));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onAcquired(boolean animateIfLastStepGood) {
|
||||||
|
mHandler.post(() -> {
|
||||||
|
if (animateIfLastStepGood) mFingerprintProgressDrawable.onLastStepAcquired();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void setOverlayParams(UdfpsOverlayParams params) {
|
||||||
|
mOverlayParams = params;
|
||||||
|
|
||||||
|
post(() -> {
|
||||||
|
mProgressBarRadius =
|
||||||
|
(int) (mOverlayParams.getScaleFactor() * getContext().getResources().getInteger(
|
||||||
|
R.integer.config_udfpsEnrollProgressBar));
|
||||||
|
mSensorRect = mOverlayParams.getSensorBounds();
|
||||||
|
|
||||||
|
onSensorRectUpdated();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void setEnrollHelper(UdfpsEnrollHelper enrollHelper) {
|
||||||
|
mFingerprintDrawable.setEnrollHelper(enrollHelper);
|
||||||
|
enrollHelper.setListener(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void onSensorRectUpdated() {
|
||||||
|
updateDimensions();
|
||||||
|
updateAccessibilityViewLocation();
|
||||||
|
|
||||||
|
// Updates sensor rect in relation to the overlay view
|
||||||
|
mSensorRect.set(getPaddingX(), getPaddingY(),
|
||||||
|
(mOverlayParams.getSensorBounds().width() + getPaddingX()),
|
||||||
|
(mOverlayParams.getSensorBounds().height() + getPaddingY()));
|
||||||
|
mFingerprintDrawable.onSensorRectUpdated(new RectF(mSensorRect));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateDimensions() {
|
||||||
|
// Original sensorBounds assume portrait mode.
|
||||||
|
Rect rotatedBounds = mOverlayParams.getSensorBounds();
|
||||||
|
int rotation = mOverlayParams.getRotation();
|
||||||
|
if (rotation == Surface.ROTATION_90 || rotation == Surface.ROTATION_270) {
|
||||||
|
RotationUtils.rotateBounds(
|
||||||
|
rotatedBounds,
|
||||||
|
mOverlayParams.getNaturalDisplayWidth(),
|
||||||
|
mOverlayParams.getNaturalDisplayHeight(),
|
||||||
|
rotation
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Use parent view's and rotatedBound's absolute coordinates to decide the margins of
|
||||||
|
// UdfpsEnrollView, so that its center keeps consistent with sensor rect's.
|
||||||
|
ViewGroup parentView = (ViewGroup) getParent();
|
||||||
|
int[] coords = parentView.getLocationOnScreen();
|
||||||
|
int parentLeft = coords[0];
|
||||||
|
int parentTop = coords[1];
|
||||||
|
int parentRight = parentLeft + parentView.getWidth();
|
||||||
|
int parentBottom = parentTop + parentView.getHeight();
|
||||||
|
MarginLayoutParams marginLayoutParams = (MarginLayoutParams) getLayoutParams();
|
||||||
|
FrameLayout.LayoutParams params = (LayoutParams) getLayoutParams();
|
||||||
|
|
||||||
|
switch (rotation) {
|
||||||
|
case Surface.ROTATION_0:
|
||||||
|
case Surface.ROTATION_180:
|
||||||
|
params.gravity = Gravity.RIGHT | Gravity.TOP;
|
||||||
|
marginLayoutParams.rightMargin = parentRight - rotatedBounds.right - getPaddingX();
|
||||||
|
marginLayoutParams.topMargin = rotatedBounds.top - parentTop - getPaddingY();
|
||||||
|
break;
|
||||||
|
case Surface.ROTATION_90:
|
||||||
|
params.gravity = Gravity.RIGHT | Gravity.BOTTOM;
|
||||||
|
marginLayoutParams.rightMargin = parentRight - rotatedBounds.right - getPaddingX();
|
||||||
|
marginLayoutParams.bottomMargin =
|
||||||
|
parentBottom - rotatedBounds.bottom - getPaddingY();
|
||||||
|
break;
|
||||||
|
case Surface.ROTATION_270:
|
||||||
|
params.gravity = Gravity.LEFT | Gravity.BOTTOM;
|
||||||
|
marginLayoutParams.leftMargin = rotatedBounds.left - parentLeft - getPaddingX();
|
||||||
|
marginLayoutParams.bottomMargin =
|
||||||
|
parentBottom - rotatedBounds.bottom - getPaddingY();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
params.height = rotatedBounds.height() + 2 * getPaddingX();
|
||||||
|
params.width = rotatedBounds.width() + 2 * getPaddingY();
|
||||||
|
setLayoutParams(params);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateAccessibilityViewLocation() {
|
||||||
|
View fingerprintAccessibilityView = findViewById(R.id.udfps_enroll_accessibility_view);
|
||||||
|
ViewGroup.LayoutParams params = fingerprintAccessibilityView.getLayoutParams();
|
||||||
|
params.width = mOverlayParams.getSensorBounds().width();
|
||||||
|
params.height = mOverlayParams.getSensorBounds().height();
|
||||||
|
fingerprintAccessibilityView.setLayoutParams(params);
|
||||||
|
fingerprintAccessibilityView.requestLayout();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void onFingerDown() {
|
||||||
|
if (mOverlayParams.isOptical()) {
|
||||||
|
mFingerprintDrawable.setShouldSkipDraw(true);
|
||||||
|
mFingerprintDrawable.invalidateSelf();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void onFingerUp() {
|
||||||
|
if (mOverlayParams.isOptical()) {
|
||||||
|
mFingerprintDrawable.setShouldSkipDraw(false);
|
||||||
|
mFingerprintDrawable.invalidateSelf();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private int getPaddingX() {
|
||||||
|
return mProgressBarRadius;
|
||||||
|
}
|
||||||
|
|
||||||
|
private int getPaddingY() {
|
||||||
|
return mProgressBarRadius;
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,95 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2022 The Android Open Source Project
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.android.settings.biometrics.fingerprint;
|
||||||
|
|
||||||
|
import android.graphics.Rect;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Collection of parameters that define an under-display fingerprint sensor (UDFPS) overlay.
|
||||||
|
*
|
||||||
|
* [sensorBounds] coordinates of the bounding box around the sensor in natural orientation, in
|
||||||
|
* pixels, for the current resolution.
|
||||||
|
*
|
||||||
|
* [overlayBounds] coordinates of the UI overlay in natural orientation, in pixels, for the current
|
||||||
|
* resolution.
|
||||||
|
*
|
||||||
|
* [naturalDisplayWidth] width of the physical display in natural orientation, in pixels, for the
|
||||||
|
* current resolution.
|
||||||
|
*
|
||||||
|
* [naturalDisplayHeight] height of the physical display in natural orientation, in pixels, for the
|
||||||
|
* current resolution.
|
||||||
|
*
|
||||||
|
* [scaleFactor] ratio of a dimension in the current resolution to the corresponding dimension in
|
||||||
|
* the native resolution.
|
||||||
|
*
|
||||||
|
* [rotation] current rotation of the display.
|
||||||
|
*/
|
||||||
|
public final class UdfpsOverlayParams {
|
||||||
|
@NonNull
|
||||||
|
private final Rect mSensorBounds;
|
||||||
|
@NonNull
|
||||||
|
private final Rect mOverlayBounds;
|
||||||
|
private final int mNaturalDisplayWidth;
|
||||||
|
private final int mNaturalDisplayHeight;
|
||||||
|
private final float mScaleFactor;
|
||||||
|
private final int mRotation;
|
||||||
|
private final boolean mIsOptical;
|
||||||
|
|
||||||
|
public UdfpsOverlayParams(@NonNull Rect sensorBounds, @NonNull Rect overlayBounds,
|
||||||
|
int naturalDisplayWidth, int naturalDisplayHeight, float scaleFactor, int rotation,
|
||||||
|
boolean isOptical) {
|
||||||
|
mSensorBounds = sensorBounds;
|
||||||
|
mOverlayBounds = overlayBounds;
|
||||||
|
mNaturalDisplayWidth = naturalDisplayWidth;
|
||||||
|
mNaturalDisplayHeight = naturalDisplayHeight;
|
||||||
|
mScaleFactor = scaleFactor;
|
||||||
|
mRotation = rotation;
|
||||||
|
mIsOptical = isOptical;
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
public Rect getSensorBounds() {
|
||||||
|
return mSensorBounds;
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
public Rect getOverlayBounds() {
|
||||||
|
return mOverlayBounds;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getNaturalDisplayWidth() {
|
||||||
|
return mNaturalDisplayWidth;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getNaturalDisplayHeight() {
|
||||||
|
return mNaturalDisplayHeight;
|
||||||
|
}
|
||||||
|
|
||||||
|
public float getScaleFactor() {
|
||||||
|
return mScaleFactor;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getRotation() {
|
||||||
|
return mRotation;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isOptical() {
|
||||||
|
return mIsOptical;
|
||||||
|
}
|
||||||
|
}
|
Reference in New Issue
Block a user