Refactor FingerprintEnrollEnrolling to fragment
Bug: b/260957933 Test: NA Change-Id: I281ec7a7373f6863e2e6f8f20bbbb01fa01e009a
This commit is contained in:
104
res/layout/udfps_enroll_enrolling_v2.xml
Normal file
104
res/layout/udfps_enroll_enrolling_v2.xml
Normal file
@@ -0,0 +1,104 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!--
|
||||||
|
~ Copyright (C) 2023 The Android Open Source Project
|
||||||
|
~
|
||||||
|
~ Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
~ you may not use this file except in compliance with the License.
|
||||||
|
~ You may obtain a copy of the License at
|
||||||
|
~
|
||||||
|
~ http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
~
|
||||||
|
~ Unless required by applicable law or agreed to in writing, software
|
||||||
|
~ distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
~ See the License for the specific language governing permissions and
|
||||||
|
~ limitations under the License.
|
||||||
|
-->
|
||||||
|
<RelativeLayout
|
||||||
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
android:id="@+id/suw_lift"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent">
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/sud_layout_icon"
|
||||||
|
style="@style/SudGlifIcon"
|
||||||
|
android:layout_marginStart="48dp"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_alignParentStart="true"
|
||||||
|
android:layout_alignParentTop="true"
|
||||||
|
android:layout_marginTop="68dp"
|
||||||
|
android:scaleType="fitStart"
|
||||||
|
android:src="@drawable/ic_lock" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
style="@style/SudGlifHeaderTitle"
|
||||||
|
android:id="@+id/suc_layout_title"
|
||||||
|
android:layout_alignParentTop="true"
|
||||||
|
android:layout_alignParentStart="true"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="138dp"
|
||||||
|
android:layout_marginStart="40dp"
|
||||||
|
android:layout_marginEnd="24dp"/>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
style="@style/SudDescription.Glif"
|
||||||
|
android:id="@+id/sud_layout_subtitle"
|
||||||
|
android:layout_alignParentTop="true"
|
||||||
|
android:layout_alignParentStart="true"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="206dp"
|
||||||
|
android:layout_marginStart="40dp"
|
||||||
|
android:layout_marginEnd="24dp"/>
|
||||||
|
|
||||||
|
<com.airbnb.lottie.LottieAnimationView
|
||||||
|
android:id="@+id/illustration_lottie"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="200dp"
|
||||||
|
android:layout_alignParentTop="true"
|
||||||
|
android:layout_alignParentStart="true"
|
||||||
|
android:layout_marginTop="294dp"
|
||||||
|
android:layout_marginStart="16dp"
|
||||||
|
android:layout_marginEnd="17dp"
|
||||||
|
android:scaleType="centerInside"
|
||||||
|
android:visibility="gone"
|
||||||
|
app:lottie_autoPlay="true"
|
||||||
|
app:lottie_loop="true"
|
||||||
|
app:lottie_speed=".85" />
|
||||||
|
|
||||||
|
<com.android.settings.biometrics2.ui.widget.UdfpsEnrollView
|
||||||
|
android:id="@+id/udfps_animation_view"
|
||||||
|
android:layout_width="218.42dp"
|
||||||
|
android:layout_height="216dp"
|
||||||
|
android:layout_alignParentTop="true"
|
||||||
|
android:layout_centerHorizontal="true"
|
||||||
|
android:layout_marginTop="553dp">
|
||||||
|
|
||||||
|
<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.biometrics2.ui.widget.UdfpsEnrollView>
|
||||||
|
|
||||||
|
<Button
|
||||||
|
style="@style/SudGlifButton.Secondary"
|
||||||
|
android:id="@+id/skip_btn"
|
||||||
|
android:text="@string/security_settings_fingerprint_enroll_enrolling_skip"
|
||||||
|
android:layout_alignParentBottom="true"
|
||||||
|
android:layout_alignParentStart="true"
|
||||||
|
android:layout_marginBottom="26dp"
|
||||||
|
android:layout_marginStart="66dp"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"/>
|
||||||
|
|
||||||
|
</RelativeLayout>
|
@@ -22,24 +22,19 @@ import android.annotation.RawRes;
|
|||||||
import android.app.Activity;
|
import android.app.Activity;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.res.Configuration;
|
import android.content.res.Configuration;
|
||||||
import android.graphics.Point;
|
|
||||||
import android.graphics.Rect;
|
|
||||||
import android.graphics.drawable.Animatable2;
|
import android.graphics.drawable.Animatable2;
|
||||||
import android.graphics.drawable.Drawable;
|
import android.graphics.drawable.Drawable;
|
||||||
import android.hardware.fingerprint.FingerprintManager;
|
|
||||||
import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
|
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.text.TextUtils;
|
import android.text.TextUtils;
|
||||||
|
import android.util.DisplayMetrics;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
import android.view.DisplayInfo;
|
|
||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
import android.view.Surface;
|
import android.view.Surface;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
import android.view.animation.AnimationUtils;
|
import android.widget.Button;
|
||||||
import android.view.animation.Interpolator;
|
import android.widget.ImageView;
|
||||||
import android.widget.FrameLayout;
|
import android.widget.RelativeLayout;
|
||||||
import android.widget.LinearLayout;
|
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
@@ -52,25 +47,16 @@ import androidx.transition.TransitionSet;
|
|||||||
|
|
||||||
import com.android.settings.R;
|
import com.android.settings.R;
|
||||||
import com.android.settings.biometrics.BiometricUtils;
|
import com.android.settings.biometrics.BiometricUtils;
|
||||||
import com.android.settings.biometrics.fingerprint.UdfpsEnrollHelper;
|
|
||||||
import com.android.settings.biometrics.fingerprint.UdfpsEnrollView;
|
|
||||||
import com.android.settings.biometrics2.ui.model.EnrollmentProgress;
|
import com.android.settings.biometrics2.ui.model.EnrollmentProgress;
|
||||||
import com.android.settings.biometrics2.ui.model.EnrollmentStatusMessage;
|
import com.android.settings.biometrics2.ui.model.EnrollmentStatusMessage;
|
||||||
import com.android.settings.biometrics2.ui.viewmodel.DeviceRotationViewModel;
|
import com.android.settings.biometrics2.ui.viewmodel.DeviceRotationViewModel;
|
||||||
import com.android.settings.biometrics2.ui.viewmodel.FingerprintEnrollEnrollingViewModel;
|
import com.android.settings.biometrics2.ui.viewmodel.FingerprintEnrollEnrollingViewModel;
|
||||||
import com.android.settings.biometrics2.ui.viewmodel.FingerprintEnrollProgressViewModel;
|
import com.android.settings.biometrics2.ui.viewmodel.FingerprintEnrollProgressViewModel;
|
||||||
|
import com.android.settings.biometrics2.ui.widget.UdfpsEnrollView;
|
||||||
import com.android.settingslib.display.DisplayDensityUtils;
|
import com.android.settingslib.display.DisplayDensityUtils;
|
||||||
import com.android.settingslib.udfps.UdfpsOverlayParams;
|
|
||||||
import com.android.settingslib.udfps.UdfpsUtils;
|
|
||||||
|
|
||||||
import com.airbnb.lottie.LottieAnimationView;
|
import com.airbnb.lottie.LottieAnimationView;
|
||||||
import com.airbnb.lottie.LottieCompositionFactory;
|
import com.airbnb.lottie.LottieCompositionFactory;
|
||||||
import com.google.android.setupcompat.template.FooterActionButton;
|
|
||||||
import com.google.android.setupcompat.template.FooterBarMixin;
|
|
||||||
import com.google.android.setupcompat.template.FooterButton;
|
|
||||||
import com.google.android.setupdesign.GlifLayout;
|
|
||||||
|
|
||||||
import java.util.Locale;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Fragment is used to handle enrolling process for udfps
|
* Fragment is used to handle enrolling process for udfps
|
||||||
@@ -92,9 +78,6 @@ public class FingerprintEnrollEnrollingUdfpsFragment extends Fragment {
|
|||||||
private DeviceRotationViewModel mRotationViewModel;
|
private DeviceRotationViewModel mRotationViewModel;
|
||||||
private FingerprintEnrollProgressViewModel mProgressViewModel;
|
private FingerprintEnrollProgressViewModel mProgressViewModel;
|
||||||
|
|
||||||
private Interpolator mFastOutSlowInInterpolator;
|
|
||||||
private Interpolator mLinearOutSlowInInterpolator;
|
|
||||||
private Interpolator mFastOutLinearInInterpolator;
|
|
||||||
private boolean mAnimationCancelled;
|
private boolean mAnimationCancelled;
|
||||||
|
|
||||||
private LottieAnimationView mIllustrationLottie;
|
private LottieAnimationView mIllustrationLottie;
|
||||||
@@ -104,9 +87,13 @@ public class FingerprintEnrollEnrollingUdfpsFragment extends Fragment {
|
|||||||
private boolean mHaveShownUdfpsCenterLottie;
|
private boolean mHaveShownUdfpsCenterLottie;
|
||||||
private boolean mHaveShownUdfpsGuideLottie;
|
private boolean mHaveShownUdfpsGuideLottie;
|
||||||
|
|
||||||
private GlifLayout mView;
|
private RelativeLayout mView;
|
||||||
|
private ImageView mIcon;
|
||||||
private TextView mErrorText;
|
private TextView mErrorText;
|
||||||
private FooterBarMixin mFooterBarMixin;
|
private TextView mTitleText;
|
||||||
|
private TextView mSubTitleText;
|
||||||
|
private Button mSkipBtn;
|
||||||
|
private UdfpsEnrollView mUdfpsEnrollView;
|
||||||
|
|
||||||
private boolean mShouldShowLottie;
|
private boolean mShouldShowLottie;
|
||||||
private boolean mIsAccessibilityEnabled;
|
private boolean mIsAccessibilityEnabled;
|
||||||
@@ -145,13 +132,6 @@ public class FingerprintEnrollEnrollingUdfpsFragment extends Fragment {
|
|||||||
|
|
||||||
private int mIconTouchCount;
|
private int mIconTouchCount;
|
||||||
|
|
||||||
private UdfpsUtils mUdfpsUtils;
|
|
||||||
private float mScaleFactor = 1.0f;
|
|
||||||
//TODO UdfpsEnrollHelper should not be a Fragment, we should tell enrollview & progress
|
|
||||||
// drawable enough information EnrollView & ProgressDrawable should draw themselves without
|
|
||||||
// UdfpsEnrollHelper
|
|
||||||
private UdfpsEnrollHelper mUdfpsEnrollHelper;
|
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onAttach(@NonNull Context context) {
|
public void onAttach(@NonNull Context context) {
|
||||||
@@ -200,10 +180,6 @@ public class FingerprintEnrollEnrollingUdfpsFragment extends Fragment {
|
|||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
mEnrollingViewModel.restoreSavedState(savedInstanceState);
|
mEnrollingViewModel.restoreSavedState(savedInstanceState);
|
||||||
mIsAccessibilityEnabled = mEnrollingViewModel.isAccessibilityEnabled();
|
mIsAccessibilityEnabled = mEnrollingViewModel.isAccessibilityEnabled();
|
||||||
mUdfpsUtils = new UdfpsUtils();
|
|
||||||
mUdfpsEnrollHelper = new UdfpsEnrollHelper(getActivity(), getActivity().getSystemService(
|
|
||||||
FingerprintManager.class
|
|
||||||
));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -216,95 +192,85 @@ public class FingerprintEnrollEnrollingUdfpsFragment extends Fragment {
|
|||||||
public View onCreateView(LayoutInflater inflater, ViewGroup container,
|
public View onCreateView(LayoutInflater inflater, ViewGroup container,
|
||||||
Bundle savedInstanceState) {
|
Bundle savedInstanceState) {
|
||||||
mView = initUdfpsLayout(inflater, container);
|
mView = initUdfpsLayout(inflater, container);
|
||||||
|
|
||||||
return mView;
|
return mView;
|
||||||
}
|
}
|
||||||
|
|
||||||
private GlifLayout initUdfpsLayout(LayoutInflater inflater, ViewGroup container) {
|
private RelativeLayout initUdfpsLayout(LayoutInflater inflater, ViewGroup container) {
|
||||||
final GlifLayout containView = (GlifLayout) inflater.inflate(
|
final RelativeLayout containView = (RelativeLayout) inflater.inflate(
|
||||||
R.layout.udfps_enroll_enrolling, container, false);
|
R.layout.udfps_enroll_enrolling_v2, container, false);
|
||||||
final UdfpsEnrollView udfpsEnrollView = addUdfpsEnrollView(inflater,
|
|
||||||
mEnrollingViewModel.getFirstFingerprintSensorPropertiesInternal());
|
|
||||||
final int rotation = mRotationViewModel.getLiveData().getValue();
|
|
||||||
if (rotation == Surface.ROTATION_90) {
|
|
||||||
final boolean isLayoutRtl = (TextUtils.getLayoutDirectionFromLocale(
|
|
||||||
Locale.getDefault()) == View.LAYOUT_DIRECTION_RTL);
|
|
||||||
final LinearLayout layoutContainer = containView.findViewById(
|
|
||||||
R.id.layout_container);
|
|
||||||
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);
|
|
||||||
|
|
||||||
final LinearLayout.LayoutParams lp = new LinearLayout.LayoutParams(
|
|
||||||
LinearLayout.LayoutParams.MATCH_PARENT,
|
|
||||||
LinearLayout.LayoutParams.MATCH_PARENT);
|
|
||||||
lp.setMarginEnd((int) getResources().getDimension(
|
|
||||||
R.dimen.rotation_90_enroll_margin_end));
|
|
||||||
layoutContainer.setLayoutParams(lp);
|
|
||||||
containView.addView(udfpsEnrollView);
|
|
||||||
containView.setClipChildren(false);
|
|
||||||
containView.setClipToPadding(false);
|
|
||||||
containView.setLayoutParams(lp);
|
|
||||||
setOnHoverListener(true, containView, udfpsEnrollView);
|
|
||||||
} else if (rotation == Surface.ROTATION_270) {
|
|
||||||
containView.addView(udfpsEnrollView);
|
|
||||||
containView.setClipChildren(false);
|
|
||||||
containView.setClipToPadding(false);
|
|
||||||
setOnHoverListener(true, containView, udfpsEnrollView);
|
|
||||||
} else {
|
|
||||||
final FrameLayout portraitLayoutContainer = containView.findViewById(
|
|
||||||
R.id.layout_container);
|
|
||||||
portraitLayoutContainer.addView(udfpsEnrollView);
|
|
||||||
ViewGroup parent = ((ViewGroup) portraitLayoutContainer.getParent());
|
|
||||||
parent.setClipChildren(false);
|
|
||||||
parent.setClipToPadding(false);
|
|
||||||
setOnHoverListener(false, containView, udfpsEnrollView);
|
|
||||||
}
|
|
||||||
|
|
||||||
final Activity activity = getActivity();
|
final Activity activity = getActivity();
|
||||||
new GlifLayoutHelper(activity, containView).setDescriptionText(
|
mIcon = containView.findViewById(R.id.sud_layout_icon);
|
||||||
getString(R.string.security_settings_udfps_enroll_start_message));
|
mTitleText = containView.findViewById(R.id.suc_layout_title);
|
||||||
|
mSubTitleText = containView.findViewById(R.id.sud_layout_subtitle);
|
||||||
|
mErrorText = containView.findViewById(R.id.error_text);
|
||||||
|
mSkipBtn = containView.findViewById(R.id.skip_btn);
|
||||||
|
mSkipBtn.setOnClickListener(mOnSkipClickListener);
|
||||||
|
mUdfpsEnrollView = containView.findViewById(R.id.udfps_animation_view);
|
||||||
|
mUdfpsEnrollView.setSensorProperties(
|
||||||
|
mEnrollingViewModel.getFirstFingerprintSensorPropertiesInternal());
|
||||||
mShouldShowLottie = shouldShowLottie();
|
mShouldShowLottie = shouldShowLottie();
|
||||||
boolean isLandscape = BiometricUtils.isReverseLandscape(activity)
|
boolean isLandscape = BiometricUtils.isReverseLandscape(activity)
|
||||||
|| BiometricUtils.isLandscape(activity);
|
|| BiometricUtils.isLandscape(activity);
|
||||||
updateOrientation(containView, (isLandscape
|
updateOrientation(containView, (isLandscape
|
||||||
? Configuration.ORIENTATION_LANDSCAPE : Configuration.ORIENTATION_PORTRAIT));
|
? Configuration.ORIENTATION_LANDSCAPE : Configuration.ORIENTATION_PORTRAIT));
|
||||||
|
|
||||||
mErrorText = containView.findViewById(R.id.error_text);
|
|
||||||
mFooterBarMixin = containView.getMixin(FooterBarMixin.class);
|
|
||||||
mFooterBarMixin.setSecondaryButton(
|
|
||||||
new FooterButton.Builder(activity)
|
|
||||||
.setText(R.string.security_settings_fingerprint_enroll_enrolling_skip)
|
|
||||||
.setListener(mOnSkipClickListener)
|
|
||||||
.setButtonType(FooterButton.ButtonType.SKIP)
|
|
||||||
.setTheme(R.style.SudGlifButton_Secondary)
|
|
||||||
.build()
|
|
||||||
);
|
|
||||||
|
|
||||||
mFastOutSlowInInterpolator = AnimationUtils.loadInterpolator(
|
final int rotation = mRotationViewModel.getLiveData().getValue();
|
||||||
activity, android.R.interpolator.fast_out_slow_in);
|
if (rotation == Surface.ROTATION_270) {
|
||||||
mLinearOutSlowInInterpolator = AnimationUtils.loadInterpolator(
|
RelativeLayout.LayoutParams iconLP = new RelativeLayout.LayoutParams(-2, -2);
|
||||||
activity, android.R.interpolator.linear_out_slow_in);
|
iconLP.addRule(RelativeLayout.ALIGN_PARENT_TOP);
|
||||||
mFastOutLinearInInterpolator = AnimationUtils.loadInterpolator(
|
iconLP.addRule(RelativeLayout.END_OF, R.id.udfps_animation_view);
|
||||||
activity, android.R.interpolator.fast_out_linear_in);
|
iconLP.topMargin = (int) convertDpToPixel(76.64f, activity);
|
||||||
|
iconLP.leftMargin = (int) convertDpToPixel(151.54f, activity);
|
||||||
|
mIcon.setLayoutParams(iconLP);
|
||||||
|
|
||||||
final LinearLayout buttonContainer = mFooterBarMixin.getButtonContainer();
|
RelativeLayout.LayoutParams titleLP = new RelativeLayout.LayoutParams(-1, -2);
|
||||||
View spaceView = null;
|
titleLP.addRule(RelativeLayout.ALIGN_PARENT_TOP);
|
||||||
for (int i = 0; i < buttonContainer.getChildCount(); i++) {
|
titleLP.addRule(RelativeLayout.END_OF, R.id.udfps_animation_view);
|
||||||
if (!(buttonContainer.getChildAt(i) instanceof FooterActionButton)) {
|
titleLP.topMargin = (int) convertDpToPixel(138f, activity);
|
||||||
spaceView = buttonContainer.getChildAt(i);
|
titleLP.leftMargin = (int) convertDpToPixel(144f, activity);
|
||||||
break;
|
mTitleText.setLayoutParams(titleLP);
|
||||||
}
|
|
||||||
}
|
RelativeLayout.LayoutParams subtitleLP = new RelativeLayout.LayoutParams(-1, -2);
|
||||||
if (spaceView != null) {
|
subtitleLP.addRule(RelativeLayout.ALIGN_PARENT_TOP);
|
||||||
spaceView.setVisibility(View.GONE);
|
subtitleLP.addRule(RelativeLayout.END_OF, R.id.udfps_animation_view);
|
||||||
buttonContainer.getLayoutParams().width = ViewGroup.LayoutParams.WRAP_CONTENT;
|
subtitleLP.topMargin = (int) convertDpToPixel(198f, activity);
|
||||||
|
subtitleLP.leftMargin = (int) convertDpToPixel(144f, activity);
|
||||||
|
mSubTitleText.setLayoutParams(subtitleLP);
|
||||||
|
} else if (rotation == Surface.ROTATION_90) {
|
||||||
|
DisplayMetrics metrics = activity.getResources().getDisplayMetrics();
|
||||||
|
RelativeLayout.LayoutParams iconLP = new RelativeLayout.LayoutParams(-2, -2);
|
||||||
|
iconLP.addRule(RelativeLayout.ALIGN_PARENT_TOP);
|
||||||
|
iconLP.addRule(RelativeLayout.ALIGN_PARENT_START);
|
||||||
|
iconLP.topMargin = (int) convertDpToPixel(76.64f, activity);
|
||||||
|
iconLP.leftMargin = (int) convertDpToPixel(71.99f, activity);
|
||||||
|
mIcon.setLayoutParams(iconLP);
|
||||||
|
|
||||||
|
RelativeLayout.LayoutParams titleLP = new RelativeLayout.LayoutParams(
|
||||||
|
metrics.widthPixels / 2, -2);
|
||||||
|
titleLP.addRule(RelativeLayout.ALIGN_PARENT_TOP);
|
||||||
|
titleLP.addRule(RelativeLayout.ALIGN_PARENT_START, R.id.udfps_animation_view);
|
||||||
|
titleLP.topMargin = (int) convertDpToPixel(138f, activity);
|
||||||
|
titleLP.leftMargin = (int) convertDpToPixel(66f, activity);
|
||||||
|
mTitleText.setLayoutParams(titleLP);
|
||||||
|
|
||||||
|
RelativeLayout.LayoutParams subtitleLP = new RelativeLayout.LayoutParams(
|
||||||
|
metrics.widthPixels / 2, -2);
|
||||||
|
subtitleLP.addRule(RelativeLayout.ALIGN_PARENT_TOP);
|
||||||
|
subtitleLP.addRule(RelativeLayout.ALIGN_PARENT_START);
|
||||||
|
subtitleLP.topMargin = (int) convertDpToPixel(198f, activity);
|
||||||
|
subtitleLP.leftMargin = (int) convertDpToPixel(66f, activity);
|
||||||
|
mSubTitleText.setLayoutParams(subtitleLP);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (rotation == Surface.ROTATION_90 || rotation == Surface.ROTATION_270) {
|
||||||
|
RelativeLayout.LayoutParams skipBtnLP =
|
||||||
|
(RelativeLayout.LayoutParams) mIcon.getLayoutParams();
|
||||||
|
skipBtnLP.topMargin = (int) convertDpToPixel(26f, activity);
|
||||||
|
skipBtnLP.leftMargin = (int) convertDpToPixel(54f, activity);
|
||||||
|
mSkipBtn.requestLayout();
|
||||||
|
}
|
||||||
return containView;
|
return containView;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -358,8 +324,9 @@ public class FingerprintEnrollEnrollingUdfpsFragment extends Fragment {
|
|||||||
|
|
||||||
final int progress = getProgress(enrollmentProgress);
|
final int progress = getProgress(enrollmentProgress);
|
||||||
|
|
||||||
mUdfpsEnrollHelper.onEnrollmentProgress(enrollmentProgress.getSteps(),
|
|
||||||
enrollmentProgress.getRemaining());
|
mUdfpsEnrollView.onEnrollmentProgress(enrollmentProgress.getRemaining(),
|
||||||
|
enrollmentProgress.getSteps());
|
||||||
|
|
||||||
if (animate) {
|
if (animate) {
|
||||||
animateProgress(progress);
|
animateProgress(progress);
|
||||||
@@ -382,38 +349,6 @@ public class FingerprintEnrollEnrollingUdfpsFragment extends Fragment {
|
|||||||
super.onDestroy();
|
super.onDestroy();
|
||||||
}
|
}
|
||||||
|
|
||||||
private UdfpsEnrollView addUdfpsEnrollView(LayoutInflater inflater,
|
|
||||||
FingerprintSensorPropertiesInternal udfpsProps) {
|
|
||||||
|
|
||||||
UdfpsEnrollView enrollView = (UdfpsEnrollView) inflater.inflate(R.layout.udfps_enroll_view,
|
|
||||||
null, false);
|
|
||||||
DisplayInfo displayInfo = new DisplayInfo();
|
|
||||||
getActivity().getDisplay().getDisplayInfo(displayInfo);
|
|
||||||
mScaleFactor = mUdfpsUtils.getScaleFactor(displayInfo);
|
|
||||||
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 */);
|
|
||||||
|
|
||||||
UdfpsOverlayParams params = new UdfpsOverlayParams(
|
|
||||||
udfpsBounds,
|
|
||||||
overlayBounds,
|
|
||||||
displayInfo.getNaturalWidth(),
|
|
||||||
displayInfo.getNaturalHeight(),
|
|
||||||
mScaleFactor,
|
|
||||||
displayInfo.rotation);
|
|
||||||
|
|
||||||
enrollView.setOverlayParams(params);
|
|
||||||
|
|
||||||
enrollView.setEnrollHelper(mUdfpsEnrollHelper);
|
|
||||||
|
|
||||||
return enrollView;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void animateProgress(int progress) {
|
private void animateProgress(int progress) {
|
||||||
// UDFPS animations are owned by SystemUI
|
// UDFPS animations are owned by SystemUI
|
||||||
if (progress >= PROGRESS_BAR_MAX) {
|
if (progress >= PROGRESS_BAR_MAX) {
|
||||||
@@ -423,17 +358,11 @@ public class FingerprintEnrollEnrollingUdfpsFragment extends Fragment {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void updateTitleAndDescription() {
|
private void updateTitleAndDescription() {
|
||||||
|
|
||||||
final Activity activity = getActivity();
|
|
||||||
final GlifLayoutHelper glifLayoutHelper = new GlifLayoutHelper(activity, mView);
|
|
||||||
|
|
||||||
switch (getCurrentStage()) {
|
switch (getCurrentStage()) {
|
||||||
case STAGE_CENTER:
|
case STAGE_CENTER:
|
||||||
glifLayoutHelper.setHeaderText(
|
mTitleText.setText(R.string.security_settings_fingerprint_enroll_repeat_title);
|
||||||
R.string.security_settings_fingerprint_enroll_repeat_title);
|
|
||||||
if (mIsAccessibilityEnabled || mIllustrationLottie == null) {
|
if (mIsAccessibilityEnabled || mIllustrationLottie == null) {
|
||||||
glifLayoutHelper.setDescriptionText(getString(
|
mSubTitleText.setText(R.string.security_settings_udfps_enroll_start_message);
|
||||||
R.string.security_settings_udfps_enroll_start_message));
|
|
||||||
} else if (!mHaveShownUdfpsCenterLottie && mIllustrationLottie != null) {
|
} else if (!mHaveShownUdfpsCenterLottie && mIllustrationLottie != null) {
|
||||||
mHaveShownUdfpsCenterLottie = true;
|
mHaveShownUdfpsCenterLottie = true;
|
||||||
// Note: Update string reference when differentiate in between udfps & sfps
|
// Note: Update string reference when differentiate in between udfps & sfps
|
||||||
@@ -445,11 +374,10 @@ public class FingerprintEnrollEnrollingUdfpsFragment extends Fragment {
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
case STAGE_GUIDED:
|
case STAGE_GUIDED:
|
||||||
glifLayoutHelper.setHeaderText(
|
mTitleText.setText(R.string.security_settings_fingerprint_enroll_repeat_title);
|
||||||
R.string.security_settings_fingerprint_enroll_repeat_title);
|
|
||||||
if (mIsAccessibilityEnabled || mIllustrationLottie == null) {
|
if (mIsAccessibilityEnabled || mIllustrationLottie == null) {
|
||||||
glifLayoutHelper.setDescriptionText(getString(
|
mSubTitleText.setText(
|
||||||
R.string.security_settings_udfps_enroll_repeat_a11y_message));
|
R.string.security_settings_udfps_enroll_repeat_a11y_message);
|
||||||
} else if (!mHaveShownUdfpsGuideLottie && mIllustrationLottie != null) {
|
} else if (!mHaveShownUdfpsGuideLottie && mIllustrationLottie != null) {
|
||||||
mHaveShownUdfpsGuideLottie = true;
|
mHaveShownUdfpsGuideLottie = true;
|
||||||
mIllustrationLottie.setContentDescription(
|
mIllustrationLottie.setContentDescription(
|
||||||
@@ -460,8 +388,7 @@ public class FingerprintEnrollEnrollingUdfpsFragment extends Fragment {
|
|||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case STAGE_FINGERTIP:
|
case STAGE_FINGERTIP:
|
||||||
glifLayoutHelper.setHeaderText(
|
mTitleText.setText(R.string.security_settings_udfps_enroll_fingertip_title);
|
||||||
R.string.security_settings_udfps_enroll_fingertip_title);
|
|
||||||
if (!mHaveShownUdfpsTipLottie && mIllustrationLottie != null) {
|
if (!mHaveShownUdfpsTipLottie && mIllustrationLottie != null) {
|
||||||
mHaveShownUdfpsTipLottie = true;
|
mHaveShownUdfpsTipLottie = true;
|
||||||
mIllustrationLottie.setContentDescription(
|
mIllustrationLottie.setContentDescription(
|
||||||
@@ -471,8 +398,7 @@ public class FingerprintEnrollEnrollingUdfpsFragment extends Fragment {
|
|||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case STAGE_LEFT_EDGE:
|
case STAGE_LEFT_EDGE:
|
||||||
glifLayoutHelper.setHeaderText(
|
mTitleText.setText(R.string.security_settings_udfps_enroll_left_edge_title);
|
||||||
R.string.security_settings_udfps_enroll_left_edge_title);
|
|
||||||
if (!mHaveShownUdfpsLeftEdgeLottie && mIllustrationLottie != null) {
|
if (!mHaveShownUdfpsLeftEdgeLottie && mIllustrationLottie != null) {
|
||||||
mHaveShownUdfpsLeftEdgeLottie = true;
|
mHaveShownUdfpsLeftEdgeLottie = true;
|
||||||
mIllustrationLottie.setContentDescription(
|
mIllustrationLottie.setContentDescription(
|
||||||
@@ -481,17 +407,15 @@ public class FingerprintEnrollEnrollingUdfpsFragment extends Fragment {
|
|||||||
configureEnrollmentStage("", R.raw.udfps_left_edge_hint_lottie);
|
configureEnrollmentStage("", R.raw.udfps_left_edge_hint_lottie);
|
||||||
} else if (mIllustrationLottie == null) {
|
} else if (mIllustrationLottie == null) {
|
||||||
if (isStageHalfCompleted()) {
|
if (isStageHalfCompleted()) {
|
||||||
glifLayoutHelper.setDescriptionText(getString(
|
mSubTitleText.setText(
|
||||||
R.string.security_settings_fingerprint_enroll_repeat_message));
|
R.string.security_settings_fingerprint_enroll_repeat_message);
|
||||||
} else {
|
} else {
|
||||||
glifLayoutHelper.setDescriptionText(getString(
|
mSubTitleText.setText(R.string.security_settings_udfps_enroll_edge_message);
|
||||||
R.string.security_settings_udfps_enroll_edge_message));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case STAGE_RIGHT_EDGE:
|
case STAGE_RIGHT_EDGE:
|
||||||
glifLayoutHelper.setHeaderText(
|
mTitleText.setText(R.string.security_settings_udfps_enroll_right_edge_title);
|
||||||
R.string.security_settings_udfps_enroll_right_edge_title);
|
|
||||||
if (!mHaveShownUdfpsRightEdgeLottie && mIllustrationLottie != null) {
|
if (!mHaveShownUdfpsRightEdgeLottie && mIllustrationLottie != null) {
|
||||||
mHaveShownUdfpsRightEdgeLottie = true;
|
mHaveShownUdfpsRightEdgeLottie = true;
|
||||||
mIllustrationLottie.setContentDescription(
|
mIllustrationLottie.setContentDescription(
|
||||||
@@ -501,31 +425,22 @@ public class FingerprintEnrollEnrollingUdfpsFragment extends Fragment {
|
|||||||
|
|
||||||
} else if (mIllustrationLottie == null) {
|
} else if (mIllustrationLottie == null) {
|
||||||
if (isStageHalfCompleted()) {
|
if (isStageHalfCompleted()) {
|
||||||
glifLayoutHelper.setDescriptionText(getString(
|
mSubTitleText.setText(
|
||||||
R.string.security_settings_fingerprint_enroll_repeat_message));
|
R.string.security_settings_fingerprint_enroll_repeat_message);
|
||||||
} else {
|
} else {
|
||||||
glifLayoutHelper.setDescriptionText(getString(
|
mSubTitleText.setText(R.string.security_settings_udfps_enroll_edge_message);
|
||||||
R.string.security_settings_udfps_enroll_edge_message));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case STAGE_UNKNOWN:
|
case STAGE_UNKNOWN:
|
||||||
default:
|
default:
|
||||||
// setHeaderText(R.string.security_settings_fingerprint_enroll_udfps_title);
|
mTitleText.setText(R.string.security_settings_fingerprint_enroll_udfps_title);
|
||||||
// Don't use BiometricEnrollBase#setHeaderText, since that invokes setTitle,
|
mSubTitleText.setText(R.string.security_settings_udfps_enroll_start_message);
|
||||||
// which gets announced for a11y upon entering the page. For UDFPS, we want to
|
|
||||||
// announce a different string for a11y upon entering the page.
|
|
||||||
glifLayoutHelper.setHeaderText(
|
|
||||||
R.string.security_settings_fingerprint_enroll_udfps_title);
|
|
||||||
glifLayoutHelper.setDescriptionText(getString(
|
|
||||||
R.string.security_settings_udfps_enroll_start_message));
|
|
||||||
final CharSequence description = getString(
|
final CharSequence description = getString(
|
||||||
R.string.security_settings_udfps_enroll_a11y);
|
R.string.security_settings_udfps_enroll_a11y);
|
||||||
mView.getHeaderTextView().setContentDescription(description);
|
getActivity().setTitle(description);
|
||||||
activity.setTitle(description);
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -538,7 +453,7 @@ public class FingerprintEnrollEnrollingUdfpsFragment extends Fragment {
|
|||||||
return defaultDensity == currentDensity;
|
return defaultDensity == currentDensity;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void updateOrientation(@NonNull GlifLayout glifLayout, int orientation) {
|
private void updateOrientation(@NonNull RelativeLayout content, int orientation) {
|
||||||
switch (orientation) {
|
switch (orientation) {
|
||||||
case Configuration.ORIENTATION_LANDSCAPE: {
|
case Configuration.ORIENTATION_LANDSCAPE: {
|
||||||
mIllustrationLottie = null;
|
mIllustrationLottie = null;
|
||||||
@@ -546,7 +461,7 @@ public class FingerprintEnrollEnrollingUdfpsFragment extends Fragment {
|
|||||||
}
|
}
|
||||||
case Configuration.ORIENTATION_PORTRAIT: {
|
case Configuration.ORIENTATION_PORTRAIT: {
|
||||||
if (mShouldShowLottie) {
|
if (mShouldShowLottie) {
|
||||||
mIllustrationLottie = glifLayout.findViewById(R.id.illustration_lottie);
|
mIllustrationLottie = content.findViewById(R.id.illustration_lottie);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -625,7 +540,7 @@ public class FingerprintEnrollEnrollingUdfpsFragment extends Fragment {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void configureEnrollmentStage(CharSequence description, @RawRes int lottie) {
|
private void configureEnrollmentStage(CharSequence description, @RawRes int lottie) {
|
||||||
new GlifLayoutHelper(getActivity(), mView).setDescriptionText(description);
|
mSubTitleText.setText(description);
|
||||||
LottieCompositionFactory.fromRawRes(getActivity(), lottie)
|
LottieCompositionFactory.fromRawRes(getActivity(), lottie)
|
||||||
.addListener((c) -> {
|
.addListener((c) -> {
|
||||||
mIllustrationLottie.setComposition(c);
|
mIllustrationLottie.setComposition(c);
|
||||||
@@ -634,31 +549,6 @@ public class FingerprintEnrollEnrollingUdfpsFragment extends Fragment {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private void setOnHoverListener(boolean isLandscape, GlifLayout enrollLayout,
|
|
||||||
UdfpsEnrollView udfpsEnrollView) {
|
|
||||||
if (!mIsAccessibilityEnabled) return;
|
|
||||||
|
|
||||||
final Context context = getActivity();
|
|
||||||
final View.OnHoverListener onHoverListener = (v, event) -> {
|
|
||||||
// Map the touch to portrait mode if the device is in
|
|
||||||
// landscape mode.
|
|
||||||
final Point scaledTouch =
|
|
||||||
mUdfpsUtils.getTouchInNativeCoordinates(event.getPointerId(0),
|
|
||||||
event, udfpsEnrollView.getOverlayParams());
|
|
||||||
|
|
||||||
final String theStr = mUdfpsUtils.onTouchOutsideOfSensorArea(
|
|
||||||
mEnrollingViewModel.isTouchExplorationEnabled(), context,
|
|
||||||
scaledTouch.x, scaledTouch.y, udfpsEnrollView.getOverlayParams());
|
|
||||||
if (theStr != null) {
|
|
||||||
v.announceForAccessibility(theStr);
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
};
|
|
||||||
|
|
||||||
enrollLayout.findManagedViewById(isLandscape ? R.id.sud_landscape_content_area
|
|
||||||
: R.id.sud_layout_content).setOnHoverListener(onHoverListener);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void onEnrollmentProgressChange(@NonNull EnrollmentProgress progress) {
|
private void onEnrollmentProgressChange(@NonNull EnrollmentProgress progress) {
|
||||||
updateProgress(true /* animate */, progress);
|
updateProgress(true /* animate */, progress);
|
||||||
|
|
||||||
@@ -678,34 +568,43 @@ public class FingerprintEnrollEnrollingUdfpsFragment extends Fragment {
|
|||||||
private void onEnrollmentHelp(int helpMsgId, CharSequence helpString) {
|
private void onEnrollmentHelp(int helpMsgId, CharSequence helpString) {
|
||||||
if (!TextUtils.isEmpty(helpString)) {
|
if (!TextUtils.isEmpty(helpString)) {
|
||||||
showError(helpString);
|
showError(helpString);
|
||||||
mUdfpsEnrollHelper.onEnrollmentHelp();
|
mUdfpsEnrollView.onEnrollmentHelp();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void onAcquired(boolean isAcquiredGood) {
|
private void onAcquired(boolean isAcquiredGood) {
|
||||||
if (mUdfpsEnrollHelper != null) {
|
if (mUdfpsEnrollView != null) {
|
||||||
mUdfpsEnrollHelper.onAcquired(isAcquiredGood);
|
mUdfpsEnrollView.onAcquired(isAcquiredGood);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void onPointerDown(int sensorId) {
|
private void onPointerDown(int sensorId) {
|
||||||
if (mUdfpsEnrollHelper != null) {
|
if (mUdfpsEnrollView != null) {
|
||||||
mUdfpsEnrollHelper.onPointerDown(sensorId);
|
mUdfpsEnrollView.onPointerDown(sensorId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void onPointerUp(int sensorId) {
|
private void onPointerUp(int sensorId) {
|
||||||
if (mUdfpsEnrollHelper != null) {
|
if (mUdfpsEnrollView != null) {
|
||||||
mUdfpsEnrollHelper.onPointerUp(sensorId);
|
mUdfpsEnrollView.onPointerUp(sensorId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void showError(CharSequence error) {
|
private void showError(CharSequence error) {
|
||||||
mView.setHeaderText(error);
|
mTitleText.setText(error);
|
||||||
mView.getHeaderTextView().setContentDescription(error);
|
mTitleText.setContentDescription(error);
|
||||||
new GlifLayoutHelper(getActivity(), mView).setDescriptionText("");
|
mSubTitleText.setContentDescription("");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private float convertDpToPixel(float dp, Context context) {
|
||||||
|
float px = dp * getDensity(context);
|
||||||
|
return px;
|
||||||
|
}
|
||||||
|
|
||||||
|
private float getDensity(Context context) {
|
||||||
|
DisplayMetrics metrics = context.getResources().getDisplayMetrics();
|
||||||
|
return metrics.density;
|
||||||
|
}
|
||||||
|
|
||||||
private final Runnable mShowDialogRunnable = new Runnable() {
|
private final Runnable mShowDialogRunnable = new Runnable() {
|
||||||
@Override
|
@Override
|
||||||
|
@@ -0,0 +1,436 @@
|
|||||||
|
/*
|
||||||
|
* 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.widget;
|
||||||
|
|
||||||
|
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.hardware.fingerprint.FingerprintManager;
|
||||||
|
import android.os.Build;
|
||||||
|
import android.os.UserHandle;
|
||||||
|
import android.provider.Settings;
|
||||||
|
import android.util.AttributeSet;
|
||||||
|
import android.util.Log;
|
||||||
|
import android.util.PathParser;
|
||||||
|
import android.util.TypedValue;
|
||||||
|
import android.view.accessibility.AccessibilityManager;
|
||||||
|
import android.view.animation.AccelerateDecelerateInterpolator;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
|
||||||
|
import com.android.settings.R;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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;
|
||||||
|
private static final float SCALE = 0.5f;
|
||||||
|
private static final String SCALE_OVERRIDE =
|
||||||
|
"com.android.systemui.biometrics.UdfpsEnrollHelper.scale";
|
||||||
|
private static final String NEW_COORDS_OVERRIDE =
|
||||||
|
"com.android.systemui.biometrics.UdfpsNewCoords";
|
||||||
|
|
||||||
|
@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;
|
||||||
|
|
||||||
|
// 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;
|
||||||
|
|
||||||
|
private int mTotalSteps = -1;
|
||||||
|
private int mRemainingSteps = -1;
|
||||||
|
private int mLocationsEnrolled = 0;
|
||||||
|
private int mCenterTouchCount = 0;
|
||||||
|
|
||||||
|
private FingerprintManager mFingerprintManager;
|
||||||
|
|
||||||
|
private boolean mAccessibilityEnabled;
|
||||||
|
private Context mContext;
|
||||||
|
private final List<PointF> mGuidedEnrollmentPoints;
|
||||||
|
|
||||||
|
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) {
|
||||||
|
}
|
||||||
|
};
|
||||||
|
mContext = context;
|
||||||
|
mFingerprintManager = context.getSystemService(FingerprintManager.class);
|
||||||
|
final AccessibilityManager am = context.getSystemService(AccessibilityManager.class);
|
||||||
|
mAccessibilityEnabled = am.isEnabled();
|
||||||
|
mGuidedEnrollmentPoints = new ArrayList<>();
|
||||||
|
initEnrollPoint(context);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 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 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(final int remaining, final int totalSteps) {
|
||||||
|
if (mTotalSteps == -1) {
|
||||||
|
mTotalSteps = totalSteps;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (remaining != mRemainingSteps) {
|
||||||
|
mLocationsEnrolled++;
|
||||||
|
if (isCenterEnrollmentStage()) {
|
||||||
|
mCenterTouchCount++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
mRemainingSteps = remaining;
|
||||||
|
|
||||||
|
if (!isCenterEnrollmentStage()) {
|
||||||
|
if (mTargetAnimatorSet != null && mTargetAnimatorSet.isRunning()) {
|
||||||
|
mTargetAnimatorSet.end();
|
||||||
|
}
|
||||||
|
|
||||||
|
final PointF point = 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 (!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 = 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 = 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();
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isCenterEnrollmentStage() {
|
||||||
|
if (mTotalSteps == -1 || mRemainingSteps == -1) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return mTotalSteps - mRemainingSteps < getStageThresholdSteps(mTotalSteps, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
private int getStageThresholdSteps(int totalSteps, int stageIndex) {
|
||||||
|
return Math.round(totalSteps * mFingerprintManager.getEnrollStageThreshold(stageIndex));
|
||||||
|
}
|
||||||
|
|
||||||
|
private 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);
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isTipEnrollmentStage() {
|
||||||
|
if (mTotalSteps == -1 || mRemainingSteps == -1) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
final int progressSteps = mTotalSteps - mRemainingSteps;
|
||||||
|
return progressSteps >= getStageThresholdSteps(mTotalSteps, 1)
|
||||||
|
&& progressSteps < getStageThresholdSteps(mTotalSteps, 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isEdgeEnrollmentStage() {
|
||||||
|
if (mTotalSteps == -1 || mRemainingSteps == -1) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return mTotalSteps - mRemainingSteps >= getStageThresholdSteps(mTotalSteps, 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void initEnrollPoint(Context context) {
|
||||||
|
// 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));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@@ -0,0 +1,424 @@
|
|||||||
|
/*
|
||||||
|
* 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.widget;
|
||||||
|
|
||||||
|
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.internal.annotations.VisibleForTesting;
|
||||||
|
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;
|
||||||
|
@VisibleForTesting
|
||||||
|
@NonNull
|
||||||
|
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(final int remaining, final int totalSteps) {
|
||||||
|
android.util.Log.e(TAG, "remaining =" + remaining);
|
||||||
|
android.util.Log.e(TAG, "totalSteps =" + 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,260 @@
|
|||||||
|
/*
|
||||||
|
* 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.widget;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.graphics.Rect;
|
||||||
|
import android.graphics.RectF;
|
||||||
|
import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
|
||||||
|
import android.os.Handler;
|
||||||
|
import android.os.Looper;
|
||||||
|
import android.util.AttributeSet;
|
||||||
|
import android.util.RotationUtils;
|
||||||
|
import android.view.DisplayInfo;
|
||||||
|
import android.view.Surface;
|
||||||
|
import android.view.ViewGroup;
|
||||||
|
import android.view.ViewTreeObserver;
|
||||||
|
import android.widget.FrameLayout;
|
||||||
|
import android.widget.ImageView;
|
||||||
|
import android.widget.RelativeLayout;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
|
||||||
|
import com.android.settings.R;
|
||||||
|
import com.android.settingslib.udfps.UdfpsOverlayParams;
|
||||||
|
import com.android.settingslib.udfps.UdfpsUtils;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* View corresponding with udfps_enroll_view.xml
|
||||||
|
*/
|
||||||
|
public class UdfpsEnrollView extends FrameLayout {
|
||||||
|
private static final String TAG = "UdfpsEnrollView";
|
||||||
|
@NonNull
|
||||||
|
private final UdfpsEnrollDrawable mFingerprintDrawable;
|
||||||
|
@NonNull
|
||||||
|
private final UdfpsEnrollProgressBarDrawable mFingerprintProgressDrawable;
|
||||||
|
@NonNull
|
||||||
|
private final Handler mHandler;
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
private ImageView mFingerprintProgressView;
|
||||||
|
private UdfpsUtils mUdfpsUtils;
|
||||||
|
|
||||||
|
private int mProgressBarRadius;
|
||||||
|
|
||||||
|
private Rect mSensorRect;
|
||||||
|
private UdfpsOverlayParams mOverlayParams;
|
||||||
|
private FingerprintSensorPropertiesInternal mSensorProperties;
|
||||||
|
|
||||||
|
private int mTotalSteps = -1;
|
||||||
|
private int mRemainingSteps = -1;
|
||||||
|
private int mLocationsEnrolled = 0;
|
||||||
|
private int mCenterTouchCount = 0;
|
||||||
|
|
||||||
|
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());
|
||||||
|
mUdfpsUtils = new UdfpsUtils();
|
||||||
|
}
|
||||||
|
|
||||||
|
@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);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Receive enroll progress information from FingerprintEnrollEnrollingUdfpsFragment
|
||||||
|
*/
|
||||||
|
public void onEnrollmentProgress(int remaining, int totalSteps) {
|
||||||
|
if (mTotalSteps == -1) {
|
||||||
|
mTotalSteps = totalSteps;
|
||||||
|
}
|
||||||
|
mRemainingSteps = remaining;
|
||||||
|
mHandler.post(() -> {
|
||||||
|
mFingerprintProgressDrawable.onEnrollmentProgress(remaining, totalSteps);
|
||||||
|
mFingerprintDrawable.onEnrollmentProgress(remaining, totalSteps);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Receive enroll help information from FingerprintEnrollEnrollingUdfpsFragment
|
||||||
|
*/
|
||||||
|
public void onEnrollmentHelp() {
|
||||||
|
mHandler.post(
|
||||||
|
() -> mFingerprintProgressDrawable.onEnrollmentHelp(mRemainingSteps, mTotalSteps));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Receive onAcquired from FingerprintEnrollEnrollingUdfpsFragment
|
||||||
|
*/
|
||||||
|
public void onAcquired(boolean isAcquiredGood) {
|
||||||
|
final boolean animateIfLastStepGood =
|
||||||
|
isAcquiredGood && (mRemainingSteps <= 2 && mRemainingSteps >= 0);
|
||||||
|
mHandler.post(() -> {
|
||||||
|
onFingerUp();
|
||||||
|
if (animateIfLastStepGood) mFingerprintProgressDrawable.onLastStepAcquired();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Receive onPointerDown from FingerprintEnrollEnrollingUdfpsFragment
|
||||||
|
*/
|
||||||
|
public void onPointerDown(int sensorId) {
|
||||||
|
onFingerDown();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Receive onPointerUp from FingerprintEnrollEnrollingUdfpsFragment
|
||||||
|
*/
|
||||||
|
public void onPointerUp(int sensorId) {
|
||||||
|
onFingerUp();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* setup SensorProperties
|
||||||
|
*/
|
||||||
|
public void setSensorProperties(FingerprintSensorPropertiesInternal properties) {
|
||||||
|
mSensorProperties = properties;
|
||||||
|
((ViewGroup) getParent()).getViewTreeObserver().addOnDrawListener(
|
||||||
|
new ViewTreeObserver.OnDrawListener() {
|
||||||
|
@Override
|
||||||
|
public void onDraw() {
|
||||||
|
updateOverlayParams();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void onSensorRectUpdated() {
|
||||||
|
updateDimensions();
|
||||||
|
|
||||||
|
// 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.
|
||||||
|
final Rect rotatedBounds = new Rect(mOverlayParams.getSensorBounds());
|
||||||
|
int rotation = mOverlayParams.getRotation();
|
||||||
|
if (rotation == Surface.ROTATION_90 || rotation == Surface.ROTATION_270) {
|
||||||
|
RotationUtils.rotateBounds(
|
||||||
|
rotatedBounds,
|
||||||
|
mOverlayParams.getNaturalDisplayWidth(),
|
||||||
|
mOverlayParams.getNaturalDisplayHeight(),
|
||||||
|
rotation
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
RelativeLayout parent = ((RelativeLayout) getParent());
|
||||||
|
final int[] coords = parent.getLocationOnScreen();
|
||||||
|
final int parentLeft = coords[0];
|
||||||
|
final int parentTop = coords[1];
|
||||||
|
final int parentRight = parentLeft + parent.getWidth();
|
||||||
|
final int parentBottom = parentTop + parent.getHeight();
|
||||||
|
|
||||||
|
|
||||||
|
RelativeLayout.LayoutParams params = new RelativeLayout.LayoutParams(getWidth(),
|
||||||
|
getHeight());
|
||||||
|
if (rotation == Surface.ROTATION_0 || rotation == Surface.ROTATION_180) {
|
||||||
|
params.addRule(RelativeLayout.ALIGN_PARENT_TOP);
|
||||||
|
params.addRule(RelativeLayout.ALIGN_PARENT_RIGHT);
|
||||||
|
params.rightMargin = parentRight - rotatedBounds.right - getPaddingX();
|
||||||
|
params.topMargin = rotatedBounds.top - parentTop - getPaddingY();
|
||||||
|
} else {
|
||||||
|
if (rotation == Surface.ROTATION_90) {
|
||||||
|
params.addRule(RelativeLayout.ALIGN_PARENT_BOTTOM);
|
||||||
|
params.addRule(RelativeLayout.ALIGN_PARENT_RIGHT);
|
||||||
|
params.rightMargin = parentRight - rotatedBounds.right - getPaddingX();
|
||||||
|
params.bottomMargin = parentBottom - rotatedBounds.bottom - getPaddingY();
|
||||||
|
} else {
|
||||||
|
params.addRule(RelativeLayout.ALIGN_PARENT_BOTTOM);
|
||||||
|
params.addRule(RelativeLayout.ALIGN_PARENT_LEFT);
|
||||||
|
params.bottomMargin = parentBottom - rotatedBounds.bottom - getPaddingY();
|
||||||
|
params.leftMargin = rotatedBounds.left - parentLeft - getPaddingX();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
params.height = rotatedBounds.height() + 2 * getPaddingX();
|
||||||
|
params.width = rotatedBounds.width() + 2 * getPaddingY();
|
||||||
|
setLayoutParams(params);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void onFingerDown() {
|
||||||
|
mFingerprintDrawable.setShouldSkipDraw(true);
|
||||||
|
mFingerprintDrawable.invalidateSelf();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void onFingerUp() {
|
||||||
|
mFingerprintDrawable.setShouldSkipDraw(false);
|
||||||
|
mFingerprintDrawable.invalidateSelf();
|
||||||
|
}
|
||||||
|
|
||||||
|
private int getPaddingX() {
|
||||||
|
return mProgressBarRadius;
|
||||||
|
}
|
||||||
|
|
||||||
|
private int getPaddingY() {
|
||||||
|
return mProgressBarRadius;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateOverlayParams() {
|
||||||
|
|
||||||
|
if (mSensorProperties == null) {
|
||||||
|
android.util.Log.e(TAG, "There is no sensor info!");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
DisplayInfo displayInfo = new DisplayInfo();
|
||||||
|
getDisplay().getDisplayInfo(displayInfo);
|
||||||
|
Rect udfpsBounds = mSensorProperties.getLocation().getRect();
|
||||||
|
float scaleFactor = mUdfpsUtils.getScaleFactor(displayInfo);
|
||||||
|
udfpsBounds.scale(scaleFactor);
|
||||||
|
|
||||||
|
final Rect overlayBounds = new Rect(
|
||||||
|
0, /* left */
|
||||||
|
displayInfo.getNaturalHeight() / 2, /* top */
|
||||||
|
displayInfo.getNaturalWidth(), /* right */
|
||||||
|
displayInfo.getNaturalHeight() /* botom */);
|
||||||
|
|
||||||
|
mOverlayParams = new UdfpsOverlayParams(
|
||||||
|
udfpsBounds,
|
||||||
|
overlayBounds,
|
||||||
|
displayInfo.getNaturalWidth(),
|
||||||
|
displayInfo.getNaturalHeight(),
|
||||||
|
scaleFactor,
|
||||||
|
displayInfo.rotation);
|
||||||
|
|
||||||
|
|
||||||
|
post(() -> {
|
||||||
|
mProgressBarRadius =
|
||||||
|
(int) (mOverlayParams.getScaleFactor() * getContext().getResources().getInteger(
|
||||||
|
R.integer.config_udfpsEnrollProgressBar));
|
||||||
|
mSensorRect = new Rect(mOverlayParams.getSensorBounds());
|
||||||
|
|
||||||
|
onSensorRectUpdated();
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
Reference in New Issue
Block a user