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.content.Context;
|
||||
import android.content.res.Configuration;
|
||||
import android.graphics.Point;
|
||||
import android.graphics.Rect;
|
||||
import android.graphics.drawable.Animatable2;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.hardware.fingerprint.FingerprintManager;
|
||||
import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
|
||||
import android.os.Bundle;
|
||||
import android.text.TextUtils;
|
||||
import android.util.DisplayMetrics;
|
||||
import android.util.Log;
|
||||
import android.view.DisplayInfo;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.Surface;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.animation.AnimationUtils;
|
||||
import android.view.animation.Interpolator;
|
||||
import android.widget.FrameLayout;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.Button;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.RelativeLayout;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
@@ -52,25 +47,16 @@ import androidx.transition.TransitionSet;
|
||||
|
||||
import com.android.settings.R;
|
||||
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.EnrollmentStatusMessage;
|
||||
import com.android.settings.biometrics2.ui.viewmodel.DeviceRotationViewModel;
|
||||
import com.android.settings.biometrics2.ui.viewmodel.FingerprintEnrollEnrollingViewModel;
|
||||
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.udfps.UdfpsOverlayParams;
|
||||
import com.android.settingslib.udfps.UdfpsUtils;
|
||||
|
||||
import com.airbnb.lottie.LottieAnimationView;
|
||||
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
|
||||
@@ -92,9 +78,6 @@ public class FingerprintEnrollEnrollingUdfpsFragment extends Fragment {
|
||||
private DeviceRotationViewModel mRotationViewModel;
|
||||
private FingerprintEnrollProgressViewModel mProgressViewModel;
|
||||
|
||||
private Interpolator mFastOutSlowInInterpolator;
|
||||
private Interpolator mLinearOutSlowInInterpolator;
|
||||
private Interpolator mFastOutLinearInInterpolator;
|
||||
private boolean mAnimationCancelled;
|
||||
|
||||
private LottieAnimationView mIllustrationLottie;
|
||||
@@ -104,9 +87,13 @@ public class FingerprintEnrollEnrollingUdfpsFragment extends Fragment {
|
||||
private boolean mHaveShownUdfpsCenterLottie;
|
||||
private boolean mHaveShownUdfpsGuideLottie;
|
||||
|
||||
private GlifLayout mView;
|
||||
private RelativeLayout mView;
|
||||
private ImageView mIcon;
|
||||
private TextView mErrorText;
|
||||
private FooterBarMixin mFooterBarMixin;
|
||||
private TextView mTitleText;
|
||||
private TextView mSubTitleText;
|
||||
private Button mSkipBtn;
|
||||
private UdfpsEnrollView mUdfpsEnrollView;
|
||||
|
||||
private boolean mShouldShowLottie;
|
||||
private boolean mIsAccessibilityEnabled;
|
||||
@@ -145,13 +132,6 @@ public class FingerprintEnrollEnrollingUdfpsFragment extends Fragment {
|
||||
|
||||
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
|
||||
public void onAttach(@NonNull Context context) {
|
||||
@@ -200,10 +180,6 @@ public class FingerprintEnrollEnrollingUdfpsFragment extends Fragment {
|
||||
super.onCreate(savedInstanceState);
|
||||
mEnrollingViewModel.restoreSavedState(savedInstanceState);
|
||||
mIsAccessibilityEnabled = mEnrollingViewModel.isAccessibilityEnabled();
|
||||
mUdfpsUtils = new UdfpsUtils();
|
||||
mUdfpsEnrollHelper = new UdfpsEnrollHelper(getActivity(), getActivity().getSystemService(
|
||||
FingerprintManager.class
|
||||
));
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -216,95 +192,85 @@ public class FingerprintEnrollEnrollingUdfpsFragment extends Fragment {
|
||||
public View onCreateView(LayoutInflater inflater, ViewGroup container,
|
||||
Bundle savedInstanceState) {
|
||||
mView = initUdfpsLayout(inflater, container);
|
||||
|
||||
return mView;
|
||||
}
|
||||
|
||||
private GlifLayout initUdfpsLayout(LayoutInflater inflater, ViewGroup container) {
|
||||
final GlifLayout containView = (GlifLayout) inflater.inflate(
|
||||
R.layout.udfps_enroll_enrolling, 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);
|
||||
}
|
||||
private RelativeLayout initUdfpsLayout(LayoutInflater inflater, ViewGroup container) {
|
||||
final RelativeLayout containView = (RelativeLayout) inflater.inflate(
|
||||
R.layout.udfps_enroll_enrolling_v2, container, false);
|
||||
|
||||
final Activity activity = getActivity();
|
||||
new GlifLayoutHelper(activity, containView).setDescriptionText(
|
||||
getString(R.string.security_settings_udfps_enroll_start_message));
|
||||
|
||||
mIcon = containView.findViewById(R.id.sud_layout_icon);
|
||||
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();
|
||||
boolean isLandscape = BiometricUtils.isReverseLandscape(activity)
|
||||
|| BiometricUtils.isLandscape(activity);
|
||||
updateOrientation(containView, (isLandscape
|
||||
? 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(
|
||||
activity, android.R.interpolator.fast_out_slow_in);
|
||||
mLinearOutSlowInInterpolator = AnimationUtils.loadInterpolator(
|
||||
activity, android.R.interpolator.linear_out_slow_in);
|
||||
mFastOutLinearInInterpolator = AnimationUtils.loadInterpolator(
|
||||
activity, android.R.interpolator.fast_out_linear_in);
|
||||
final int rotation = mRotationViewModel.getLiveData().getValue();
|
||||
if (rotation == Surface.ROTATION_270) {
|
||||
RelativeLayout.LayoutParams iconLP = new RelativeLayout.LayoutParams(-2, -2);
|
||||
iconLP.addRule(RelativeLayout.ALIGN_PARENT_TOP);
|
||||
iconLP.addRule(RelativeLayout.END_OF, R.id.udfps_animation_view);
|
||||
iconLP.topMargin = (int) convertDpToPixel(76.64f, activity);
|
||||
iconLP.leftMargin = (int) convertDpToPixel(151.54f, activity);
|
||||
mIcon.setLayoutParams(iconLP);
|
||||
|
||||
final LinearLayout buttonContainer = mFooterBarMixin.getButtonContainer();
|
||||
View spaceView = null;
|
||||
for (int i = 0; i < buttonContainer.getChildCount(); i++) {
|
||||
if (!(buttonContainer.getChildAt(i) instanceof FooterActionButton)) {
|
||||
spaceView = buttonContainer.getChildAt(i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (spaceView != null) {
|
||||
spaceView.setVisibility(View.GONE);
|
||||
buttonContainer.getLayoutParams().width = ViewGroup.LayoutParams.WRAP_CONTENT;
|
||||
RelativeLayout.LayoutParams titleLP = new RelativeLayout.LayoutParams(-1, -2);
|
||||
titleLP.addRule(RelativeLayout.ALIGN_PARENT_TOP);
|
||||
titleLP.addRule(RelativeLayout.END_OF, R.id.udfps_animation_view);
|
||||
titleLP.topMargin = (int) convertDpToPixel(138f, activity);
|
||||
titleLP.leftMargin = (int) convertDpToPixel(144f, activity);
|
||||
mTitleText.setLayoutParams(titleLP);
|
||||
|
||||
RelativeLayout.LayoutParams subtitleLP = new RelativeLayout.LayoutParams(-1, -2);
|
||||
subtitleLP.addRule(RelativeLayout.ALIGN_PARENT_TOP);
|
||||
subtitleLP.addRule(RelativeLayout.END_OF, R.id.udfps_animation_view);
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -358,8 +324,9 @@ public class FingerprintEnrollEnrollingUdfpsFragment extends Fragment {
|
||||
|
||||
final int progress = getProgress(enrollmentProgress);
|
||||
|
||||
mUdfpsEnrollHelper.onEnrollmentProgress(enrollmentProgress.getSteps(),
|
||||
enrollmentProgress.getRemaining());
|
||||
|
||||
mUdfpsEnrollView.onEnrollmentProgress(enrollmentProgress.getRemaining(),
|
||||
enrollmentProgress.getSteps());
|
||||
|
||||
if (animate) {
|
||||
animateProgress(progress);
|
||||
@@ -382,38 +349,6 @@ public class FingerprintEnrollEnrollingUdfpsFragment extends Fragment {
|
||||
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) {
|
||||
// UDFPS animations are owned by SystemUI
|
||||
if (progress >= PROGRESS_BAR_MAX) {
|
||||
@@ -423,17 +358,11 @@ public class FingerprintEnrollEnrollingUdfpsFragment extends Fragment {
|
||||
}
|
||||
|
||||
private void updateTitleAndDescription() {
|
||||
|
||||
final Activity activity = getActivity();
|
||||
final GlifLayoutHelper glifLayoutHelper = new GlifLayoutHelper(activity, mView);
|
||||
|
||||
switch (getCurrentStage()) {
|
||||
case STAGE_CENTER:
|
||||
glifLayoutHelper.setHeaderText(
|
||||
R.string.security_settings_fingerprint_enroll_repeat_title);
|
||||
mTitleText.setText(R.string.security_settings_fingerprint_enroll_repeat_title);
|
||||
if (mIsAccessibilityEnabled || mIllustrationLottie == null) {
|
||||
glifLayoutHelper.setDescriptionText(getString(
|
||||
R.string.security_settings_udfps_enroll_start_message));
|
||||
mSubTitleText.setText(R.string.security_settings_udfps_enroll_start_message);
|
||||
} else if (!mHaveShownUdfpsCenterLottie && mIllustrationLottie != null) {
|
||||
mHaveShownUdfpsCenterLottie = true;
|
||||
// Note: Update string reference when differentiate in between udfps & sfps
|
||||
@@ -445,11 +374,10 @@ public class FingerprintEnrollEnrollingUdfpsFragment extends Fragment {
|
||||
break;
|
||||
|
||||
case STAGE_GUIDED:
|
||||
glifLayoutHelper.setHeaderText(
|
||||
R.string.security_settings_fingerprint_enroll_repeat_title);
|
||||
mTitleText.setText(R.string.security_settings_fingerprint_enroll_repeat_title);
|
||||
if (mIsAccessibilityEnabled || mIllustrationLottie == null) {
|
||||
glifLayoutHelper.setDescriptionText(getString(
|
||||
R.string.security_settings_udfps_enroll_repeat_a11y_message));
|
||||
mSubTitleText.setText(
|
||||
R.string.security_settings_udfps_enroll_repeat_a11y_message);
|
||||
} else if (!mHaveShownUdfpsGuideLottie && mIllustrationLottie != null) {
|
||||
mHaveShownUdfpsGuideLottie = true;
|
||||
mIllustrationLottie.setContentDescription(
|
||||
@@ -460,8 +388,7 @@ public class FingerprintEnrollEnrollingUdfpsFragment extends Fragment {
|
||||
}
|
||||
break;
|
||||
case STAGE_FINGERTIP:
|
||||
glifLayoutHelper.setHeaderText(
|
||||
R.string.security_settings_udfps_enroll_fingertip_title);
|
||||
mTitleText.setText(R.string.security_settings_udfps_enroll_fingertip_title);
|
||||
if (!mHaveShownUdfpsTipLottie && mIllustrationLottie != null) {
|
||||
mHaveShownUdfpsTipLottie = true;
|
||||
mIllustrationLottie.setContentDescription(
|
||||
@@ -471,8 +398,7 @@ public class FingerprintEnrollEnrollingUdfpsFragment extends Fragment {
|
||||
}
|
||||
break;
|
||||
case STAGE_LEFT_EDGE:
|
||||
glifLayoutHelper.setHeaderText(
|
||||
R.string.security_settings_udfps_enroll_left_edge_title);
|
||||
mTitleText.setText(R.string.security_settings_udfps_enroll_left_edge_title);
|
||||
if (!mHaveShownUdfpsLeftEdgeLottie && mIllustrationLottie != null) {
|
||||
mHaveShownUdfpsLeftEdgeLottie = true;
|
||||
mIllustrationLottie.setContentDescription(
|
||||
@@ -481,17 +407,15 @@ public class FingerprintEnrollEnrollingUdfpsFragment extends Fragment {
|
||||
configureEnrollmentStage("", R.raw.udfps_left_edge_hint_lottie);
|
||||
} else if (mIllustrationLottie == null) {
|
||||
if (isStageHalfCompleted()) {
|
||||
glifLayoutHelper.setDescriptionText(getString(
|
||||
R.string.security_settings_fingerprint_enroll_repeat_message));
|
||||
mSubTitleText.setText(
|
||||
R.string.security_settings_fingerprint_enroll_repeat_message);
|
||||
} else {
|
||||
glifLayoutHelper.setDescriptionText(getString(
|
||||
R.string.security_settings_udfps_enroll_edge_message));
|
||||
mSubTitleText.setText(R.string.security_settings_udfps_enroll_edge_message);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case STAGE_RIGHT_EDGE:
|
||||
glifLayoutHelper.setHeaderText(
|
||||
R.string.security_settings_udfps_enroll_right_edge_title);
|
||||
mTitleText.setText(R.string.security_settings_udfps_enroll_right_edge_title);
|
||||
if (!mHaveShownUdfpsRightEdgeLottie && mIllustrationLottie != null) {
|
||||
mHaveShownUdfpsRightEdgeLottie = true;
|
||||
mIllustrationLottie.setContentDescription(
|
||||
@@ -501,31 +425,22 @@ public class FingerprintEnrollEnrollingUdfpsFragment extends Fragment {
|
||||
|
||||
} else if (mIllustrationLottie == null) {
|
||||
if (isStageHalfCompleted()) {
|
||||
glifLayoutHelper.setDescriptionText(getString(
|
||||
R.string.security_settings_fingerprint_enroll_repeat_message));
|
||||
mSubTitleText.setText(
|
||||
R.string.security_settings_fingerprint_enroll_repeat_message);
|
||||
} else {
|
||||
glifLayoutHelper.setDescriptionText(getString(
|
||||
R.string.security_settings_udfps_enroll_edge_message));
|
||||
mSubTitleText.setText(R.string.security_settings_udfps_enroll_edge_message);
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case STAGE_UNKNOWN:
|
||||
default:
|
||||
// setHeaderText(R.string.security_settings_fingerprint_enroll_udfps_title);
|
||||
// Don't use BiometricEnrollBase#setHeaderText, since that invokes setTitle,
|
||||
// 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));
|
||||
mTitleText.setText(R.string.security_settings_fingerprint_enroll_udfps_title);
|
||||
mSubTitleText.setText(R.string.security_settings_udfps_enroll_start_message);
|
||||
final CharSequence description = getString(
|
||||
R.string.security_settings_udfps_enroll_a11y);
|
||||
mView.getHeaderTextView().setContentDescription(description);
|
||||
activity.setTitle(description);
|
||||
getActivity().setTitle(description);
|
||||
break;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -538,7 +453,7 @@ public class FingerprintEnrollEnrollingUdfpsFragment extends Fragment {
|
||||
return defaultDensity == currentDensity;
|
||||
}
|
||||
|
||||
private void updateOrientation(@NonNull GlifLayout glifLayout, int orientation) {
|
||||
private void updateOrientation(@NonNull RelativeLayout content, int orientation) {
|
||||
switch (orientation) {
|
||||
case Configuration.ORIENTATION_LANDSCAPE: {
|
||||
mIllustrationLottie = null;
|
||||
@@ -546,7 +461,7 @@ public class FingerprintEnrollEnrollingUdfpsFragment extends Fragment {
|
||||
}
|
||||
case Configuration.ORIENTATION_PORTRAIT: {
|
||||
if (mShouldShowLottie) {
|
||||
mIllustrationLottie = glifLayout.findViewById(R.id.illustration_lottie);
|
||||
mIllustrationLottie = content.findViewById(R.id.illustration_lottie);
|
||||
}
|
||||
break;
|
||||
}
|
||||
@@ -625,7 +540,7 @@ public class FingerprintEnrollEnrollingUdfpsFragment extends Fragment {
|
||||
}
|
||||
|
||||
private void configureEnrollmentStage(CharSequence description, @RawRes int lottie) {
|
||||
new GlifLayoutHelper(getActivity(), mView).setDescriptionText(description);
|
||||
mSubTitleText.setText(description);
|
||||
LottieCompositionFactory.fromRawRes(getActivity(), lottie)
|
||||
.addListener((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) {
|
||||
updateProgress(true /* animate */, progress);
|
||||
|
||||
@@ -678,34 +568,43 @@ public class FingerprintEnrollEnrollingUdfpsFragment extends Fragment {
|
||||
private void onEnrollmentHelp(int helpMsgId, CharSequence helpString) {
|
||||
if (!TextUtils.isEmpty(helpString)) {
|
||||
showError(helpString);
|
||||
mUdfpsEnrollHelper.onEnrollmentHelp();
|
||||
mUdfpsEnrollView.onEnrollmentHelp();
|
||||
}
|
||||
}
|
||||
|
||||
private void onAcquired(boolean isAcquiredGood) {
|
||||
if (mUdfpsEnrollHelper != null) {
|
||||
mUdfpsEnrollHelper.onAcquired(isAcquiredGood);
|
||||
if (mUdfpsEnrollView != null) {
|
||||
mUdfpsEnrollView.onAcquired(isAcquiredGood);
|
||||
}
|
||||
}
|
||||
|
||||
private void onPointerDown(int sensorId) {
|
||||
if (mUdfpsEnrollHelper != null) {
|
||||
mUdfpsEnrollHelper.onPointerDown(sensorId);
|
||||
if (mUdfpsEnrollView != null) {
|
||||
mUdfpsEnrollView.onPointerDown(sensorId);
|
||||
}
|
||||
}
|
||||
|
||||
private void onPointerUp(int sensorId) {
|
||||
if (mUdfpsEnrollHelper != null) {
|
||||
mUdfpsEnrollHelper.onPointerUp(sensorId);
|
||||
if (mUdfpsEnrollView != null) {
|
||||
mUdfpsEnrollView.onPointerUp(sensorId);
|
||||
}
|
||||
}
|
||||
|
||||
private void showError(CharSequence error) {
|
||||
mView.setHeaderText(error);
|
||||
mView.getHeaderTextView().setContentDescription(error);
|
||||
new GlifLayoutHelper(getActivity(), mView).setDescriptionText("");
|
||||
mTitleText.setText(error);
|
||||
mTitleText.setContentDescription(error);
|
||||
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() {
|
||||
@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