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.util.Log; 28 import android.util.TypedValue; 29 import android.view.accessibility.AccessibilityManager; 30 31 import com.android.systemui.util.settings.SecureSettings; 32 33 import java.util.ArrayList; 34 import java.util.List; 35 36 /** 37 * Helps keep track of enrollment state and animates the progress bar accordingly. 38 */ 39 public class UdfpsEnrollHelper { 40 private static final String TAG = "UdfpsEnrollHelper"; 41 42 private static final String SCALE_OVERRIDE = 43 "com.android.systemui.biometrics.UdfpsEnrollHelper.scale"; 44 private static final float SCALE = 0.5f; 45 46 private static final String NEW_COORDS_OVERRIDE = 47 "com.android.systemui.biometrics.UdfpsNewCoords"; 48 49 interface Listener { onEnrollmentProgress(int remaining, int totalSteps)50 void onEnrollmentProgress(int remaining, int totalSteps); onEnrollmentHelp(int remaining, int totalSteps)51 void onEnrollmentHelp(int remaining, int totalSteps); onLastStepAcquired()52 void onLastStepAcquired(); 53 } 54 55 @NonNull private final FingerprintManager mFingerprintManager; 56 @NonNull private final SecureSettings mSecureSettings; 57 // IUdfpsOverlayController reason 58 private final int mEnrollReason; 59 private final boolean mAccessibilityEnabled; 60 @NonNull private final List<PointF> mGuidedEnrollmentPoints; 61 62 private int mTotalSteps = -1; 63 private int mRemainingSteps = -1; 64 65 // Note that this is actually not equal to "mTotalSteps - mRemainingSteps", because the 66 // interface makes no promises about monotonically increasing by one each time. 67 private int mLocationsEnrolled = 0; 68 69 private int mCenterTouchCount = 0; 70 71 @Nullable Listener mListener; 72 UdfpsEnrollHelper(@onNull Context context, @NonNull FingerprintManager fingerprintManager, SecureSettings secureSettings, int reason)73 public UdfpsEnrollHelper(@NonNull Context context, 74 @NonNull FingerprintManager fingerprintManager, SecureSettings secureSettings, 75 int reason) { 76 77 mFingerprintManager = fingerprintManager; 78 mSecureSettings = secureSettings; 79 mEnrollReason = reason; 80 81 final AccessibilityManager am = context.getSystemService(AccessibilityManager.class); 82 mAccessibilityEnabled = am.isEnabled(); 83 84 mGuidedEnrollmentPoints = new ArrayList<>(); 85 86 // Number of pixels per mm 87 float px = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_MM, 1, 88 context.getResources().getDisplayMetrics()); 89 boolean useNewCoords = mSecureSettings.getIntForUser(NEW_COORDS_OVERRIDE, 0, 90 UserHandle.USER_CURRENT) != 0; 91 if (useNewCoords && (Build.IS_ENG || Build.IS_USERDEBUG)) { 92 Log.v(TAG, "Using new coordinates"); 93 mGuidedEnrollmentPoints.add(new PointF(-0.15f * px, -1.02f * px)); 94 mGuidedEnrollmentPoints.add(new PointF(-0.15f * px, 1.02f * px)); 95 mGuidedEnrollmentPoints.add(new PointF( 0.29f * px, 0.00f * px)); 96 mGuidedEnrollmentPoints.add(new PointF( 2.17f * px, -2.35f * px)); 97 mGuidedEnrollmentPoints.add(new PointF( 1.07f * px, -3.96f * px)); 98 mGuidedEnrollmentPoints.add(new PointF(-0.37f * px, -4.31f * px)); 99 mGuidedEnrollmentPoints.add(new PointF(-1.69f * px, -3.29f * px)); 100 mGuidedEnrollmentPoints.add(new PointF(-2.48f * px, -1.23f * px)); 101 mGuidedEnrollmentPoints.add(new PointF(-2.48f * px, 1.23f * px)); 102 mGuidedEnrollmentPoints.add(new PointF(-1.69f * px, 3.29f * px)); 103 mGuidedEnrollmentPoints.add(new PointF(-0.37f * px, 4.31f * px)); 104 mGuidedEnrollmentPoints.add(new PointF( 1.07f * px, 3.96f * px)); 105 mGuidedEnrollmentPoints.add(new PointF( 2.17f * px, 2.35f * px)); 106 mGuidedEnrollmentPoints.add(new PointF( 2.58f * px, 0.00f * px)); 107 } else { 108 Log.v(TAG, "Using old coordinates"); 109 mGuidedEnrollmentPoints.add(new PointF( 2.00f * px, 0.00f * px)); 110 mGuidedEnrollmentPoints.add(new PointF( 0.87f * px, -2.70f * px)); 111 mGuidedEnrollmentPoints.add(new PointF(-1.80f * px, -1.31f * px)); 112 mGuidedEnrollmentPoints.add(new PointF(-1.80f * px, 1.31f * px)); 113 mGuidedEnrollmentPoints.add(new PointF( 0.88f * px, 2.70f * px)); 114 mGuidedEnrollmentPoints.add(new PointF( 3.94f * px, -1.06f * px)); 115 mGuidedEnrollmentPoints.add(new PointF( 2.90f * px, -4.14f * px)); 116 mGuidedEnrollmentPoints.add(new PointF(-0.52f * px, -5.95f * px)); 117 mGuidedEnrollmentPoints.add(new PointF(-3.33f * px, -3.33f * px)); 118 mGuidedEnrollmentPoints.add(new PointF(-3.99f * px, -0.35f * px)); 119 mGuidedEnrollmentPoints.add(new PointF(-3.62f * px, 2.54f * px)); 120 mGuidedEnrollmentPoints.add(new PointF(-1.49f * px, 5.57f * px)); 121 mGuidedEnrollmentPoints.add(new PointF( 2.29f * px, 4.92f * px)); 122 mGuidedEnrollmentPoints.add(new PointF( 3.82f * px, 1.78f * px)); 123 } 124 } 125 getStageCount()126 int getStageCount() { 127 return mFingerprintManager.getEnrollStageCount(); 128 } 129 getStageThresholdSteps(int totalSteps, int stageIndex)130 int getStageThresholdSteps(int totalSteps, int stageIndex) { 131 return Math.round(totalSteps * mFingerprintManager.getEnrollStageThreshold(stageIndex)); 132 } 133 shouldShowProgressBar()134 boolean shouldShowProgressBar() { 135 return mEnrollReason == BiometricOverlayConstants.REASON_ENROLL_ENROLLING; 136 } 137 onEnrollmentProgress(int remaining)138 void onEnrollmentProgress(int remaining) { 139 if (mTotalSteps == -1) { 140 mTotalSteps = remaining; 141 } 142 143 if (remaining != mRemainingSteps) { 144 mLocationsEnrolled++; 145 if (isCenterEnrollmentStage()) { 146 mCenterTouchCount++; 147 } 148 } 149 150 mRemainingSteps = remaining; 151 152 if (mListener != null) { 153 mListener.onEnrollmentProgress(remaining, mTotalSteps); 154 } 155 } 156 onEnrollmentHelp()157 void onEnrollmentHelp() { 158 if (mListener != null) { 159 mListener.onEnrollmentHelp(mRemainingSteps, mTotalSteps); 160 } 161 } 162 setListener(Listener listener)163 void setListener(Listener listener) { 164 mListener = listener; 165 166 // Only notify during setListener if enrollment is already in progress, so the progress 167 // bar can be updated. If enrollment has not started yet, the progress bar will be empty 168 // anyway. 169 if (mListener != null && mTotalSteps != -1) { 170 mListener.onEnrollmentProgress(mRemainingSteps, mTotalSteps); 171 } 172 } 173 isCenterEnrollmentStage()174 boolean isCenterEnrollmentStage() { 175 if (mTotalSteps == -1 || mRemainingSteps == -1) { 176 return true; 177 } 178 return mTotalSteps - mRemainingSteps < getStageThresholdSteps(mTotalSteps, 0); 179 } 180 isGuidedEnrollmentStage()181 boolean isGuidedEnrollmentStage() { 182 if (mAccessibilityEnabled || mTotalSteps == -1 || mRemainingSteps == -1) { 183 return false; 184 } 185 final int progressSteps = mTotalSteps - mRemainingSteps; 186 return progressSteps >= getStageThresholdSteps(mTotalSteps, 0) 187 && progressSteps < getStageThresholdSteps(mTotalSteps, 1); 188 } 189 isTipEnrollmentStage()190 boolean isTipEnrollmentStage() { 191 if (mTotalSteps == -1 || mRemainingSteps == -1) { 192 return false; 193 } 194 final int progressSteps = mTotalSteps - mRemainingSteps; 195 return progressSteps >= getStageThresholdSteps(mTotalSteps, 1) 196 && progressSteps < getStageThresholdSteps(mTotalSteps, 2); 197 } 198 isEdgeEnrollmentStage()199 boolean isEdgeEnrollmentStage() { 200 if (mTotalSteps == -1 || mRemainingSteps == -1) { 201 return false; 202 } 203 return mTotalSteps - mRemainingSteps >= getStageThresholdSteps(mTotalSteps, 2); 204 } 205 206 @NonNull getNextGuidedEnrollmentPoint()207 PointF getNextGuidedEnrollmentPoint() { 208 if (mAccessibilityEnabled || !isGuidedEnrollmentStage()) { 209 return new PointF(0f, 0f); 210 } 211 212 float scale = SCALE; 213 if (Build.IS_ENG || Build.IS_USERDEBUG) { 214 scale = mSecureSettings.getFloatForUser(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