1 /*
2  * Copyright (C) 2021 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.systemui.biometrics;
18 
19 import android.annotation.NonNull;
20 import android.annotation.Nullable;
21 import android.content.Context;
22 import android.graphics.PointF;
23 import android.hardware.biometrics.BiometricOverlayConstants;
24 import android.hardware.fingerprint.FingerprintManager;
25 import android.os.Build;
26 import android.os.UserHandle;
27 import android.provider.Settings;
28 import android.util.Log;
29 import android.util.TypedValue;
30 import android.view.accessibility.AccessibilityManager;
31 
32 import java.util.ArrayList;
33 import java.util.List;
34 
35 /**
36  * Helps keep track of enrollment state and animates the progress bar accordingly.
37  */
38 public class UdfpsEnrollHelper {
39     private static final String TAG = "UdfpsEnrollHelper";
40 
41     private static final String SCALE_OVERRIDE =
42             "com.android.systemui.biometrics.UdfpsEnrollHelper.scale";
43     private static final float SCALE = 0.5f;
44 
45     private static final String NEW_COORDS_OVERRIDE =
46             "com.android.systemui.biometrics.UdfpsNewCoords";
47 
48     interface Listener {
onEnrollmentProgress(int remaining, int totalSteps)49         void onEnrollmentProgress(int remaining, int totalSteps);
onEnrollmentHelp(int remaining, int totalSteps)50         void onEnrollmentHelp(int remaining, int totalSteps);
onLastStepAcquired()51         void onLastStepAcquired();
52     }
53 
54     @NonNull private final Context mContext;
55     @NonNull private final FingerprintManager mFingerprintManager;
56     // IUdfpsOverlayController reason
57     private final int mEnrollReason;
58     private final boolean mAccessibilityEnabled;
59     @NonNull private final List<PointF> mGuidedEnrollmentPoints;
60 
61     private int mTotalSteps = -1;
62     private int mRemainingSteps = -1;
63 
64     // Note that this is actually not equal to "mTotalSteps - mRemainingSteps", because the
65     // interface makes no promises about monotonically increasing by one each time.
66     private int mLocationsEnrolled = 0;
67 
68     private int mCenterTouchCount = 0;
69 
70     @Nullable Listener mListener;
71 
UdfpsEnrollHelper(@onNull Context context, @NonNull FingerprintManager fingerprintManager, int reason)72     public UdfpsEnrollHelper(@NonNull Context context,
73             @NonNull FingerprintManager fingerprintManager, int reason) {
74 
75         mContext = context;
76         mFingerprintManager = fingerprintManager;
77         mEnrollReason = reason;
78 
79         final AccessibilityManager am = context.getSystemService(AccessibilityManager.class);
80         mAccessibilityEnabled = am.isEnabled();
81 
82         mGuidedEnrollmentPoints = new ArrayList<>();
83 
84         // Number of pixels per mm
85         float px = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_MM, 1,
86                 context.getResources().getDisplayMetrics());
87         boolean useNewCoords = Settings.Secure.getIntForUser(mContext.getContentResolver(),
88                 NEW_COORDS_OVERRIDE, 0,
89                 UserHandle.USER_CURRENT) != 0;
90         if (useNewCoords && (Build.IS_ENG || Build.IS_USERDEBUG)) {
91             Log.v(TAG, "Using new coordinates");
92             mGuidedEnrollmentPoints.add(new PointF(-0.15f * px, -1.02f * px));
93             mGuidedEnrollmentPoints.add(new PointF(-0.15f * px,  1.02f * px));
94             mGuidedEnrollmentPoints.add(new PointF( 0.29f * px,  0.00f * px));
95             mGuidedEnrollmentPoints.add(new PointF( 2.17f * px, -2.35f * px));
96             mGuidedEnrollmentPoints.add(new PointF( 1.07f * px, -3.96f * px));
97             mGuidedEnrollmentPoints.add(new PointF(-0.37f * px, -4.31f * px));
98             mGuidedEnrollmentPoints.add(new PointF(-1.69f * px, -3.29f * px));
99             mGuidedEnrollmentPoints.add(new PointF(-2.48f * px, -1.23f * px));
100             mGuidedEnrollmentPoints.add(new PointF(-2.48f * px,  1.23f * px));
101             mGuidedEnrollmentPoints.add(new PointF(-1.69f * px,  3.29f * px));
102             mGuidedEnrollmentPoints.add(new PointF(-0.37f * px,  4.31f * px));
103             mGuidedEnrollmentPoints.add(new PointF( 1.07f * px,  3.96f * px));
104             mGuidedEnrollmentPoints.add(new PointF( 2.17f * px,  2.35f * px));
105             mGuidedEnrollmentPoints.add(new PointF( 2.58f * px,  0.00f * px));
106         } else {
107             Log.v(TAG, "Using old coordinates");
108             mGuidedEnrollmentPoints.add(new PointF( 2.00f * px,  0.00f * px));
109             mGuidedEnrollmentPoints.add(new PointF( 0.87f * px, -2.70f * px));
110             mGuidedEnrollmentPoints.add(new PointF(-1.80f * px, -1.31f * px));
111             mGuidedEnrollmentPoints.add(new PointF(-1.80f * px,  1.31f * px));
112             mGuidedEnrollmentPoints.add(new PointF( 0.88f * px,  2.70f * px));
113             mGuidedEnrollmentPoints.add(new PointF( 3.94f * px, -1.06f * px));
114             mGuidedEnrollmentPoints.add(new PointF( 2.90f * px, -4.14f * px));
115             mGuidedEnrollmentPoints.add(new PointF(-0.52f * px, -5.95f * px));
116             mGuidedEnrollmentPoints.add(new PointF(-3.33f * px, -3.33f * px));
117             mGuidedEnrollmentPoints.add(new PointF(-3.99f * px, -0.35f * px));
118             mGuidedEnrollmentPoints.add(new PointF(-3.62f * px,  2.54f * px));
119             mGuidedEnrollmentPoints.add(new PointF(-1.49f * px,  5.57f * px));
120             mGuidedEnrollmentPoints.add(new PointF( 2.29f * px,  4.92f * px));
121             mGuidedEnrollmentPoints.add(new PointF( 3.82f * px,  1.78f * px));
122         }
123     }
124 
getStageCount()125     int getStageCount() {
126         return mFingerprintManager.getEnrollStageCount();
127     }
128 
getStageThresholdSteps(int totalSteps, int stageIndex)129     int getStageThresholdSteps(int totalSteps, int stageIndex) {
130         return Math.round(totalSteps * mFingerprintManager.getEnrollStageThreshold(stageIndex));
131     }
132 
shouldShowProgressBar()133     boolean shouldShowProgressBar() {
134         return mEnrollReason == BiometricOverlayConstants.REASON_ENROLL_ENROLLING;
135     }
136 
onEnrollmentProgress(int remaining)137     void onEnrollmentProgress(int remaining) {
138         if (mTotalSteps == -1) {
139             mTotalSteps = remaining;
140         }
141 
142         if (remaining != mRemainingSteps) {
143             mLocationsEnrolled++;
144             if (isCenterEnrollmentStage()) {
145                 mCenterTouchCount++;
146             }
147         }
148 
149         mRemainingSteps = remaining;
150 
151         if (mListener != null) {
152             mListener.onEnrollmentProgress(remaining, mTotalSteps);
153         }
154     }
155 
onEnrollmentHelp()156     void onEnrollmentHelp() {
157         if (mListener != null) {
158             mListener.onEnrollmentHelp(mRemainingSteps, mTotalSteps);
159         }
160     }
161 
setListener(Listener listener)162     void setListener(Listener listener) {
163         mListener = listener;
164 
165         // Only notify during setListener if enrollment is already in progress, so the progress
166         // bar can be updated. If enrollment has not started yet, the progress bar will be empty
167         // anyway.
168         if (mListener != null && mTotalSteps != -1) {
169             mListener.onEnrollmentProgress(mRemainingSteps, mTotalSteps);
170         }
171     }
172 
isCenterEnrollmentStage()173     boolean isCenterEnrollmentStage() {
174         if (mTotalSteps == -1 || mRemainingSteps == -1) {
175             return true;
176         }
177         return mTotalSteps - mRemainingSteps < getStageThresholdSteps(mTotalSteps, 0);
178     }
179 
isGuidedEnrollmentStage()180     boolean isGuidedEnrollmentStage() {
181         if (mAccessibilityEnabled || mTotalSteps == -1 || mRemainingSteps == -1) {
182             return false;
183         }
184         final int progressSteps = mTotalSteps - mRemainingSteps;
185         return progressSteps >= getStageThresholdSteps(mTotalSteps, 0)
186                 && progressSteps < getStageThresholdSteps(mTotalSteps, 1);
187     }
188 
isTipEnrollmentStage()189     boolean isTipEnrollmentStage() {
190         if (mTotalSteps == -1 || mRemainingSteps == -1) {
191             return false;
192         }
193         final int progressSteps = mTotalSteps - mRemainingSteps;
194         return progressSteps >= getStageThresholdSteps(mTotalSteps, 1)
195                 && progressSteps < getStageThresholdSteps(mTotalSteps, 2);
196     }
197 
isEdgeEnrollmentStage()198     boolean isEdgeEnrollmentStage() {
199         if (mTotalSteps == -1 || mRemainingSteps == -1) {
200             return false;
201         }
202         return mTotalSteps - mRemainingSteps >= getStageThresholdSteps(mTotalSteps, 2);
203     }
204 
205     @NonNull
getNextGuidedEnrollmentPoint()206     PointF getNextGuidedEnrollmentPoint() {
207         if (mAccessibilityEnabled || !isGuidedEnrollmentStage()) {
208             return new PointF(0f, 0f);
209         }
210 
211         float scale = SCALE;
212         if (Build.IS_ENG || Build.IS_USERDEBUG) {
213             scale = Settings.Secure.getFloatForUser(mContext.getContentResolver(),
214                     SCALE_OVERRIDE, SCALE,
215                     UserHandle.USER_CURRENT);
216         }
217         final int index = mLocationsEnrolled - mCenterTouchCount;
218         final PointF originalPoint = mGuidedEnrollmentPoints
219                 .get(index % mGuidedEnrollmentPoints.size());
220         return new PointF(originalPoint.x * scale, originalPoint.y * scale);
221     }
222 
animateIfLastStep()223     void animateIfLastStep() {
224         if (mListener == null) {
225             Log.e(TAG, "animateIfLastStep, null listener");
226             return;
227         }
228 
229         if (mRemainingSteps <= 2 && mRemainingSteps >= 0) {
230             mListener.onLastStepAcquired();
231         }
232     }
233 }
234