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.biometrics2.ui.viewmodel; 18 19 import static android.hardware.biometrics.BiometricFingerprintConstants.FINGERPRINT_ERROR_CANCELED; 20 import static android.hardware.fingerprint.FingerprintManager.ENROLL_ENROLL; 21 22 import static com.android.settings.biometrics2.ui.model.EnrollmentProgress.INITIAL_REMAINING; 23 import static com.android.settings.biometrics2.ui.model.EnrollmentProgress.INITIAL_STEPS; 24 25 import android.app.Application; 26 import android.content.Intent; 27 import android.content.res.Resources; 28 import android.hardware.fingerprint.FingerprintManager.EnrollReason; 29 import android.hardware.fingerprint.FingerprintManager.EnrollmentCallback; 30 import android.os.CancellationSignal; 31 import android.os.SystemClock; 32 import android.util.Log; 33 34 import androidx.annotation.NonNull; 35 import androidx.annotation.Nullable; 36 import androidx.lifecycle.AndroidViewModel; 37 import androidx.lifecycle.LiveData; 38 import androidx.lifecycle.MutableLiveData; 39 40 import com.android.settings.R; 41 import com.android.settings.biometrics.fingerprint.FingerprintUpdater; 42 import com.android.settings.biometrics.fingerprint.MessageDisplayController; 43 import com.android.settings.biometrics2.ui.model.EnrollmentProgress; 44 import com.android.settings.biometrics2.ui.model.EnrollmentStatusMessage; 45 46 import java.util.LinkedList; 47 48 /** 49 * Progress ViewModel handles the state around biometric enrollment. It manages the state of 50 * enrollment throughout the activity lifecycle so the app can continue after an event like 51 * rotation. 52 */ 53 public class FingerprintEnrollProgressViewModel extends AndroidViewModel { 54 55 private static final boolean DEBUG = false; 56 private static final String TAG = "FingerprintEnrollProgressViewModel"; 57 58 private final MutableLiveData<EnrollmentProgress> mProgressLiveData = new MutableLiveData<>( 59 new EnrollmentProgress(INITIAL_STEPS, INITIAL_REMAINING)); 60 private final MutableLiveData<EnrollmentStatusMessage> mHelpMessageLiveData = 61 new MutableLiveData<>(); 62 private final MutableLiveData<EnrollmentStatusMessage> mErrorMessageLiveData = 63 new MutableLiveData<>(); 64 private final MutableLiveData<Object> mCanceledSignalLiveData = new MutableLiveData<>(); 65 private final MutableLiveData<Boolean> mAcquireLiveData = new MutableLiveData<>(); 66 private final MutableLiveData<Integer> mPointerDownLiveData = new MutableLiveData<>(); 67 private final MutableLiveData<Integer> mPointerUpLiveData = new MutableLiveData<>(); 68 69 private byte[] mToken = null; 70 private final int mUserId; 71 72 private final FingerprintUpdater mFingerprintUpdater; 73 @Nullable private CancellationSignal mCancellationSignal = null; 74 @NonNull private final LinkedList<CancellationSignal> mCancelingSignalQueue = 75 new LinkedList<>(); 76 private final EnrollmentCallback mEnrollmentCallback = new EnrollmentCallback() { 77 78 @Override 79 public void onEnrollmentProgress(int remaining) { 80 final int currentSteps = getSteps(); 81 final EnrollmentProgress progress = new EnrollmentProgress( 82 currentSteps == INITIAL_STEPS ? remaining : getSteps(), remaining); 83 if (DEBUG) { 84 Log.d(TAG, "onEnrollmentProgress(" + remaining + "), steps: " + currentSteps 85 + ", post progress as " + progress); 86 } 87 mHelpMessageLiveData.setValue(null); 88 mProgressLiveData.postValue(progress); 89 } 90 91 @Override 92 public void onEnrollmentHelp(int helpMsgId, CharSequence helpString) { 93 if (DEBUG) { 94 Log.d(TAG, "onEnrollmentHelp(" + helpMsgId + ", " + helpString + ")"); 95 } 96 mHelpMessageLiveData.postValue(new EnrollmentStatusMessage(helpMsgId, helpString)); 97 } 98 99 @Override 100 public void onEnrollmentError(int errMsgId, CharSequence errString) { 101 Log.d(TAG, "onEnrollmentError(" + errMsgId + ", " + errString 102 + "), cancelingQueueSize:" + mCancelingSignalQueue.size()); 103 if (FINGERPRINT_ERROR_CANCELED == errMsgId && mCancelingSignalQueue.size() > 0) { 104 mCanceledSignalLiveData.postValue(mCancelingSignalQueue.poll()); 105 } else { 106 mErrorMessageLiveData.postValue(new EnrollmentStatusMessage(errMsgId, errString)); 107 } 108 } 109 110 @Override 111 public void onAcquired(boolean isAcquiredGood) { 112 mAcquireLiveData.postValue(isAcquiredGood); 113 } 114 115 @Override 116 public void onUdfpsPointerDown(int sensorId) { 117 mPointerDownLiveData.postValue(sensorId); 118 } 119 120 @Override 121 public void onUdfpsPointerUp(int sensorId) { 122 mPointerUpLiveData.postValue(sensorId); 123 } 124 }; 125 FingerprintEnrollProgressViewModel(@onNull Application application, @NonNull FingerprintUpdater fingerprintUpdater, int userId)126 public FingerprintEnrollProgressViewModel(@NonNull Application application, 127 @NonNull FingerprintUpdater fingerprintUpdater, int userId) { 128 super(application); 129 mFingerprintUpdater = fingerprintUpdater; 130 mUserId = userId; 131 } 132 setToken(byte[] token)133 public void setToken(byte[] token) { 134 mToken = token; 135 } 136 137 /** 138 * clear progress 139 */ clearProgressLiveData()140 public void clearProgressLiveData() { 141 mProgressLiveData.setValue(new EnrollmentProgress(INITIAL_STEPS, INITIAL_REMAINING)); 142 mHelpMessageLiveData.setValue(null); 143 mErrorMessageLiveData.setValue(null); 144 } 145 146 /** 147 * clear error message 148 */ clearErrorMessageLiveData()149 public void clearErrorMessageLiveData() { 150 mErrorMessageLiveData.setValue(null); 151 } 152 getProgressLiveData()153 public LiveData<EnrollmentProgress> getProgressLiveData() { 154 return mProgressLiveData; 155 } 156 getHelpMessageLiveData()157 public LiveData<EnrollmentStatusMessage> getHelpMessageLiveData() { 158 return mHelpMessageLiveData; 159 } 160 getErrorMessageLiveData()161 public LiveData<EnrollmentStatusMessage> getErrorMessageLiveData() { 162 return mErrorMessageLiveData; 163 } 164 getCanceledSignalLiveData()165 public LiveData<Object> getCanceledSignalLiveData() { 166 return mCanceledSignalLiveData; 167 } 168 getAcquireLiveData()169 public LiveData<Boolean> getAcquireLiveData() { 170 return mAcquireLiveData; 171 } 172 getPointerDownLiveData()173 public LiveData<Integer> getPointerDownLiveData() { 174 return mPointerDownLiveData; 175 } 176 getPointerUpLiveData()177 public LiveData<Integer> getPointerUpLiveData() { 178 return mPointerUpLiveData; 179 } 180 181 /** 182 * Starts enrollment and return latest isEnrolling() result 183 */ startEnrollment(@nrollReason int reason)184 public Object startEnrollment(@EnrollReason int reason) { 185 if (mToken == null) { 186 Log.e(TAG, "Null hardware auth token for enroll"); 187 return null; 188 } 189 if (mCancellationSignal != null) { 190 Log.w(TAG, "Enrolling is running, shall not start again"); 191 return mCancellationSignal; 192 } 193 if (DEBUG) { 194 Log.e(TAG, "startEnrollment(" + reason + ")"); 195 } 196 197 // Clear data 198 mProgressLiveData.setValue(new EnrollmentProgress(INITIAL_STEPS, INITIAL_REMAINING)); 199 mHelpMessageLiveData.setValue(null); 200 mErrorMessageLiveData.setValue(null); 201 202 mCancellationSignal = new CancellationSignal(); 203 204 final Resources res = getApplication().getResources(); 205 if (reason == ENROLL_ENROLL 206 && res.getBoolean(R.bool.enrollment_message_display_controller_flag)) { 207 final EnrollmentCallback callback = new MessageDisplayController( 208 getApplication().getMainThreadHandler(), 209 mEnrollmentCallback, 210 SystemClock.elapsedRealtimeClock(), 211 res.getInteger(R.integer.enrollment_help_minimum_time_display), 212 res.getInteger(R.integer.enrollment_progress_minimum_time_display), 213 res.getBoolean(R.bool.enrollment_progress_priority_over_help), 214 res.getBoolean(R.bool.enrollment_prioritize_acquire_messages), 215 res.getInteger(R.integer.enrollment_collect_time)); 216 mFingerprintUpdater.enroll(mToken, mCancellationSignal, mUserId, callback, reason, 217 new Intent()); 218 } else { 219 mFingerprintUpdater.enroll(mToken, mCancellationSignal, mUserId, mEnrollmentCallback, 220 reason, new Intent()); 221 } 222 return mCancellationSignal; 223 } 224 225 /** 226 * Cancels enrollment and return latest isEnrolling result 227 */ cancelEnrollment()228 public boolean cancelEnrollment() { 229 final CancellationSignal cancellationSignal = mCancellationSignal; 230 mCancellationSignal = null; 231 232 if (cancellationSignal == null) { 233 Log.e(TAG, "Fail to cancel enrollment, has cancelled or not start"); 234 return false; 235 } else { 236 Log.d(TAG, "enrollment cancelled"); 237 } 238 mCancelingSignalQueue.add(cancellationSignal); 239 cancellationSignal.cancel(); 240 241 return true; 242 } 243 isEnrolling()244 public boolean isEnrolling() { 245 return (mCancellationSignal != null); 246 } 247 getSteps()248 private int getSteps() { 249 return mProgressLiveData.getValue().getSteps(); 250 } 251 } 252