1 /* 2 * Copyright (C) 2022 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.settings.biometrics.fingerprint; 18 19 import android.content.Context; 20 import android.graphics.PointF; 21 import android.hardware.fingerprint.FingerprintManager; 22 import android.os.Build; 23 import android.os.Bundle; 24 import android.os.UserHandle; 25 import android.provider.Settings; 26 import android.util.Log; 27 import android.util.TypedValue; 28 import android.view.accessibility.AccessibilityManager; 29 30 import androidx.annotation.NonNull; 31 import androidx.annotation.Nullable; 32 33 import com.android.settings.core.InstrumentedFragment; 34 35 import java.util.ArrayList; 36 import java.util.List; 37 38 /** 39 * Helps keep track of enrollment state and animates the progress bar accordingly. 40 */ 41 public class UdfpsEnrollHelper extends InstrumentedFragment { 42 private static final String TAG = "UdfpsEnrollHelper"; 43 44 private static final String SCALE_OVERRIDE = 45 "com.android.systemui.biometrics.UdfpsEnrollHelper.scale"; 46 private static final float SCALE = 0.5f; 47 48 private static final String NEW_COORDS_OVERRIDE = 49 "com.android.systemui.biometrics.UdfpsNewCoords"; 50 51 interface Listener { onEnrollmentProgress(int remaining, int totalSteps)52 void onEnrollmentProgress(int remaining, int totalSteps); 53 onEnrollmentHelp(int remaining, int totalSteps)54 void onEnrollmentHelp(int remaining, int totalSteps); 55 onAcquired(boolean animateIfLastStepGood)56 void onAcquired(boolean animateIfLastStepGood); 57 onPointerDown(int sensorId)58 void onPointerDown(int sensorId); 59 onPointerUp(int sensorId)60 void onPointerUp(int sensorId); 61 } 62 63 @NonNull 64 private final Context mContext; 65 @NonNull 66 private final FingerprintManager mFingerprintManager; 67 private final boolean mAccessibilityEnabled; 68 @NonNull 69 private final List<PointF> mGuidedEnrollmentPoints; 70 71 private int mTotalSteps = -1; 72 private int mRemainingSteps = -1; 73 74 // Note that this is actually not equal to "mTotalSteps - mRemainingSteps", because the 75 // interface makes no promises about monotonically increasing by one each time. 76 private int mLocationsEnrolled = 0; 77 78 private int mCenterTouchCount = 0; 79 80 private int mPace = 1; 81 82 @Nullable 83 UdfpsEnrollHelper.Listener mListener; 84 UdfpsEnrollHelper(@onNull Context context, @NonNull FingerprintManager fingerprintManager)85 public UdfpsEnrollHelper(@NonNull Context context, 86 @NonNull FingerprintManager fingerprintManager) { 87 88 mContext = context; 89 mFingerprintManager = fingerprintManager; 90 91 final AccessibilityManager am = context.getSystemService(AccessibilityManager.class); 92 mAccessibilityEnabled = am.isEnabled(); 93 94 mGuidedEnrollmentPoints = new ArrayList<>(); 95 96 // Number of pixels per mm 97 float px = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_MM, 1, 98 context.getResources().getDisplayMetrics()); 99 boolean useNewCoords = Settings.Secure.getIntForUser(mContext.getContentResolver(), 100 NEW_COORDS_OVERRIDE, 0, 101 UserHandle.USER_CURRENT) != 0; 102 if (useNewCoords && (Build.IS_ENG || Build.IS_USERDEBUG)) { 103 Log.v(TAG, "Using new coordinates"); 104 mGuidedEnrollmentPoints.add(new PointF(-0.15f * px, -1.02f * px)); 105 mGuidedEnrollmentPoints.add(new PointF(-0.15f * px, 1.02f * px)); 106 mGuidedEnrollmentPoints.add(new PointF(0.29f * px, 0.00f * px)); 107 mGuidedEnrollmentPoints.add(new PointF(2.17f * px, -2.35f * px)); 108 mGuidedEnrollmentPoints.add(new PointF(1.07f * px, -3.96f * px)); 109 mGuidedEnrollmentPoints.add(new PointF(-0.37f * px, -4.31f * px)); 110 mGuidedEnrollmentPoints.add(new PointF(-1.69f * px, -3.29f * px)); 111 mGuidedEnrollmentPoints.add(new PointF(-2.48f * px, -1.23f * px)); 112 mGuidedEnrollmentPoints.add(new PointF(-2.48f * px, 1.23f * px)); 113 mGuidedEnrollmentPoints.add(new PointF(-1.69f * px, 3.29f * px)); 114 mGuidedEnrollmentPoints.add(new PointF(-0.37f * px, 4.31f * px)); 115 mGuidedEnrollmentPoints.add(new PointF(1.07f * px, 3.96f * px)); 116 mGuidedEnrollmentPoints.add(new PointF(2.17f * px, 2.35f * px)); 117 mGuidedEnrollmentPoints.add(new PointF(2.58f * px, 0.00f * px)); 118 } else { 119 Log.v(TAG, "Using old coordinates"); 120 mGuidedEnrollmentPoints.add(new PointF(2.00f * px, 0.00f * px)); 121 mGuidedEnrollmentPoints.add(new PointF(0.87f * px, -2.70f * px)); 122 mGuidedEnrollmentPoints.add(new PointF(-1.80f * px, -1.31f * px)); 123 mGuidedEnrollmentPoints.add(new PointF(-1.80f * px, 1.31f * px)); 124 mGuidedEnrollmentPoints.add(new PointF(0.88f * px, 2.70f * px)); 125 mGuidedEnrollmentPoints.add(new PointF(3.94f * px, -1.06f * px)); 126 mGuidedEnrollmentPoints.add(new PointF(2.90f * px, -4.14f * px)); 127 mGuidedEnrollmentPoints.add(new PointF(-0.52f * px, -5.95f * px)); 128 mGuidedEnrollmentPoints.add(new PointF(-3.33f * px, -3.33f * px)); 129 mGuidedEnrollmentPoints.add(new PointF(-3.99f * px, -0.35f * px)); 130 mGuidedEnrollmentPoints.add(new PointF(-3.62f * px, 2.54f * px)); 131 mGuidedEnrollmentPoints.add(new PointF(-1.49f * px, 5.57f * px)); 132 mGuidedEnrollmentPoints.add(new PointF(2.29f * px, 4.92f * px)); 133 mGuidedEnrollmentPoints.add(new PointF(3.82f * px, 1.78f * px)); 134 } 135 } 136 137 @Override getMetricsCategory()138 public int getMetricsCategory() { 139 return 0; 140 } 141 142 @Override onCreate(@ullable Bundle savedInstanceState)143 public void onCreate(@Nullable Bundle savedInstanceState) { 144 super.onCreate(savedInstanceState); 145 setRetainInstance(true); 146 } 147 148 /** 149 * Called when a enroll progress update 150 */ onEnrollmentProgress(int totalSteps, int remaining)151 public void onEnrollmentProgress(int totalSteps, int remaining) { 152 if (mTotalSteps == -1) { 153 mTotalSteps = totalSteps; 154 } 155 156 if (remaining != mRemainingSteps) { 157 mLocationsEnrolled++; 158 if (isCenterEnrollmentStage()) { 159 mCenterTouchCount++; 160 } 161 } 162 163 if (mRemainingSteps > remaining) { 164 mPace = mRemainingSteps - remaining; 165 } 166 mRemainingSteps = remaining; 167 168 if (mListener != null && mTotalSteps != -1) { 169 mListener.onEnrollmentProgress(remaining, mTotalSteps); 170 } 171 } 172 173 /** 174 * Called when a receive error has been encountered during enrollment. 175 */ onEnrollmentHelp()176 public void onEnrollmentHelp() { 177 if (mListener != null) { 178 mListener.onEnrollmentHelp(mRemainingSteps, mTotalSteps); 179 } 180 } 181 182 /** 183 * Called when a fingerprint image has been acquired, but wasn't processed yet. 184 */ onAcquired(boolean isAcquiredGood)185 public void onAcquired(boolean isAcquiredGood) { 186 if (mListener != null) { 187 mListener.onAcquired(isAcquiredGood && animateIfLastStep()); 188 } 189 } 190 191 /** 192 * Called when pointer down 193 */ onPointerDown(int sensorId)194 public void onPointerDown(int sensorId) { 195 if (mListener != null) { 196 mListener.onPointerDown(sensorId); 197 } 198 } 199 200 /** 201 * Called when pointer up 202 */ onPointerUp(int sensorId)203 public void onPointerUp(int sensorId) { 204 if (mListener != null) { 205 mListener.onPointerUp(sensorId); 206 } 207 } 208 setListener(UdfpsEnrollHelper.Listener listener)209 void setListener(UdfpsEnrollHelper.Listener listener) { 210 mListener = listener; 211 212 // Only notify during setListener if enrollment is already in progress, so the progress 213 // bar can be updated. If enrollment has not started yet, the progress bar will be empty 214 // anyway. 215 if (mListener != null && mTotalSteps != -1) { 216 mListener.onEnrollmentProgress(mRemainingSteps, mTotalSteps); 217 } 218 } 219 isCenterEnrollmentStage()220 boolean isCenterEnrollmentStage() { 221 if (mTotalSteps == -1 || mRemainingSteps == -1) { 222 return true; 223 } 224 return mTotalSteps - mRemainingSteps < getStageThresholdSteps(mTotalSteps, 0); 225 } 226 isTipEnrollmentStage()227 boolean isTipEnrollmentStage() { 228 if (mTotalSteps == -1 || mRemainingSteps == -1) { 229 return false; 230 } 231 final int progressSteps = mTotalSteps - mRemainingSteps; 232 return progressSteps >= getStageThresholdSteps(mTotalSteps, 1) 233 && progressSteps < getStageThresholdSteps(mTotalSteps, 2); 234 } 235 isEdgeEnrollmentStage()236 boolean isEdgeEnrollmentStage() { 237 if (mTotalSteps == -1 || mRemainingSteps == -1) { 238 return false; 239 } 240 return mTotalSteps - mRemainingSteps >= getStageThresholdSteps(mTotalSteps, 2); 241 } 242 243 @NonNull getNextGuidedEnrollmentPoint()244 PointF getNextGuidedEnrollmentPoint() { 245 if (mAccessibilityEnabled || !isGuidedEnrollmentStage()) { 246 return new PointF(0f, 0f); 247 } 248 249 float scale = SCALE; 250 if (Build.IS_ENG || Build.IS_USERDEBUG) { 251 scale = Settings.Secure.getFloatForUser(mContext.getContentResolver(), 252 SCALE_OVERRIDE, SCALE, 253 UserHandle.USER_CURRENT); 254 } 255 final int index = mLocationsEnrolled - mCenterTouchCount; 256 final PointF originalPoint = mGuidedEnrollmentPoints 257 .get(index % mGuidedEnrollmentPoints.size()); 258 return new PointF(originalPoint.x * scale, originalPoint.y * scale); 259 } 260 animateIfLastStep()261 boolean animateIfLastStep() { 262 if (mListener == null) { 263 Log.e(TAG, "animateIfLastStep, null listener"); 264 return false; 265 } 266 267 return mRemainingSteps <= mPace && mRemainingSteps >= 0; 268 } 269 getStageThresholdSteps(int totalSteps, int stageIndex)270 private int getStageThresholdSteps(int totalSteps, int stageIndex) { 271 return Math.round(totalSteps * mFingerprintManager.getEnrollStageThreshold(stageIndex)); 272 } 273 isGuidedEnrollmentStage()274 private boolean isGuidedEnrollmentStage() { 275 if (mAccessibilityEnabled || mTotalSteps == -1 || mRemainingSteps == -1) { 276 return false; 277 } 278 final int progressSteps = mTotalSteps - mRemainingSteps; 279 return progressSteps >= getStageThresholdSteps(mTotalSteps, 0) 280 && progressSteps < getStageThresholdSteps(mTotalSteps, 1); 281 } 282 } 283