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