blob: 139a5ee09dd840c52e2e5faedd005f6114cdcefa [file] [log] [blame]
Gilad Brettercb51b8b2018-03-22 17:04:51 +02001/**
2 * Copyright (C) 2018 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
17package android.hardware.face;
18
19import static android.Manifest.permission.INTERACT_ACROSS_USERS;
Kevin Chyna24e9fd2018-08-27 12:39:17 -070020import static android.Manifest.permission.MANAGE_BIOMETRIC;
21import static android.Manifest.permission.USE_BIOMETRIC_INTERNAL;
Gilad Brettercb51b8b2018-03-22 17:04:51 +020022
23import android.annotation.NonNull;
24import android.annotation.Nullable;
25import android.annotation.RequiresPermission;
26import android.annotation.SystemService;
27import android.app.ActivityManager;
28import android.content.Context;
Kevin Chyna56dff72018-06-19 18:41:12 -070029import android.hardware.biometrics.BiometricAuthenticator;
Kevin Chyna24e9fd2018-08-27 12:39:17 -070030import android.hardware.biometrics.BiometricConstants;
Kevin Chyna56dff72018-06-19 18:41:12 -070031import android.hardware.biometrics.BiometricFaceConstants;
Kevin Chyna56dff72018-06-19 18:41:12 -070032import android.hardware.biometrics.CryptoObject;
Kevin Chyna56dff72018-06-19 18:41:12 -070033import android.hardware.biometrics.IBiometricServiceLockoutResetCallback;
Gilad Brettercb51b8b2018-03-22 17:04:51 +020034import android.os.Binder;
35import android.os.CancellationSignal;
36import android.os.CancellationSignal.OnCancelListener;
37import android.os.Handler;
38import android.os.IBinder;
39import android.os.IRemoteCallback;
40import android.os.Looper;
41import android.os.PowerManager;
42import android.os.RemoteException;
43import android.os.UserHandle;
Gilad Brettercb51b8b2018-03-22 17:04:51 +020044import android.util.Log;
45import android.util.Slog;
46
47import com.android.internal.R;
48
Kevin Chyna56dff72018-06-19 18:41:12 -070049import java.util.List;
Gilad Brettercb51b8b2018-03-22 17:04:51 +020050
51/**
52 * A class that coordinates access to the face authentication hardware.
53 * @hide
54 */
55@SystemService(Context.FACE_SERVICE)
Kevin Chyna24e9fd2018-08-27 12:39:17 -070056public class FaceManager implements BiometricAuthenticator, BiometricFaceConstants {
Kevin Chyna56dff72018-06-19 18:41:12 -070057
Gilad Brettercb51b8b2018-03-22 17:04:51 +020058 private static final String TAG = "FaceManager";
59 private static final boolean DEBUG = true;
60 private static final int MSG_ENROLL_RESULT = 100;
61 private static final int MSG_ACQUIRED = 101;
62 private static final int MSG_AUTHENTICATION_SUCCEEDED = 102;
63 private static final int MSG_AUTHENTICATION_FAILED = 103;
64 private static final int MSG_ERROR = 104;
65 private static final int MSG_REMOVED = 105;
Kevin Chyna56dff72018-06-19 18:41:12 -070066
Gilad Brettercb51b8b2018-03-22 17:04:51 +020067 private IFaceService mService;
Kevin Chyna56dff72018-06-19 18:41:12 -070068 private final Context mContext;
Gilad Brettercb51b8b2018-03-22 17:04:51 +020069 private IBinder mToken = new Binder();
Kevin Chyna24e9fd2018-08-27 12:39:17 -070070 private AuthenticationCallback mAuthenticationCallback;
Gilad Brettercb51b8b2018-03-22 17:04:51 +020071 private EnrollmentCallback mEnrollmentCallback;
72 private RemovalCallback mRemovalCallback;
73 private CryptoObject mCryptoObject;
74 private Face mRemovalFace;
75 private Handler mHandler;
Kevin Chyna56dff72018-06-19 18:41:12 -070076
Gilad Brettercb51b8b2018-03-22 17:04:51 +020077 private IFaceServiceReceiver mServiceReceiver = new IFaceServiceReceiver.Stub() {
78
79 @Override // binder call
80 public void onEnrollResult(long deviceId, int faceId, int remaining) {
81 mHandler.obtainMessage(MSG_ENROLL_RESULT, remaining, 0,
82 new Face(null, faceId, deviceId)).sendToTarget();
83 }
84
85 @Override // binder call
86 public void onAcquired(long deviceId, int acquireInfo, int vendorCode) {
87 mHandler.obtainMessage(MSG_ACQUIRED, acquireInfo, vendorCode, deviceId).sendToTarget();
88 }
89
90 @Override // binder call
91 public void onAuthenticationSucceeded(long deviceId, Face face) {
92 mHandler.obtainMessage(MSG_AUTHENTICATION_SUCCEEDED, face).sendToTarget();
93 }
94
95 @Override // binder call
96 public void onAuthenticationFailed(long deviceId) {
97 mHandler.obtainMessage(MSG_AUTHENTICATION_FAILED).sendToTarget();
98 }
99
100 @Override // binder call
101 public void onError(long deviceId, int error, int vendorCode) {
102 mHandler.obtainMessage(MSG_ERROR, error, vendorCode, deviceId).sendToTarget();
103 }
104
105 @Override // binder call
106 public void onRemoved(long deviceId, int faceId, int remaining) {
107 mHandler.obtainMessage(MSG_REMOVED, remaining, 0,
108 new Face(null, faceId, deviceId)).sendToTarget();
109 }
Kevin Chyn6737c572019-02-08 16:10:54 -0800110
111 @Override
112 public void onEnumerated(long deviceId, int faceId, int remaining) {
113 // TODO: Finish. Low priority since it's not used.
114 }
Gilad Brettercb51b8b2018-03-22 17:04:51 +0200115 };
116
117 /**
118 * @hide
119 */
120 public FaceManager(Context context, IFaceService service) {
121 mContext = context;
122 mService = service;
123 if (mService == null) {
124 Slog.v(TAG, "FaceAuthenticationManagerService was null");
125 }
126 mHandler = new MyHandler(context);
127 }
128
129 /**
130 * Request authentication of a crypto object. This call operates the face recognition hardware
131 * and starts capturing images. It terminates when
132 * {@link AuthenticationCallback#onAuthenticationError(int, CharSequence)} or
133 * {@link AuthenticationCallback#onAuthenticationSucceeded(AuthenticationResult)} is called, at
134 * which point the object is no longer valid. The operation can be canceled by using the
135 * provided cancel object.
136 *
137 * @param crypto object associated with the call or null if none required.
138 * @param cancel an object that can be used to cancel authentication
139 * @param flags optional flags; should be 0
140 * @param callback an object to receive authentication events
141 * @param handler an optional handler to handle callback events
142 * @throws IllegalArgumentException if the crypto operation is not supported or is not backed
143 * by
144 * <a href="{@docRoot}training/articles/keystore.html">Android
145 * Keystore facility</a>.
146 * @throws IllegalStateException if the crypto primitive is not initialized.
Gilad Brettercb51b8b2018-03-22 17:04:51 +0200147 * @hide
148 */
Kevin Chyna24e9fd2018-08-27 12:39:17 -0700149 @RequiresPermission(USE_BIOMETRIC_INTERNAL)
Gilad Brettercb51b8b2018-03-22 17:04:51 +0200150 public void authenticate(@Nullable CryptoObject crypto, @Nullable CancellationSignal cancel,
Kevin Chyna56dff72018-06-19 18:41:12 -0700151 int flags, @NonNull AuthenticationCallback callback, @Nullable Handler handler) {
Gilad Brettercb51b8b2018-03-22 17:04:51 +0200152 if (callback == null) {
153 throw new IllegalArgumentException("Must supply an authentication callback");
154 }
155
156 if (cancel != null) {
157 if (cancel.isCanceled()) {
158 Log.w(TAG, "authentication already canceled");
159 return;
160 } else {
161 cancel.setOnCancelListener(new OnAuthenticationCancelListener(crypto));
162 }
163 }
164
165 if (mService != null) {
166 try {
167 useHandler(handler);
168 mAuthenticationCallback = callback;
169 mCryptoObject = crypto;
170 long sessionId = crypto != null ? crypto.getOpId() : 0;
Kevin Chyn747e29b2019-01-11 17:01:53 -0800171 mService.authenticate(mToken, sessionId, mContext.getUserId(), mServiceReceiver,
172 flags, mContext.getOpPackageName());
Gilad Brettercb51b8b2018-03-22 17:04:51 +0200173 } catch (RemoteException e) {
174 Log.w(TAG, "Remote exception while authenticating: ", e);
175 if (callback != null) {
Kevin Chyn8b7a0372018-09-17 15:06:05 -0700176 // Though this may not be a hardware issue, it will cause apps to give up or
177 // try again later.
Gilad Brettercb51b8b2018-03-22 17:04:51 +0200178 callback.onAuthenticationError(FACE_ERROR_HW_UNAVAILABLE,
Kevin Chyn8b7a0372018-09-17 15:06:05 -0700179 getErrorString(mContext, FACE_ERROR_HW_UNAVAILABLE,
180 0 /* vendorCode */));
Gilad Brettercb51b8b2018-03-22 17:04:51 +0200181 }
182 }
183 }
184 }
185
186 /**
Kevin Chyna56dff72018-06-19 18:41:12 -0700187 * Use the provided handler thread for events.
188 */
189 private void useHandler(Handler handler) {
190 if (handler != null) {
191 mHandler = new MyHandler(handler.getLooper());
192 } else if (mHandler.getLooper() != mContext.getMainLooper()) {
193 mHandler = new MyHandler(mContext.getMainLooper());
194 }
195 }
196
197 /**
Gilad Brettercb51b8b2018-03-22 17:04:51 +0200198 * Request face authentication enrollment. This call operates the face authentication hardware
199 * and starts capturing images. Progress will be indicated by callbacks to the
200 * {@link EnrollmentCallback} object. It terminates when
201 * {@link EnrollmentCallback#onEnrollmentError(int, CharSequence)} or
202 * {@link EnrollmentCallback#onEnrollmentProgress(int) is called with remaining == 0, at
203 * which point the object is no longer valid. The operation can be canceled by using the
204 * provided cancel object.
205 *
206 * @param token a unique token provided by a recent creation or verification of device
207 * credentials (e.g. pin, pattern or password).
208 * @param cancel an object that can be used to cancel enrollment
209 * @param flags optional flags
210 * @param userId the user to whom this face will belong to
211 * @param callback an object to receive enrollment events
212 * @hide
213 */
Kevin Chyna24e9fd2018-08-27 12:39:17 -0700214 @RequiresPermission(MANAGE_BIOMETRIC)
Kevin Chyn1f16c2d2018-12-07 13:06:08 -0800215 public void enroll(byte[] token, CancellationSignal cancel,
216 EnrollmentCallback callback, int[] disabledFeatures) {
Gilad Brettercb51b8b2018-03-22 17:04:51 +0200217 if (callback == null) {
218 throw new IllegalArgumentException("Must supply an enrollment callback");
219 }
220
221 if (cancel != null) {
222 if (cancel.isCanceled()) {
223 Log.w(TAG, "enrollment already canceled");
224 return;
225 } else {
226 cancel.setOnCancelListener(new OnEnrollCancelListener());
227 }
228 }
229
230 if (mService != null) {
231 try {
232 mEnrollmentCallback = callback;
Kevin Chyn1f16c2d2018-12-07 13:06:08 -0800233 mService.enroll(mToken, token, mServiceReceiver,
234 mContext.getOpPackageName(), disabledFeatures);
Gilad Brettercb51b8b2018-03-22 17:04:51 +0200235 } catch (RemoteException e) {
236 Log.w(TAG, "Remote exception in enroll: ", e);
237 if (callback != null) {
Kevin Chyn8b7a0372018-09-17 15:06:05 -0700238 // Though this may not be a hardware issue, it will cause apps to give up or
239 // try again later.
Gilad Brettercb51b8b2018-03-22 17:04:51 +0200240 callback.onEnrollmentError(FACE_ERROR_HW_UNAVAILABLE,
Kevin Chyn8b7a0372018-09-17 15:06:05 -0700241 getErrorString(mContext, FACE_ERROR_HW_UNAVAILABLE,
242 0 /* vendorCode */));
Gilad Brettercb51b8b2018-03-22 17:04:51 +0200243 }
244 }
245 }
246 }
247
248 /**
Kevin Chynd79e24e2018-09-25 12:06:59 -0700249 * Requests an auth token to tie sensitive operations to the confirmation of
Gilad Brettercb51b8b2018-03-22 17:04:51 +0200250 * existing device credentials (e.g. pin/pattern/password).
251 *
252 * @hide
253 */
Kevin Chyna24e9fd2018-08-27 12:39:17 -0700254 @RequiresPermission(MANAGE_BIOMETRIC)
Kevin Chynd79e24e2018-09-25 12:06:59 -0700255 public long generateChallenge() {
Gilad Brettercb51b8b2018-03-22 17:04:51 +0200256 long result = 0;
257 if (mService != null) {
258 try {
Kevin Chynd79e24e2018-09-25 12:06:59 -0700259 result = mService.generateChallenge(mToken);
Gilad Brettercb51b8b2018-03-22 17:04:51 +0200260 } catch (RemoteException e) {
261 throw e.rethrowFromSystemServer();
262 }
263 }
264 return result;
265 }
266
267 /**
Kevin Chynd79e24e2018-09-25 12:06:59 -0700268 * Invalidates the current auth token.
Gilad Brettercb51b8b2018-03-22 17:04:51 +0200269 *
270 * @hide
271 */
Kevin Chyna24e9fd2018-08-27 12:39:17 -0700272 @RequiresPermission(MANAGE_BIOMETRIC)
Kevin Chynd79e24e2018-09-25 12:06:59 -0700273 public int revokeChallenge() {
Gilad Brettercb51b8b2018-03-22 17:04:51 +0200274 int result = 0;
275 if (mService != null) {
276 try {
Kevin Chynd79e24e2018-09-25 12:06:59 -0700277 result = mService.revokeChallenge(mToken);
278 } catch (RemoteException e) {
279 throw e.rethrowFromSystemServer();
280 }
281 }
282 return result;
283 }
284
285 /**
286 * @hide
287 */
288 @RequiresPermission(MANAGE_BIOMETRIC)
Kevin Chynb95f1522019-03-04 16:45:15 -0800289 public boolean setFeature(int feature, boolean enabled, byte[] token) {
Kevin Chynd79e24e2018-09-25 12:06:59 -0700290 if (mService != null) {
291 try {
Kevin Chynb95f1522019-03-04 16:45:15 -0800292 return mService.setFeature(feature, enabled, token);
Kevin Chynd79e24e2018-09-25 12:06:59 -0700293 } catch (RemoteException e) {
294 throw e.rethrowFromSystemServer();
295 }
296 }
Kevin Chynb95f1522019-03-04 16:45:15 -0800297 return false;
Kevin Chynd79e24e2018-09-25 12:06:59 -0700298 }
299
300 /**
301 * @hide
302 */
303 @RequiresPermission(MANAGE_BIOMETRIC)
Kevin Chyn1f16c2d2018-12-07 13:06:08 -0800304 public boolean getFeature(int feature) {
Kevin Chynd79e24e2018-09-25 12:06:59 -0700305 boolean result = true;
306 if (mService != null) {
307 try {
Kevin Chyn1f16c2d2018-12-07 13:06:08 -0800308 result = mService.getFeature(feature);
Gilad Brettercb51b8b2018-03-22 17:04:51 +0200309 } catch (RemoteException e) {
310 throw e.rethrowFromSystemServer();
311 }
312 }
313 return result;
314 }
315
316 /**
Kevin Chyn57f119b2018-10-25 12:03:41 -0700317 * Pokes the the driver to have it start looking for faces again.
318 * @hide
319 */
320 @RequiresPermission(MANAGE_BIOMETRIC)
321 public void userActivity() {
322 if (mService != null) {
323 try {
324 mService.userActivity();
325 } catch (RemoteException e) {
326 throw e.rethrowFromSystemServer();
327 }
328 }
329 }
330
331 /**
Gilad Brettercb51b8b2018-03-22 17:04:51 +0200332 * Sets the active user. This is meant to be used to select the current profile for enrollment
333 * to allow separate enrolled faces for a work profile
334 *
335 * @hide
336 */
Kevin Chyna24e9fd2018-08-27 12:39:17 -0700337 @RequiresPermission(MANAGE_BIOMETRIC)
Kevin Chynbf830a32018-10-07 15:58:46 -0700338 @Override
Gilad Brettercb51b8b2018-03-22 17:04:51 +0200339 public void setActiveUser(int userId) {
340 if (mService != null) {
341 try {
342 mService.setActiveUser(userId);
343 } catch (RemoteException e) {
344 throw e.rethrowFromSystemServer();
345 }
346 }
347 }
348
349 /**
350 * Remove given face template from face hardware and/or protected storage.
351 *
352 * @param face the face item to remove
353 * @param userId the user who this face belongs to
354 * @param callback an optional callback to verify that face templates have been
355 * successfully removed. May be null if no callback is required.
356 * @hide
357 */
Kevin Chyna24e9fd2018-08-27 12:39:17 -0700358 @RequiresPermission(MANAGE_BIOMETRIC)
Gilad Brettercb51b8b2018-03-22 17:04:51 +0200359 public void remove(Face face, int userId, RemovalCallback callback) {
360 if (mService != null) {
361 try {
362 mRemovalCallback = callback;
363 mRemovalFace = face;
Kevin Chynb528d692018-07-20 11:53:14 -0700364 mService.remove(mToken, face.getBiometricId(), userId, mServiceReceiver);
Gilad Brettercb51b8b2018-03-22 17:04:51 +0200365 } catch (RemoteException e) {
366 Log.w(TAG, "Remote exception in remove: ", e);
367 if (callback != null) {
368 callback.onRemovalError(face, FACE_ERROR_HW_UNAVAILABLE,
Kevin Chyn8b7a0372018-09-17 15:06:05 -0700369 getErrorString(mContext, FACE_ERROR_HW_UNAVAILABLE,
370 0 /* vendorCode */));
Gilad Brettercb51b8b2018-03-22 17:04:51 +0200371 }
372 }
373 }
374 }
375
376 /**
377 * Obtain the enrolled face template.
378 *
379 * @return the current face item
380 * @hide
381 */
Kevin Chyna24e9fd2018-08-27 12:39:17 -0700382 @RequiresPermission(MANAGE_BIOMETRIC)
Kevin Chyna56dff72018-06-19 18:41:12 -0700383 public List<Face> getEnrolledFaces(int userId) {
Gilad Brettercb51b8b2018-03-22 17:04:51 +0200384 if (mService != null) {
385 try {
Kevin Chyna56dff72018-06-19 18:41:12 -0700386 return mService.getEnrolledFaces(userId, mContext.getOpPackageName());
Gilad Brettercb51b8b2018-03-22 17:04:51 +0200387 } catch (RemoteException e) {
388 throw e.rethrowFromSystemServer();
389 }
390 }
391 return null;
392 }
393
394 /**
395 * Obtain the enrolled face template.
396 *
397 * @return the current face item
398 * @hide
399 */
Kevin Chyna24e9fd2018-08-27 12:39:17 -0700400 @RequiresPermission(MANAGE_BIOMETRIC)
Kevin Chyna56dff72018-06-19 18:41:12 -0700401 public List<Face> getEnrolledFaces() {
402 return getEnrolledFaces(UserHandle.myUserId());
Gilad Brettercb51b8b2018-03-22 17:04:51 +0200403 }
404
405 /**
406 * Determine if there is a face enrolled.
407 *
408 * @return true if a face is enrolled, false otherwise
409 */
Kevin Chyna24e9fd2018-08-27 12:39:17 -0700410 @RequiresPermission(USE_BIOMETRIC_INTERNAL)
411 @Override
412 public boolean hasEnrolledTemplates() {
Gilad Brettercb51b8b2018-03-22 17:04:51 +0200413 if (mService != null) {
414 try {
Kevin Chyna56dff72018-06-19 18:41:12 -0700415 return mService.hasEnrolledFaces(
Gilad Brettercb51b8b2018-03-22 17:04:51 +0200416 UserHandle.myUserId(), mContext.getOpPackageName());
417 } catch (RemoteException e) {
418 throw e.rethrowFromSystemServer();
419 }
420 }
421 return false;
422 }
423
424 /**
425 * @hide
426 */
427 @RequiresPermission(allOf = {
Kevin Chyna24e9fd2018-08-27 12:39:17 -0700428 USE_BIOMETRIC_INTERNAL,
Gilad Brettercb51b8b2018-03-22 17:04:51 +0200429 INTERACT_ACROSS_USERS})
Kevin Chyn75dbb832018-10-05 17:32:57 -0700430 @Override
Kevin Chyna24e9fd2018-08-27 12:39:17 -0700431 public boolean hasEnrolledTemplates(int userId) {
Gilad Brettercb51b8b2018-03-22 17:04:51 +0200432 if (mService != null) {
433 try {
Kevin Chyna56dff72018-06-19 18:41:12 -0700434 return mService.hasEnrolledFaces(userId, mContext.getOpPackageName());
Gilad Brettercb51b8b2018-03-22 17:04:51 +0200435 } catch (RemoteException e) {
436 throw e.rethrowFromSystemServer();
437 }
438 }
439 return false;
440 }
441
442 /**
443 * Determine if face authentication sensor hardware is present and functional.
444 *
445 * @return true if hardware is present and functional, false otherwise.
446 */
Kevin Chyna24e9fd2018-08-27 12:39:17 -0700447 @RequiresPermission(USE_BIOMETRIC_INTERNAL)
448 @Override
Gilad Brettercb51b8b2018-03-22 17:04:51 +0200449 public boolean isHardwareDetected() {
450 if (mService != null) {
451 try {
452 long deviceId = 0; /* TODO: plumb hardware id to FPMS */
453 return mService.isHardwareDetected(deviceId, mContext.getOpPackageName());
454 } catch (RemoteException e) {
455 throw e.rethrowFromSystemServer();
456 }
457 } else {
458 Log.w(TAG, "isFaceHardwareDetected(): Service not connected!");
459 }
460 return false;
461 }
462
463 /**
464 * Retrieves the authenticator token for binding keys to the lifecycle
465 * of the calling user's face. Used only by internal clients.
466 *
467 * @hide
468 */
469 public long getAuthenticatorId() {
470 if (mService != null) {
471 try {
472 return mService.getAuthenticatorId(mContext.getOpPackageName());
473 } catch (RemoteException e) {
474 throw e.rethrowFromSystemServer();
475 }
476 } else {
477 Log.w(TAG, "getAuthenticatorId(): Service not connected!");
478 }
479 return 0;
480 }
481
482 /**
Gilad Brettercb51b8b2018-03-22 17:04:51 +0200483 * @hide
484 */
Kevin Chyna24e9fd2018-08-27 12:39:17 -0700485 @RequiresPermission(USE_BIOMETRIC_INTERNAL)
Gilad Brettercb51b8b2018-03-22 17:04:51 +0200486 public void addLockoutResetCallback(final LockoutResetCallback callback) {
487 if (mService != null) {
488 try {
489 final PowerManager powerManager = mContext.getSystemService(PowerManager.class);
490 mService.addLockoutResetCallback(
Kevin Chyna56dff72018-06-19 18:41:12 -0700491 new IBiometricServiceLockoutResetCallback.Stub() {
Gilad Brettercb51b8b2018-03-22 17:04:51 +0200492
493 @Override
494 public void onLockoutReset(long deviceId,
495 IRemoteCallback serverCallback)
496 throws RemoteException {
497 try {
498 final PowerManager.WakeLock wakeLock = powerManager.newWakeLock(
499 PowerManager.PARTIAL_WAKE_LOCK,
500 "faceLockoutResetCallback");
501 wakeLock.acquire();
502 mHandler.post(() -> {
503 try {
504 callback.onLockoutReset();
505 } finally {
506 wakeLock.release();
507 }
508 });
509 } finally {
510 serverCallback.sendResult(null /* data */);
511 }
512 }
513 });
514 } catch (RemoteException e) {
515 throw e.rethrowFromSystemServer();
516 }
517 } else {
518 Log.w(TAG, "addLockoutResetCallback(): Service not connected!");
519 }
520 }
521
522 private int getCurrentUserId() {
523 try {
524 return ActivityManager.getService().getCurrentUser().id;
525 } catch (RemoteException e) {
526 throw e.rethrowFromSystemServer();
527 }
528 }
529
530 private void cancelEnrollment() {
531 if (mService != null) {
532 try {
533 mService.cancelEnrollment(mToken);
534 } catch (RemoteException e) {
535 throw e.rethrowFromSystemServer();
536 }
537 }
538 }
539
540 private void cancelAuthentication(CryptoObject cryptoObject) {
541 if (mService != null) {
542 try {
543 mService.cancelAuthentication(mToken, mContext.getOpPackageName());
544 } catch (RemoteException e) {
545 throw e.rethrowFromSystemServer();
546 }
547 }
548 }
549
Kevin Chyn8b7a0372018-09-17 15:06:05 -0700550 /**
551 * @hide
552 */
553 public static String getErrorString(Context context, int errMsg, int vendorCode) {
Gilad Brettercb51b8b2018-03-22 17:04:51 +0200554 switch (errMsg) {
Kevin Chyn6cf54e82018-09-18 19:13:27 -0700555 case FACE_ERROR_HW_UNAVAILABLE:
556 return context.getString(
557 com.android.internal.R.string.face_error_hw_not_available);
Kevin Chyn68823b02019-02-06 08:59:16 -0800558 case FACE_ERROR_UNABLE_TO_PROCESS:
559 return context.getString(
560 com.android.internal.R.string.face_error_unable_to_process);
Kevin Chyn6cf54e82018-09-18 19:13:27 -0700561 case FACE_ERROR_TIMEOUT:
562 return context.getString(com.android.internal.R.string.face_error_timeout);
Kevin Chyn68823b02019-02-06 08:59:16 -0800563 case FACE_ERROR_NO_SPACE:
564 return context.getString(com.android.internal.R.string.face_error_no_space);
Gilad Brettercb51b8b2018-03-22 17:04:51 +0200565 case FACE_ERROR_CANCELED:
Kevin Chyn8b7a0372018-09-17 15:06:05 -0700566 return context.getString(com.android.internal.R.string.face_error_canceled);
Gilad Brettercb51b8b2018-03-22 17:04:51 +0200567 case FACE_ERROR_LOCKOUT:
Kevin Chyn8b7a0372018-09-17 15:06:05 -0700568 return context.getString(com.android.internal.R.string.face_error_lockout);
Gilad Brettercb51b8b2018-03-22 17:04:51 +0200569 case FACE_ERROR_LOCKOUT_PERMANENT:
Kevin Chyn8b7a0372018-09-17 15:06:05 -0700570 return context.getString(
Gilad Brettercb51b8b2018-03-22 17:04:51 +0200571 com.android.internal.R.string.face_error_lockout_permanent);
Kevin Chyn6cf54e82018-09-18 19:13:27 -0700572 case FACE_ERROR_USER_CANCELED:
573 return context.getString(com.android.internal.R.string.face_error_user_canceled);
Gilad Brettercb51b8b2018-03-22 17:04:51 +0200574 case FACE_ERROR_NOT_ENROLLED:
Kevin Chyn8b7a0372018-09-17 15:06:05 -0700575 return context.getString(com.android.internal.R.string.face_error_not_enrolled);
Gilad Brettercb51b8b2018-03-22 17:04:51 +0200576 case FACE_ERROR_HW_NOT_PRESENT:
Kevin Chyn8b7a0372018-09-17 15:06:05 -0700577 return context.getString(com.android.internal.R.string.face_error_hw_not_present);
Gilad Brettercb51b8b2018-03-22 17:04:51 +0200578 case FACE_ERROR_VENDOR: {
Kevin Chyn8b7a0372018-09-17 15:06:05 -0700579 String[] msgArray = context.getResources().getStringArray(
Gilad Brettercb51b8b2018-03-22 17:04:51 +0200580 com.android.internal.R.array.face_error_vendor);
581 if (vendorCode < msgArray.length) {
582 return msgArray[vendorCode];
583 }
584 }
585 }
586 Slog.w(TAG, "Invalid error message: " + errMsg + ", " + vendorCode);
587 return null;
588 }
589
Kevin Chyna24e9fd2018-08-27 12:39:17 -0700590 /**
591 * @hide
592 */
Kevin Chyn8b7a0372018-09-17 15:06:05 -0700593 public static String getAcquiredString(Context context, int acquireInfo, int vendorCode) {
Gilad Brettercb51b8b2018-03-22 17:04:51 +0200594 switch (acquireInfo) {
595 case FACE_ACQUIRED_GOOD:
596 return null;
597 case FACE_ACQUIRED_INSUFFICIENT:
Kevin Chyn8b7a0372018-09-17 15:06:05 -0700598 return context.getString(R.string.face_acquired_insufficient);
Gilad Brettercb51b8b2018-03-22 17:04:51 +0200599 case FACE_ACQUIRED_TOO_BRIGHT:
Kevin Chyn8b7a0372018-09-17 15:06:05 -0700600 return context.getString(R.string.face_acquired_too_bright);
Gilad Brettercb51b8b2018-03-22 17:04:51 +0200601 case FACE_ACQUIRED_TOO_DARK:
Kevin Chyn8b7a0372018-09-17 15:06:05 -0700602 return context.getString(R.string.face_acquired_too_dark);
Gilad Brettercb51b8b2018-03-22 17:04:51 +0200603 case FACE_ACQUIRED_TOO_CLOSE:
Kevin Chyn8b7a0372018-09-17 15:06:05 -0700604 return context.getString(R.string.face_acquired_too_close);
Gilad Brettercb51b8b2018-03-22 17:04:51 +0200605 case FACE_ACQUIRED_TOO_FAR:
Kevin Chyn8b7a0372018-09-17 15:06:05 -0700606 return context.getString(R.string.face_acquired_too_far);
Gilad Brettercb51b8b2018-03-22 17:04:51 +0200607 case FACE_ACQUIRED_TOO_HIGH:
Kevin Chyn8b7a0372018-09-17 15:06:05 -0700608 return context.getString(R.string.face_acquired_too_high);
Gilad Brettercb51b8b2018-03-22 17:04:51 +0200609 case FACE_ACQUIRED_TOO_LOW:
Kevin Chyn8b7a0372018-09-17 15:06:05 -0700610 return context.getString(R.string.face_acquired_too_low);
Gilad Brettercb51b8b2018-03-22 17:04:51 +0200611 case FACE_ACQUIRED_TOO_RIGHT:
Kevin Chyn8b7a0372018-09-17 15:06:05 -0700612 return context.getString(R.string.face_acquired_too_right);
Gilad Brettercb51b8b2018-03-22 17:04:51 +0200613 case FACE_ACQUIRED_TOO_LEFT:
Kevin Chyn8b7a0372018-09-17 15:06:05 -0700614 return context.getString(R.string.face_acquired_too_left);
Gilad Brettercb51b8b2018-03-22 17:04:51 +0200615 case FACE_ACQUIRED_POOR_GAZE:
Kevin Chyn8b7a0372018-09-17 15:06:05 -0700616 return context.getString(R.string.face_acquired_poor_gaze);
Gilad Brettercb51b8b2018-03-22 17:04:51 +0200617 case FACE_ACQUIRED_NOT_DETECTED:
Kevin Chyn8b7a0372018-09-17 15:06:05 -0700618 return context.getString(R.string.face_acquired_not_detected);
Kevin Chyn68823b02019-02-06 08:59:16 -0800619 case FACE_ACQUIRED_TOO_MUCH_MOTION:
620 return context.getString(R.string.face_acquired_too_much_motion);
621 case FACE_ACQUIRED_RECALIBRATE:
622 return context.getString(R.string.face_acquired_recalibrate);
623 case FACE_ACQUIRED_TOO_DIFFERENT:
624 return context.getString(R.string.face_acquired_too_different);
625 case FACE_ACQUIRED_TOO_SIMILAR:
626 return context.getString(R.string.face_acquired_too_similar);
627 case FACE_ACQUIRED_PAN_TOO_EXTREME:
628 return context.getString(R.string.face_acquired_pan_too_extreme);
629 case FACE_ACQUIRED_TILT_TOO_EXTREME:
630 return context.getString(R.string.face_acquired_tilt_too_extreme);
631 case FACE_ACQUIRED_ROLL_TOO_EXTREME:
632 return context.getString(R.string.face_acquired_roll_too_extreme);
633 case FACE_ACQUIRED_FACE_OBSCURED:
634 return context.getString(R.string.face_acquired_obscured);
635 case FACE_ACQUIRED_START:
636 return null;
Gilad Brettercb51b8b2018-03-22 17:04:51 +0200637 case FACE_ACQUIRED_VENDOR: {
Kevin Chyn8b7a0372018-09-17 15:06:05 -0700638 String[] msgArray = context.getResources().getStringArray(
Gilad Brettercb51b8b2018-03-22 17:04:51 +0200639 R.array.face_acquired_vendor);
640 if (vendorCode < msgArray.length) {
641 return msgArray[vendorCode];
642 }
643 }
644 }
645 Slog.w(TAG, "Invalid acquired message: " + acquireInfo + ", " + vendorCode);
646 return null;
647 }
648
649 /**
Kevin Chyna24e9fd2018-08-27 12:39:17 -0700650 * Used so BiometricPrompt can map the face ones onto existing public constants.
651 * @hide
652 */
Kevin Chyn8b7a0372018-09-17 15:06:05 -0700653 public static int getMappedAcquiredInfo(int acquireInfo, int vendorCode) {
Kevin Chyna24e9fd2018-08-27 12:39:17 -0700654 switch (acquireInfo) {
655 case FACE_ACQUIRED_GOOD:
656 return BiometricConstants.BIOMETRIC_ACQUIRED_GOOD;
657 case FACE_ACQUIRED_INSUFFICIENT:
658 case FACE_ACQUIRED_TOO_BRIGHT:
659 case FACE_ACQUIRED_TOO_DARK:
660 return BiometricConstants.BIOMETRIC_ACQUIRED_INSUFFICIENT;
661 case FACE_ACQUIRED_TOO_CLOSE:
662 case FACE_ACQUIRED_TOO_FAR:
663 case FACE_ACQUIRED_TOO_HIGH:
664 case FACE_ACQUIRED_TOO_LOW:
665 case FACE_ACQUIRED_TOO_RIGHT:
666 case FACE_ACQUIRED_TOO_LEFT:
667 return BiometricConstants.BIOMETRIC_ACQUIRED_PARTIAL;
668 case FACE_ACQUIRED_POOR_GAZE:
669 case FACE_ACQUIRED_NOT_DETECTED:
670 case FACE_ACQUIRED_TOO_MUCH_MOTION:
671 case FACE_ACQUIRED_RECALIBRATE:
672 return BiometricConstants.BIOMETRIC_ACQUIRED_INSUFFICIENT;
673 case FACE_ACQUIRED_VENDOR:
674 return BiometricConstants.BIOMETRIC_ACQUIRED_VENDOR_BASE + vendorCode;
675 default:
676 return BiometricConstants.BIOMETRIC_ACQUIRED_GOOD;
677 }
678 }
679
680 /**
Gilad Brettercb51b8b2018-03-22 17:04:51 +0200681 * Container for callback data from {@link FaceManager#authenticate(CryptoObject,
682 * CancellationSignal, int, AuthenticationCallback, Handler)}.
683 */
684 public static class AuthenticationResult {
685 private Face mFace;
686 private CryptoObject mCryptoObject;
687 private int mUserId;
688
689 /**
690 * Authentication result
691 *
692 * @param crypto the crypto object
693 * @param face the recognized face data, if allowed.
694 * @hide
695 */
696 public AuthenticationResult(CryptoObject crypto, Face face, int userId) {
697 mCryptoObject = crypto;
698 mFace = face;
699 mUserId = userId;
700 }
701
702 /**
703 * Obtain the crypto object associated with this transaction
704 *
705 * @return crypto object provided to {@link FaceManager#authenticate
706 * (CryptoObject,
707 * CancellationSignal, int, AuthenticationCallback, Handler)}.
708 */
709 public CryptoObject getCryptoObject() {
710 return mCryptoObject;
711 }
712
713 /**
714 * Obtain the Face associated with this operation. Applications are strongly
715 * discouraged from associating specific faces with specific applications or operations.
716 *
717 * @hide
718 */
719 public Face getFace() {
720 return mFace;
721 }
722
723 /**
724 * Obtain the userId for which this face was authenticated.
725 *
726 * @hide
727 */
728 public int getUserId() {
729 return mUserId;
730 }
731 }
732
733 /**
734 * Callback structure provided to {@link FaceManager#authenticate(CryptoObject,
735 * CancellationSignal, int, AuthenticationCallback, Handler)}. Users of {@link
736 * FaceManager#authenticate(CryptoObject, CancellationSignal,
737 * int, AuthenticationCallback, Handler) } must provide an implementation of this for listening
738 * to face events.
739 */
Kevin Chyna56dff72018-06-19 18:41:12 -0700740 public abstract static class AuthenticationCallback
741 extends BiometricAuthenticator.AuthenticationCallback {
Gilad Brettercb51b8b2018-03-22 17:04:51 +0200742
743 /**
744 * Called when an unrecoverable error has been encountered and the operation is complete.
745 * No further callbacks will be made on this object.
746 *
747 * @param errorCode An integer identifying the error message
748 * @param errString A human-readable error string that can be shown in UI
749 */
750 public void onAuthenticationError(int errorCode, CharSequence errString) {
751 }
752
753 /**
754 * Called when a recoverable error has been encountered during authentication. The help
755 * string is provided to give the user guidance for what went wrong, such as
756 * "Sensor dirty, please clean it."
757 *
758 * @param helpCode An integer identifying the error message
759 * @param helpString A human-readable string that can be shown in UI
760 */
761 public void onAuthenticationHelp(int helpCode, CharSequence helpString) {
762 }
763
764 /**
765 * Called when a face is recognized.
766 *
767 * @param result An object containing authentication-related data
768 */
769 public void onAuthenticationSucceeded(AuthenticationResult result) {
770 }
771
772 /**
773 * Called when a face is detected but not recognized.
774 */
775 public void onAuthenticationFailed() {
776 }
777
778 /**
779 * Called when a face image has been acquired, but wasn't processed yet.
780 *
781 * @param acquireInfo one of FACE_ACQUIRED_* constants
782 * @hide
783 */
784 public void onAuthenticationAcquired(int acquireInfo) {
785 }
786 }
787
788 /**
789 * Callback structure provided to {@link FaceManager#enroll(long,
790 * EnrollmentCallback, CancellationSignal, int). Users of {@link #FaceAuthenticationManager()}
791 * must provide an implementation of this to {@link FaceManager#enroll(long,
792 * CancellationSignal, int, EnrollmentCallback) for listening to face enrollment events.
793 *
794 * @hide
795 */
796 public abstract static class EnrollmentCallback {
797
798 /**
799 * Called when an unrecoverable error has been encountered and the operation is complete.
800 * No further callbacks will be made on this object.
801 *
802 * @param errMsgId An integer identifying the error message
803 * @param errString A human-readable error string that can be shown in UI
804 */
805 public void onEnrollmentError(int errMsgId, CharSequence errString) {
806 }
807
808 /**
809 * Called when a recoverable error has been encountered during enrollment. The help
810 * string is provided to give the user guidance for what went wrong, such as
811 * "Image too dark, uncover light source" or what they need to do next, such as
812 * "Rotate face up / down."
813 *
814 * @param helpMsgId An integer identifying the error message
815 * @param helpString A human-readable string that can be shown in UI
816 */
817 public void onEnrollmentHelp(int helpMsgId, CharSequence helpString) {
818 }
819
820 /**
821 * Called as each enrollment step progresses. Enrollment is considered complete when
822 * remaining reaches 0. This function will not be called if enrollment fails. See
823 * {@link EnrollmentCallback#onEnrollmentError(int, CharSequence)}
824 *
825 * @param remaining The number of remaining steps
Gilad Brettercb51b8b2018-03-22 17:04:51 +0200826 */
Kevin Chyn8277f002018-07-02 15:14:44 -0700827 public void onEnrollmentProgress(int remaining) {
Gilad Brettercb51b8b2018-03-22 17:04:51 +0200828 }
829 }
830
831 /**
832 * Callback structure provided to {@link #remove}. Users of {@link FaceManager}
833 * may
834 * optionally provide an implementation of this to
835 * {@link #remove(Face, int, RemovalCallback)} for listening to face template
836 * removal events.
837 *
838 * @hide
839 */
840 public abstract static class RemovalCallback {
841
842 /**
843 * Called when the given face can't be removed.
844 *
845 * @param face The face that the call attempted to remove
846 * @param errMsgId An associated error message id
847 * @param errString An error message indicating why the face id can't be removed
848 */
849 public void onRemovalError(Face face, int errMsgId, CharSequence errString) {
850 }
851
852 /**
853 * Called when a given face is successfully removed.
854 *
855 * @param face The face template that was removed.
856 */
Kevin Chyn7e88d1132018-06-29 12:55:22 -0700857 public void onRemovalSucceeded(Face face, int remaining) {
Gilad Brettercb51b8b2018-03-22 17:04:51 +0200858 }
859 }
860
861 /**
862 * @hide
863 */
864 public abstract static class LockoutResetCallback {
865
866 /**
867 * Called when lockout period expired and clients are allowed to listen for face
868 * authentication
869 * again.
870 */
871 public void onLockoutReset() {
872 }
873 }
874
875 private class OnEnrollCancelListener implements OnCancelListener {
876 @Override
877 public void onCancel() {
878 cancelEnrollment();
879 }
880 }
881
882 private class OnAuthenticationCancelListener implements OnCancelListener {
883 private CryptoObject mCrypto;
884
885 OnAuthenticationCancelListener(CryptoObject crypto) {
886 mCrypto = crypto;
887 }
888
889 @Override
890 public void onCancel() {
891 cancelAuthentication(mCrypto);
892 }
893 }
894
895 private class MyHandler extends Handler {
896 private MyHandler(Context context) {
897 super(context.getMainLooper());
898 }
899
900 private MyHandler(Looper looper) {
901 super(looper);
902 }
903
904 @Override
905 public void handleMessage(android.os.Message msg) {
906 switch (msg.what) {
907 case MSG_ENROLL_RESULT:
Kevin Chyn8277f002018-07-02 15:14:44 -0700908 sendEnrollResult((Face) msg.obj, msg.arg1 /* remaining */);
Gilad Brettercb51b8b2018-03-22 17:04:51 +0200909 break;
910 case MSG_ACQUIRED:
911 sendAcquiredResult((Long) msg.obj /* deviceId */, msg.arg1 /* acquire info */,
912 msg.arg2 /* vendorCode */);
913 break;
914 case MSG_AUTHENTICATION_SUCCEEDED:
915 sendAuthenticatedSucceeded((Face) msg.obj, msg.arg1 /* userId */);
916 break;
917 case MSG_AUTHENTICATION_FAILED:
918 sendAuthenticatedFailed();
919 break;
920 case MSG_ERROR:
921 sendErrorResult((Long) msg.obj /* deviceId */, msg.arg1 /* errMsgId */,
922 msg.arg2 /* vendorCode */);
923 break;
924 case MSG_REMOVED:
Kevin Chyn7e88d1132018-06-29 12:55:22 -0700925 sendRemovedResult((Face) msg.obj, msg.arg1 /* remaining */);
Gilad Brettercb51b8b2018-03-22 17:04:51 +0200926 break;
927 }
928 }
Kevin Chyna56dff72018-06-19 18:41:12 -0700929 };
Gilad Brettercb51b8b2018-03-22 17:04:51 +0200930
Kevin Chyn7e88d1132018-06-29 12:55:22 -0700931 private void sendRemovedResult(Face face, int remaining) {
Kevin Chyna56dff72018-06-19 18:41:12 -0700932 if (mRemovalCallback == null) {
933 return;
934 }
935 if (face == null) {
936 Log.e(TAG, "Received MSG_REMOVED, but face is null");
937 return;
Gilad Brettercb51b8b2018-03-22 17:04:51 +0200938 }
Kevin Chyn7e88d1132018-06-29 12:55:22 -0700939 mRemovalCallback.onRemovalSucceeded(face, remaining);
Kevin Chyna56dff72018-06-19 18:41:12 -0700940 }
Gilad Brettercb51b8b2018-03-22 17:04:51 +0200941
Kevin Chyna56dff72018-06-19 18:41:12 -0700942 private void sendErrorResult(long deviceId, int errMsgId, int vendorCode) {
943 // emulate HAL 2.1 behavior and send real errMsgId
944 final int clientErrMsgId = errMsgId == FACE_ERROR_VENDOR
945 ? (vendorCode + FACE_ERROR_VENDOR_BASE) : errMsgId;
946 if (mEnrollmentCallback != null) {
947 mEnrollmentCallback.onEnrollmentError(clientErrMsgId,
Kevin Chyn8b7a0372018-09-17 15:06:05 -0700948 getErrorString(mContext, errMsgId, vendorCode));
Kevin Chyna56dff72018-06-19 18:41:12 -0700949 } else if (mAuthenticationCallback != null) {
950 mAuthenticationCallback.onAuthenticationError(clientErrMsgId,
Kevin Chyn8b7a0372018-09-17 15:06:05 -0700951 getErrorString(mContext, errMsgId, vendorCode));
Kevin Chyna56dff72018-06-19 18:41:12 -0700952 } else if (mRemovalCallback != null) {
953 mRemovalCallback.onRemovalError(mRemovalFace, clientErrMsgId,
Kevin Chyn8b7a0372018-09-17 15:06:05 -0700954 getErrorString(mContext, errMsgId, vendorCode));
Gilad Brettercb51b8b2018-03-22 17:04:51 +0200955 }
Kevin Chyna56dff72018-06-19 18:41:12 -0700956 }
Gilad Brettercb51b8b2018-03-22 17:04:51 +0200957
Kevin Chyn8277f002018-07-02 15:14:44 -0700958 private void sendEnrollResult(Face face, int remaining) {
Kevin Chyna56dff72018-06-19 18:41:12 -0700959 if (mEnrollmentCallback != null) {
Kevin Chyn8277f002018-07-02 15:14:44 -0700960 mEnrollmentCallback.onEnrollmentProgress(remaining);
Gilad Brettercb51b8b2018-03-22 17:04:51 +0200961 }
Kevin Chyna56dff72018-06-19 18:41:12 -0700962 }
Gilad Brettercb51b8b2018-03-22 17:04:51 +0200963
Kevin Chyna56dff72018-06-19 18:41:12 -0700964 private void sendAuthenticatedSucceeded(Face face, int userId) {
965 if (mAuthenticationCallback != null) {
Kevin Chyna24e9fd2018-08-27 12:39:17 -0700966 final AuthenticationResult result =
967 new AuthenticationResult(mCryptoObject, face, userId);
Kevin Chyna56dff72018-06-19 18:41:12 -0700968 mAuthenticationCallback.onAuthenticationSucceeded(result);
969 }
970 }
971
972 private void sendAuthenticatedFailed() {
973 if (mAuthenticationCallback != null) {
974 mAuthenticationCallback.onAuthenticationFailed();
975 }
976 }
977
978 private void sendAcquiredResult(long deviceId, int acquireInfo, int vendorCode) {
979 if (mAuthenticationCallback != null) {
980 mAuthenticationCallback.onAuthenticationAcquired(acquireInfo);
981 }
Kevin Chyn8b7a0372018-09-17 15:06:05 -0700982 final String msg = getAcquiredString(mContext, acquireInfo, vendorCode);
Kevin Chyna56dff72018-06-19 18:41:12 -0700983 final int clientInfo = acquireInfo == FACE_ACQUIRED_VENDOR
984 ? (vendorCode + FACE_ACQUIRED_VENDOR_BASE) : acquireInfo;
985 if (mEnrollmentCallback != null) {
986 mEnrollmentCallback.onEnrollmentHelp(clientInfo, msg);
Kevin Chynffef7142018-12-27 16:28:48 -0800987 } else if (mAuthenticationCallback != null && msg != null) {
Kevin Chyna56dff72018-06-19 18:41:12 -0700988 mAuthenticationCallback.onAuthenticationHelp(clientInfo, msg);
Gilad Brettercb51b8b2018-03-22 17:04:51 +0200989 }
990 }
Gilad Brettercb51b8b2018-03-22 17:04:51 +0200991}