blob: 55b340f267a66e3806995c1fd01a3904228b03c3 [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 Chyn1f16c2d2018-12-07 13:06:08 -0800289 public void setFeature(int feature, boolean enabled, byte[] token) {
Kevin Chynd79e24e2018-09-25 12:06:59 -0700290 if (mService != null) {
291 try {
Kevin Chyn1f16c2d2018-12-07 13:06:08 -0800292 mService.setFeature(feature, enabled, token);
Kevin Chynd79e24e2018-09-25 12:06:59 -0700293 } catch (RemoteException e) {
294 throw e.rethrowFromSystemServer();
295 }
296 }
297 }
298
299 /**
300 * @hide
301 */
302 @RequiresPermission(MANAGE_BIOMETRIC)
Kevin Chyn1f16c2d2018-12-07 13:06:08 -0800303 public boolean getFeature(int feature) {
Kevin Chynd79e24e2018-09-25 12:06:59 -0700304 boolean result = true;
305 if (mService != null) {
306 try {
Kevin Chyn1f16c2d2018-12-07 13:06:08 -0800307 result = mService.getFeature(feature);
Gilad Brettercb51b8b2018-03-22 17:04:51 +0200308 } catch (RemoteException e) {
309 throw e.rethrowFromSystemServer();
310 }
311 }
312 return result;
313 }
314
315 /**
Kevin Chyn57f119b2018-10-25 12:03:41 -0700316 * Pokes the the driver to have it start looking for faces again.
317 * @hide
318 */
319 @RequiresPermission(MANAGE_BIOMETRIC)
320 public void userActivity() {
321 if (mService != null) {
322 try {
323 mService.userActivity();
324 } catch (RemoteException e) {
325 throw e.rethrowFromSystemServer();
326 }
327 }
328 }
329
330 /**
Gilad Brettercb51b8b2018-03-22 17:04:51 +0200331 * Sets the active user. This is meant to be used to select the current profile for enrollment
332 * to allow separate enrolled faces for a work profile
333 *
334 * @hide
335 */
Kevin Chyna24e9fd2018-08-27 12:39:17 -0700336 @RequiresPermission(MANAGE_BIOMETRIC)
Kevin Chynbf830a32018-10-07 15:58:46 -0700337 @Override
Gilad Brettercb51b8b2018-03-22 17:04:51 +0200338 public void setActiveUser(int userId) {
339 if (mService != null) {
340 try {
341 mService.setActiveUser(userId);
342 } catch (RemoteException e) {
343 throw e.rethrowFromSystemServer();
344 }
345 }
346 }
347
348 /**
349 * Remove given face template from face hardware and/or protected storage.
350 *
351 * @param face the face item to remove
352 * @param userId the user who this face belongs to
353 * @param callback an optional callback to verify that face templates have been
354 * successfully removed. May be null if no callback is required.
355 * @hide
356 */
Kevin Chyna24e9fd2018-08-27 12:39:17 -0700357 @RequiresPermission(MANAGE_BIOMETRIC)
Gilad Brettercb51b8b2018-03-22 17:04:51 +0200358 public void remove(Face face, int userId, RemovalCallback callback) {
359 if (mService != null) {
360 try {
361 mRemovalCallback = callback;
362 mRemovalFace = face;
Kevin Chynb528d692018-07-20 11:53:14 -0700363 mService.remove(mToken, face.getBiometricId(), userId, mServiceReceiver);
Gilad Brettercb51b8b2018-03-22 17:04:51 +0200364 } catch (RemoteException e) {
365 Log.w(TAG, "Remote exception in remove: ", e);
366 if (callback != null) {
367 callback.onRemovalError(face, FACE_ERROR_HW_UNAVAILABLE,
Kevin Chyn8b7a0372018-09-17 15:06:05 -0700368 getErrorString(mContext, FACE_ERROR_HW_UNAVAILABLE,
369 0 /* vendorCode */));
Gilad Brettercb51b8b2018-03-22 17:04:51 +0200370 }
371 }
372 }
373 }
374
375 /**
376 * Obtain the enrolled face template.
377 *
378 * @return the current face item
379 * @hide
380 */
Kevin Chyna24e9fd2018-08-27 12:39:17 -0700381 @RequiresPermission(MANAGE_BIOMETRIC)
Kevin Chyna56dff72018-06-19 18:41:12 -0700382 public List<Face> getEnrolledFaces(int userId) {
Gilad Brettercb51b8b2018-03-22 17:04:51 +0200383 if (mService != null) {
384 try {
Kevin Chyna56dff72018-06-19 18:41:12 -0700385 return mService.getEnrolledFaces(userId, mContext.getOpPackageName());
Gilad Brettercb51b8b2018-03-22 17:04:51 +0200386 } catch (RemoteException e) {
387 throw e.rethrowFromSystemServer();
388 }
389 }
390 return null;
391 }
392
393 /**
394 * Obtain the enrolled face template.
395 *
396 * @return the current face item
397 * @hide
398 */
Kevin Chyna24e9fd2018-08-27 12:39:17 -0700399 @RequiresPermission(MANAGE_BIOMETRIC)
Kevin Chyna56dff72018-06-19 18:41:12 -0700400 public List<Face> getEnrolledFaces() {
401 return getEnrolledFaces(UserHandle.myUserId());
Gilad Brettercb51b8b2018-03-22 17:04:51 +0200402 }
403
404 /**
405 * Determine if there is a face enrolled.
406 *
407 * @return true if a face is enrolled, false otherwise
408 */
Kevin Chyna24e9fd2018-08-27 12:39:17 -0700409 @RequiresPermission(USE_BIOMETRIC_INTERNAL)
410 @Override
411 public boolean hasEnrolledTemplates() {
Gilad Brettercb51b8b2018-03-22 17:04:51 +0200412 if (mService != null) {
413 try {
Kevin Chyna56dff72018-06-19 18:41:12 -0700414 return mService.hasEnrolledFaces(
Gilad Brettercb51b8b2018-03-22 17:04:51 +0200415 UserHandle.myUserId(), mContext.getOpPackageName());
416 } catch (RemoteException e) {
417 throw e.rethrowFromSystemServer();
418 }
419 }
420 return false;
421 }
422
423 /**
424 * @hide
425 */
426 @RequiresPermission(allOf = {
Kevin Chyna24e9fd2018-08-27 12:39:17 -0700427 USE_BIOMETRIC_INTERNAL,
Gilad Brettercb51b8b2018-03-22 17:04:51 +0200428 INTERACT_ACROSS_USERS})
Kevin Chyn75dbb832018-10-05 17:32:57 -0700429 @Override
Kevin Chyna24e9fd2018-08-27 12:39:17 -0700430 public boolean hasEnrolledTemplates(int userId) {
Gilad Brettercb51b8b2018-03-22 17:04:51 +0200431 if (mService != null) {
432 try {
Kevin Chyna56dff72018-06-19 18:41:12 -0700433 return mService.hasEnrolledFaces(userId, mContext.getOpPackageName());
Gilad Brettercb51b8b2018-03-22 17:04:51 +0200434 } catch (RemoteException e) {
435 throw e.rethrowFromSystemServer();
436 }
437 }
438 return false;
439 }
440
441 /**
442 * Determine if face authentication sensor hardware is present and functional.
443 *
444 * @return true if hardware is present and functional, false otherwise.
445 */
Kevin Chyna24e9fd2018-08-27 12:39:17 -0700446 @RequiresPermission(USE_BIOMETRIC_INTERNAL)
447 @Override
Gilad Brettercb51b8b2018-03-22 17:04:51 +0200448 public boolean isHardwareDetected() {
449 if (mService != null) {
450 try {
451 long deviceId = 0; /* TODO: plumb hardware id to FPMS */
452 return mService.isHardwareDetected(deviceId, mContext.getOpPackageName());
453 } catch (RemoteException e) {
454 throw e.rethrowFromSystemServer();
455 }
456 } else {
457 Log.w(TAG, "isFaceHardwareDetected(): Service not connected!");
458 }
459 return false;
460 }
461
462 /**
463 * Retrieves the authenticator token for binding keys to the lifecycle
464 * of the calling user's face. Used only by internal clients.
465 *
466 * @hide
467 */
468 public long getAuthenticatorId() {
469 if (mService != null) {
470 try {
471 return mService.getAuthenticatorId(mContext.getOpPackageName());
472 } catch (RemoteException e) {
473 throw e.rethrowFromSystemServer();
474 }
475 } else {
476 Log.w(TAG, "getAuthenticatorId(): Service not connected!");
477 }
478 return 0;
479 }
480
481 /**
Gilad Brettercb51b8b2018-03-22 17:04:51 +0200482 * @hide
483 */
Kevin Chyna24e9fd2018-08-27 12:39:17 -0700484 @RequiresPermission(USE_BIOMETRIC_INTERNAL)
Gilad Brettercb51b8b2018-03-22 17:04:51 +0200485 public void addLockoutResetCallback(final LockoutResetCallback callback) {
486 if (mService != null) {
487 try {
488 final PowerManager powerManager = mContext.getSystemService(PowerManager.class);
489 mService.addLockoutResetCallback(
Kevin Chyna56dff72018-06-19 18:41:12 -0700490 new IBiometricServiceLockoutResetCallback.Stub() {
Gilad Brettercb51b8b2018-03-22 17:04:51 +0200491
492 @Override
493 public void onLockoutReset(long deviceId,
494 IRemoteCallback serverCallback)
495 throws RemoteException {
496 try {
497 final PowerManager.WakeLock wakeLock = powerManager.newWakeLock(
498 PowerManager.PARTIAL_WAKE_LOCK,
499 "faceLockoutResetCallback");
500 wakeLock.acquire();
501 mHandler.post(() -> {
502 try {
503 callback.onLockoutReset();
504 } finally {
505 wakeLock.release();
506 }
507 });
508 } finally {
509 serverCallback.sendResult(null /* data */);
510 }
511 }
512 });
513 } catch (RemoteException e) {
514 throw e.rethrowFromSystemServer();
515 }
516 } else {
517 Log.w(TAG, "addLockoutResetCallback(): Service not connected!");
518 }
519 }
520
521 private int getCurrentUserId() {
522 try {
523 return ActivityManager.getService().getCurrentUser().id;
524 } catch (RemoteException e) {
525 throw e.rethrowFromSystemServer();
526 }
527 }
528
529 private void cancelEnrollment() {
530 if (mService != null) {
531 try {
532 mService.cancelEnrollment(mToken);
533 } catch (RemoteException e) {
534 throw e.rethrowFromSystemServer();
535 }
536 }
537 }
538
539 private void cancelAuthentication(CryptoObject cryptoObject) {
540 if (mService != null) {
541 try {
542 mService.cancelAuthentication(mToken, mContext.getOpPackageName());
543 } catch (RemoteException e) {
544 throw e.rethrowFromSystemServer();
545 }
546 }
547 }
548
Kevin Chyn8b7a0372018-09-17 15:06:05 -0700549 /**
550 * @hide
551 */
552 public static String getErrorString(Context context, int errMsg, int vendorCode) {
Gilad Brettercb51b8b2018-03-22 17:04:51 +0200553 switch (errMsg) {
Kevin Chyn6cf54e82018-09-18 19:13:27 -0700554 case FACE_ERROR_HW_UNAVAILABLE:
555 return context.getString(
556 com.android.internal.R.string.face_error_hw_not_available);
Kevin Chyn68823b02019-02-06 08:59:16 -0800557 case FACE_ERROR_UNABLE_TO_PROCESS:
558 return context.getString(
559 com.android.internal.R.string.face_error_unable_to_process);
Kevin Chyn6cf54e82018-09-18 19:13:27 -0700560 case FACE_ERROR_TIMEOUT:
561 return context.getString(com.android.internal.R.string.face_error_timeout);
Kevin Chyn68823b02019-02-06 08:59:16 -0800562 case FACE_ERROR_NO_SPACE:
563 return context.getString(com.android.internal.R.string.face_error_no_space);
Gilad Brettercb51b8b2018-03-22 17:04:51 +0200564 case FACE_ERROR_CANCELED:
Kevin Chyn8b7a0372018-09-17 15:06:05 -0700565 return context.getString(com.android.internal.R.string.face_error_canceled);
Gilad Brettercb51b8b2018-03-22 17:04:51 +0200566 case FACE_ERROR_LOCKOUT:
Kevin Chyn8b7a0372018-09-17 15:06:05 -0700567 return context.getString(com.android.internal.R.string.face_error_lockout);
Gilad Brettercb51b8b2018-03-22 17:04:51 +0200568 case FACE_ERROR_LOCKOUT_PERMANENT:
Kevin Chyn8b7a0372018-09-17 15:06:05 -0700569 return context.getString(
Gilad Brettercb51b8b2018-03-22 17:04:51 +0200570 com.android.internal.R.string.face_error_lockout_permanent);
Kevin Chyn6cf54e82018-09-18 19:13:27 -0700571 case FACE_ERROR_USER_CANCELED:
572 return context.getString(com.android.internal.R.string.face_error_user_canceled);
Gilad Brettercb51b8b2018-03-22 17:04:51 +0200573 case FACE_ERROR_NOT_ENROLLED:
Kevin Chyn8b7a0372018-09-17 15:06:05 -0700574 return context.getString(com.android.internal.R.string.face_error_not_enrolled);
Gilad Brettercb51b8b2018-03-22 17:04:51 +0200575 case FACE_ERROR_HW_NOT_PRESENT:
Kevin Chyn8b7a0372018-09-17 15:06:05 -0700576 return context.getString(com.android.internal.R.string.face_error_hw_not_present);
Gilad Brettercb51b8b2018-03-22 17:04:51 +0200577 case FACE_ERROR_VENDOR: {
Kevin Chyn8b7a0372018-09-17 15:06:05 -0700578 String[] msgArray = context.getResources().getStringArray(
Gilad Brettercb51b8b2018-03-22 17:04:51 +0200579 com.android.internal.R.array.face_error_vendor);
580 if (vendorCode < msgArray.length) {
581 return msgArray[vendorCode];
582 }
583 }
584 }
585 Slog.w(TAG, "Invalid error message: " + errMsg + ", " + vendorCode);
586 return null;
587 }
588
Kevin Chyna24e9fd2018-08-27 12:39:17 -0700589 /**
590 * @hide
591 */
Kevin Chyn8b7a0372018-09-17 15:06:05 -0700592 public static String getAcquiredString(Context context, int acquireInfo, int vendorCode) {
Gilad Brettercb51b8b2018-03-22 17:04:51 +0200593 switch (acquireInfo) {
594 case FACE_ACQUIRED_GOOD:
595 return null;
596 case FACE_ACQUIRED_INSUFFICIENT:
Kevin Chyn8b7a0372018-09-17 15:06:05 -0700597 return context.getString(R.string.face_acquired_insufficient);
Gilad Brettercb51b8b2018-03-22 17:04:51 +0200598 case FACE_ACQUIRED_TOO_BRIGHT:
Kevin Chyn8b7a0372018-09-17 15:06:05 -0700599 return context.getString(R.string.face_acquired_too_bright);
Gilad Brettercb51b8b2018-03-22 17:04:51 +0200600 case FACE_ACQUIRED_TOO_DARK:
Kevin Chyn8b7a0372018-09-17 15:06:05 -0700601 return context.getString(R.string.face_acquired_too_dark);
Gilad Brettercb51b8b2018-03-22 17:04:51 +0200602 case FACE_ACQUIRED_TOO_CLOSE:
Kevin Chyn8b7a0372018-09-17 15:06:05 -0700603 return context.getString(R.string.face_acquired_too_close);
Gilad Brettercb51b8b2018-03-22 17:04:51 +0200604 case FACE_ACQUIRED_TOO_FAR:
Kevin Chyn8b7a0372018-09-17 15:06:05 -0700605 return context.getString(R.string.face_acquired_too_far);
Gilad Brettercb51b8b2018-03-22 17:04:51 +0200606 case FACE_ACQUIRED_TOO_HIGH:
Kevin Chyn8b7a0372018-09-17 15:06:05 -0700607 return context.getString(R.string.face_acquired_too_high);
Gilad Brettercb51b8b2018-03-22 17:04:51 +0200608 case FACE_ACQUIRED_TOO_LOW:
Kevin Chyn8b7a0372018-09-17 15:06:05 -0700609 return context.getString(R.string.face_acquired_too_low);
Gilad Brettercb51b8b2018-03-22 17:04:51 +0200610 case FACE_ACQUIRED_TOO_RIGHT:
Kevin Chyn8b7a0372018-09-17 15:06:05 -0700611 return context.getString(R.string.face_acquired_too_right);
Gilad Brettercb51b8b2018-03-22 17:04:51 +0200612 case FACE_ACQUIRED_TOO_LEFT:
Kevin Chyn8b7a0372018-09-17 15:06:05 -0700613 return context.getString(R.string.face_acquired_too_left);
Gilad Brettercb51b8b2018-03-22 17:04:51 +0200614 case FACE_ACQUIRED_POOR_GAZE:
Kevin Chyn8b7a0372018-09-17 15:06:05 -0700615 return context.getString(R.string.face_acquired_poor_gaze);
Gilad Brettercb51b8b2018-03-22 17:04:51 +0200616 case FACE_ACQUIRED_NOT_DETECTED:
Kevin Chyn8b7a0372018-09-17 15:06:05 -0700617 return context.getString(R.string.face_acquired_not_detected);
Kevin Chyn68823b02019-02-06 08:59:16 -0800618 case FACE_ACQUIRED_TOO_MUCH_MOTION:
619 return context.getString(R.string.face_acquired_too_much_motion);
620 case FACE_ACQUIRED_RECALIBRATE:
621 return context.getString(R.string.face_acquired_recalibrate);
622 case FACE_ACQUIRED_TOO_DIFFERENT:
623 return context.getString(R.string.face_acquired_too_different);
624 case FACE_ACQUIRED_TOO_SIMILAR:
625 return context.getString(R.string.face_acquired_too_similar);
626 case FACE_ACQUIRED_PAN_TOO_EXTREME:
627 return context.getString(R.string.face_acquired_pan_too_extreme);
628 case FACE_ACQUIRED_TILT_TOO_EXTREME:
629 return context.getString(R.string.face_acquired_tilt_too_extreme);
630 case FACE_ACQUIRED_ROLL_TOO_EXTREME:
631 return context.getString(R.string.face_acquired_roll_too_extreme);
632 case FACE_ACQUIRED_FACE_OBSCURED:
633 return context.getString(R.string.face_acquired_obscured);
634 case FACE_ACQUIRED_START:
635 return null;
Gilad Brettercb51b8b2018-03-22 17:04:51 +0200636 case FACE_ACQUIRED_VENDOR: {
Kevin Chyn8b7a0372018-09-17 15:06:05 -0700637 String[] msgArray = context.getResources().getStringArray(
Gilad Brettercb51b8b2018-03-22 17:04:51 +0200638 R.array.face_acquired_vendor);
639 if (vendorCode < msgArray.length) {
640 return msgArray[vendorCode];
641 }
642 }
643 }
644 Slog.w(TAG, "Invalid acquired message: " + acquireInfo + ", " + vendorCode);
645 return null;
646 }
647
648 /**
Kevin Chyna24e9fd2018-08-27 12:39:17 -0700649 * Used so BiometricPrompt can map the face ones onto existing public constants.
650 * @hide
651 */
Kevin Chyn8b7a0372018-09-17 15:06:05 -0700652 public static int getMappedAcquiredInfo(int acquireInfo, int vendorCode) {
Kevin Chyna24e9fd2018-08-27 12:39:17 -0700653 switch (acquireInfo) {
654 case FACE_ACQUIRED_GOOD:
655 return BiometricConstants.BIOMETRIC_ACQUIRED_GOOD;
656 case FACE_ACQUIRED_INSUFFICIENT:
657 case FACE_ACQUIRED_TOO_BRIGHT:
658 case FACE_ACQUIRED_TOO_DARK:
659 return BiometricConstants.BIOMETRIC_ACQUIRED_INSUFFICIENT;
660 case FACE_ACQUIRED_TOO_CLOSE:
661 case FACE_ACQUIRED_TOO_FAR:
662 case FACE_ACQUIRED_TOO_HIGH:
663 case FACE_ACQUIRED_TOO_LOW:
664 case FACE_ACQUIRED_TOO_RIGHT:
665 case FACE_ACQUIRED_TOO_LEFT:
666 return BiometricConstants.BIOMETRIC_ACQUIRED_PARTIAL;
667 case FACE_ACQUIRED_POOR_GAZE:
668 case FACE_ACQUIRED_NOT_DETECTED:
669 case FACE_ACQUIRED_TOO_MUCH_MOTION:
670 case FACE_ACQUIRED_RECALIBRATE:
671 return BiometricConstants.BIOMETRIC_ACQUIRED_INSUFFICIENT;
672 case FACE_ACQUIRED_VENDOR:
673 return BiometricConstants.BIOMETRIC_ACQUIRED_VENDOR_BASE + vendorCode;
674 default:
675 return BiometricConstants.BIOMETRIC_ACQUIRED_GOOD;
676 }
677 }
678
679 /**
Gilad Brettercb51b8b2018-03-22 17:04:51 +0200680 * Container for callback data from {@link FaceManager#authenticate(CryptoObject,
681 * CancellationSignal, int, AuthenticationCallback, Handler)}.
682 */
683 public static class AuthenticationResult {
684 private Face mFace;
685 private CryptoObject mCryptoObject;
686 private int mUserId;
687
688 /**
689 * Authentication result
690 *
691 * @param crypto the crypto object
692 * @param face the recognized face data, if allowed.
693 * @hide
694 */
695 public AuthenticationResult(CryptoObject crypto, Face face, int userId) {
696 mCryptoObject = crypto;
697 mFace = face;
698 mUserId = userId;
699 }
700
701 /**
702 * Obtain the crypto object associated with this transaction
703 *
704 * @return crypto object provided to {@link FaceManager#authenticate
705 * (CryptoObject,
706 * CancellationSignal, int, AuthenticationCallback, Handler)}.
707 */
708 public CryptoObject getCryptoObject() {
709 return mCryptoObject;
710 }
711
712 /**
713 * Obtain the Face associated with this operation. Applications are strongly
714 * discouraged from associating specific faces with specific applications or operations.
715 *
716 * @hide
717 */
718 public Face getFace() {
719 return mFace;
720 }
721
722 /**
723 * Obtain the userId for which this face was authenticated.
724 *
725 * @hide
726 */
727 public int getUserId() {
728 return mUserId;
729 }
730 }
731
732 /**
733 * Callback structure provided to {@link FaceManager#authenticate(CryptoObject,
734 * CancellationSignal, int, AuthenticationCallback, Handler)}. Users of {@link
735 * FaceManager#authenticate(CryptoObject, CancellationSignal,
736 * int, AuthenticationCallback, Handler) } must provide an implementation of this for listening
737 * to face events.
738 */
Kevin Chyna56dff72018-06-19 18:41:12 -0700739 public abstract static class AuthenticationCallback
740 extends BiometricAuthenticator.AuthenticationCallback {
Gilad Brettercb51b8b2018-03-22 17:04:51 +0200741
742 /**
743 * Called when an unrecoverable error has been encountered and the operation is complete.
744 * No further callbacks will be made on this object.
745 *
746 * @param errorCode An integer identifying the error message
747 * @param errString A human-readable error string that can be shown in UI
748 */
749 public void onAuthenticationError(int errorCode, CharSequence errString) {
750 }
751
752 /**
753 * Called when a recoverable error has been encountered during authentication. The help
754 * string is provided to give the user guidance for what went wrong, such as
755 * "Sensor dirty, please clean it."
756 *
757 * @param helpCode An integer identifying the error message
758 * @param helpString A human-readable string that can be shown in UI
759 */
760 public void onAuthenticationHelp(int helpCode, CharSequence helpString) {
761 }
762
763 /**
764 * Called when a face is recognized.
765 *
766 * @param result An object containing authentication-related data
767 */
768 public void onAuthenticationSucceeded(AuthenticationResult result) {
769 }
770
771 /**
772 * Called when a face is detected but not recognized.
773 */
774 public void onAuthenticationFailed() {
775 }
776
777 /**
778 * Called when a face image has been acquired, but wasn't processed yet.
779 *
780 * @param acquireInfo one of FACE_ACQUIRED_* constants
781 * @hide
782 */
783 public void onAuthenticationAcquired(int acquireInfo) {
784 }
785 }
786
787 /**
788 * Callback structure provided to {@link FaceManager#enroll(long,
789 * EnrollmentCallback, CancellationSignal, int). Users of {@link #FaceAuthenticationManager()}
790 * must provide an implementation of this to {@link FaceManager#enroll(long,
791 * CancellationSignal, int, EnrollmentCallback) for listening to face enrollment events.
792 *
793 * @hide
794 */
795 public abstract static class EnrollmentCallback {
796
797 /**
798 * Called when an unrecoverable error has been encountered and the operation is complete.
799 * No further callbacks will be made on this object.
800 *
801 * @param errMsgId An integer identifying the error message
802 * @param errString A human-readable error string that can be shown in UI
803 */
804 public void onEnrollmentError(int errMsgId, CharSequence errString) {
805 }
806
807 /**
808 * Called when a recoverable error has been encountered during enrollment. The help
809 * string is provided to give the user guidance for what went wrong, such as
810 * "Image too dark, uncover light source" or what they need to do next, such as
811 * "Rotate face up / down."
812 *
813 * @param helpMsgId An integer identifying the error message
814 * @param helpString A human-readable string that can be shown in UI
815 */
816 public void onEnrollmentHelp(int helpMsgId, CharSequence helpString) {
817 }
818
819 /**
820 * Called as each enrollment step progresses. Enrollment is considered complete when
821 * remaining reaches 0. This function will not be called if enrollment fails. See
822 * {@link EnrollmentCallback#onEnrollmentError(int, CharSequence)}
823 *
824 * @param remaining The number of remaining steps
Gilad Brettercb51b8b2018-03-22 17:04:51 +0200825 */
Kevin Chyn8277f002018-07-02 15:14:44 -0700826 public void onEnrollmentProgress(int remaining) {
Gilad Brettercb51b8b2018-03-22 17:04:51 +0200827 }
828 }
829
830 /**
831 * Callback structure provided to {@link #remove}. Users of {@link FaceManager}
832 * may
833 * optionally provide an implementation of this to
834 * {@link #remove(Face, int, RemovalCallback)} for listening to face template
835 * removal events.
836 *
837 * @hide
838 */
839 public abstract static class RemovalCallback {
840
841 /**
842 * Called when the given face can't be removed.
843 *
844 * @param face The face that the call attempted to remove
845 * @param errMsgId An associated error message id
846 * @param errString An error message indicating why the face id can't be removed
847 */
848 public void onRemovalError(Face face, int errMsgId, CharSequence errString) {
849 }
850
851 /**
852 * Called when a given face is successfully removed.
853 *
854 * @param face The face template that was removed.
855 */
Kevin Chyn7e88d1132018-06-29 12:55:22 -0700856 public void onRemovalSucceeded(Face face, int remaining) {
Gilad Brettercb51b8b2018-03-22 17:04:51 +0200857 }
858 }
859
860 /**
861 * @hide
862 */
863 public abstract static class LockoutResetCallback {
864
865 /**
866 * Called when lockout period expired and clients are allowed to listen for face
867 * authentication
868 * again.
869 */
870 public void onLockoutReset() {
871 }
872 }
873
874 private class OnEnrollCancelListener implements OnCancelListener {
875 @Override
876 public void onCancel() {
877 cancelEnrollment();
878 }
879 }
880
881 private class OnAuthenticationCancelListener implements OnCancelListener {
882 private CryptoObject mCrypto;
883
884 OnAuthenticationCancelListener(CryptoObject crypto) {
885 mCrypto = crypto;
886 }
887
888 @Override
889 public void onCancel() {
890 cancelAuthentication(mCrypto);
891 }
892 }
893
894 private class MyHandler extends Handler {
895 private MyHandler(Context context) {
896 super(context.getMainLooper());
897 }
898
899 private MyHandler(Looper looper) {
900 super(looper);
901 }
902
903 @Override
904 public void handleMessage(android.os.Message msg) {
905 switch (msg.what) {
906 case MSG_ENROLL_RESULT:
Kevin Chyn8277f002018-07-02 15:14:44 -0700907 sendEnrollResult((Face) msg.obj, msg.arg1 /* remaining */);
Gilad Brettercb51b8b2018-03-22 17:04:51 +0200908 break;
909 case MSG_ACQUIRED:
910 sendAcquiredResult((Long) msg.obj /* deviceId */, msg.arg1 /* acquire info */,
911 msg.arg2 /* vendorCode */);
912 break;
913 case MSG_AUTHENTICATION_SUCCEEDED:
914 sendAuthenticatedSucceeded((Face) msg.obj, msg.arg1 /* userId */);
915 break;
916 case MSG_AUTHENTICATION_FAILED:
917 sendAuthenticatedFailed();
918 break;
919 case MSG_ERROR:
920 sendErrorResult((Long) msg.obj /* deviceId */, msg.arg1 /* errMsgId */,
921 msg.arg2 /* vendorCode */);
922 break;
923 case MSG_REMOVED:
Kevin Chyn7e88d1132018-06-29 12:55:22 -0700924 sendRemovedResult((Face) msg.obj, msg.arg1 /* remaining */);
Gilad Brettercb51b8b2018-03-22 17:04:51 +0200925 break;
926 }
927 }
Kevin Chyna56dff72018-06-19 18:41:12 -0700928 };
Gilad Brettercb51b8b2018-03-22 17:04:51 +0200929
Kevin Chyn7e88d1132018-06-29 12:55:22 -0700930 private void sendRemovedResult(Face face, int remaining) {
Kevin Chyna56dff72018-06-19 18:41:12 -0700931 if (mRemovalCallback == null) {
932 return;
933 }
934 if (face == null) {
935 Log.e(TAG, "Received MSG_REMOVED, but face is null");
936 return;
Gilad Brettercb51b8b2018-03-22 17:04:51 +0200937 }
Kevin Chyn7e88d1132018-06-29 12:55:22 -0700938 mRemovalCallback.onRemovalSucceeded(face, remaining);
Kevin Chyna56dff72018-06-19 18:41:12 -0700939 }
Gilad Brettercb51b8b2018-03-22 17:04:51 +0200940
Kevin Chyna56dff72018-06-19 18:41:12 -0700941 private void sendErrorResult(long deviceId, int errMsgId, int vendorCode) {
942 // emulate HAL 2.1 behavior and send real errMsgId
943 final int clientErrMsgId = errMsgId == FACE_ERROR_VENDOR
944 ? (vendorCode + FACE_ERROR_VENDOR_BASE) : errMsgId;
945 if (mEnrollmentCallback != null) {
946 mEnrollmentCallback.onEnrollmentError(clientErrMsgId,
Kevin Chyn8b7a0372018-09-17 15:06:05 -0700947 getErrorString(mContext, errMsgId, vendorCode));
Kevin Chyna56dff72018-06-19 18:41:12 -0700948 } else if (mAuthenticationCallback != null) {
949 mAuthenticationCallback.onAuthenticationError(clientErrMsgId,
Kevin Chyn8b7a0372018-09-17 15:06:05 -0700950 getErrorString(mContext, errMsgId, vendorCode));
Kevin Chyna56dff72018-06-19 18:41:12 -0700951 } else if (mRemovalCallback != null) {
952 mRemovalCallback.onRemovalError(mRemovalFace, clientErrMsgId,
Kevin Chyn8b7a0372018-09-17 15:06:05 -0700953 getErrorString(mContext, errMsgId, vendorCode));
Gilad Brettercb51b8b2018-03-22 17:04:51 +0200954 }
Kevin Chyna56dff72018-06-19 18:41:12 -0700955 }
Gilad Brettercb51b8b2018-03-22 17:04:51 +0200956
Kevin Chyn8277f002018-07-02 15:14:44 -0700957 private void sendEnrollResult(Face face, int remaining) {
Kevin Chyna56dff72018-06-19 18:41:12 -0700958 if (mEnrollmentCallback != null) {
Kevin Chyn8277f002018-07-02 15:14:44 -0700959 mEnrollmentCallback.onEnrollmentProgress(remaining);
Gilad Brettercb51b8b2018-03-22 17:04:51 +0200960 }
Kevin Chyna56dff72018-06-19 18:41:12 -0700961 }
Gilad Brettercb51b8b2018-03-22 17:04:51 +0200962
Kevin Chyna56dff72018-06-19 18:41:12 -0700963 private void sendAuthenticatedSucceeded(Face face, int userId) {
964 if (mAuthenticationCallback != null) {
Kevin Chyna24e9fd2018-08-27 12:39:17 -0700965 final AuthenticationResult result =
966 new AuthenticationResult(mCryptoObject, face, userId);
Kevin Chyna56dff72018-06-19 18:41:12 -0700967 mAuthenticationCallback.onAuthenticationSucceeded(result);
968 }
969 }
970
971 private void sendAuthenticatedFailed() {
972 if (mAuthenticationCallback != null) {
973 mAuthenticationCallback.onAuthenticationFailed();
974 }
975 }
976
977 private void sendAcquiredResult(long deviceId, int acquireInfo, int vendorCode) {
978 if (mAuthenticationCallback != null) {
979 mAuthenticationCallback.onAuthenticationAcquired(acquireInfo);
980 }
Kevin Chyn8b7a0372018-09-17 15:06:05 -0700981 final String msg = getAcquiredString(mContext, acquireInfo, vendorCode);
Kevin Chyna56dff72018-06-19 18:41:12 -0700982 final int clientInfo = acquireInfo == FACE_ACQUIRED_VENDOR
983 ? (vendorCode + FACE_ACQUIRED_VENDOR_BASE) : acquireInfo;
984 if (mEnrollmentCallback != null) {
985 mEnrollmentCallback.onEnrollmentHelp(clientInfo, msg);
Kevin Chynffef7142018-12-27 16:28:48 -0800986 } else if (mAuthenticationCallback != null && msg != null) {
Kevin Chyna56dff72018-06-19 18:41:12 -0700987 mAuthenticationCallback.onAuthenticationHelp(clientInfo, msg);
Gilad Brettercb51b8b2018-03-22 17:04:51 +0200988 }
989 }
Gilad Brettercb51b8b2018-03-22 17:04:51 +0200990}