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