/**
|
* Copyright (C) 2018 The Android Open Source Project
|
*
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
* you may not use this file except in compliance with the License.
|
* You may obtain a copy of the License at
|
*
|
* http://www.apache.org/licenses/LICENSE-2.0
|
*
|
* Unless required by applicable law or agreed to in writing, software
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
* See the License for the specific language governing permissions and
|
* limitations under the License.
|
*/
|
|
package android.hardware.face;
|
|
import static android.Manifest.permission.INTERACT_ACROSS_USERS;
|
import static android.Manifest.permission.MANAGE_BIOMETRIC;
|
import static android.Manifest.permission.USE_BIOMETRIC_INTERNAL;
|
|
import android.annotation.NonNull;
|
import android.annotation.Nullable;
|
import android.annotation.RequiresPermission;
|
import android.annotation.SystemService;
|
import android.app.ActivityManager;
|
import android.content.Context;
|
import android.hardware.biometrics.BiometricAuthenticator;
|
import android.hardware.biometrics.BiometricConstants;
|
import android.hardware.biometrics.BiometricFaceConstants;
|
import android.hardware.biometrics.CryptoObject;
|
import android.hardware.biometrics.IBiometricServiceLockoutResetCallback;
|
import android.os.Binder;
|
import android.os.CancellationSignal;
|
import android.os.CancellationSignal.OnCancelListener;
|
import android.os.Handler;
|
import android.os.IBinder;
|
import android.os.IRemoteCallback;
|
import android.os.Looper;
|
import android.os.PowerManager;
|
import android.os.RemoteException;
|
import android.os.Trace;
|
import android.os.UserHandle;
|
import android.util.Log;
|
import android.util.Slog;
|
|
import com.android.internal.R;
|
import com.android.internal.os.SomeArgs;
|
|
import java.util.List;
|
|
/**
|
* A class that coordinates access to the face authentication hardware.
|
* @hide
|
*/
|
@SystemService(Context.FACE_SERVICE)
|
public class FaceManager implements BiometricAuthenticator, BiometricFaceConstants {
|
|
private static final String TAG = "FaceManager";
|
private static final boolean DEBUG = true;
|
private static final int MSG_ENROLL_RESULT = 100;
|
private static final int MSG_ACQUIRED = 101;
|
private static final int MSG_AUTHENTICATION_SUCCEEDED = 102;
|
private static final int MSG_AUTHENTICATION_FAILED = 103;
|
private static final int MSG_ERROR = 104;
|
private static final int MSG_REMOVED = 105;
|
private static final int MSG_GET_FEATURE_COMPLETED = 106;
|
private static final int MSG_SET_FEATURE_COMPLETED = 107;
|
|
private IFaceService mService;
|
private final Context mContext;
|
private IBinder mToken = new Binder();
|
private AuthenticationCallback mAuthenticationCallback;
|
private EnrollmentCallback mEnrollmentCallback;
|
private RemovalCallback mRemovalCallback;
|
private SetFeatureCallback mSetFeatureCallback;
|
private GetFeatureCallback mGetFeatureCallback;
|
private CryptoObject mCryptoObject;
|
private Face mRemovalFace;
|
private Handler mHandler;
|
|
private IFaceServiceReceiver mServiceReceiver = new IFaceServiceReceiver.Stub() {
|
|
@Override // binder call
|
public void onEnrollResult(long deviceId, int faceId, int remaining) {
|
mHandler.obtainMessage(MSG_ENROLL_RESULT, remaining, 0,
|
new Face(null, faceId, deviceId)).sendToTarget();
|
}
|
|
@Override // binder call
|
public void onAcquired(long deviceId, int acquireInfo, int vendorCode) {
|
mHandler.obtainMessage(MSG_ACQUIRED, acquireInfo, vendorCode, deviceId).sendToTarget();
|
}
|
|
@Override // binder call
|
public void onAuthenticationSucceeded(long deviceId, Face face, int userId) {
|
mHandler.obtainMessage(MSG_AUTHENTICATION_SUCCEEDED, userId, 0, face).sendToTarget();
|
}
|
|
@Override // binder call
|
public void onAuthenticationFailed(long deviceId) {
|
mHandler.obtainMessage(MSG_AUTHENTICATION_FAILED).sendToTarget();
|
}
|
|
@Override // binder call
|
public void onError(long deviceId, int error, int vendorCode) {
|
mHandler.obtainMessage(MSG_ERROR, error, vendorCode, deviceId).sendToTarget();
|
}
|
|
@Override // binder call
|
public void onRemoved(long deviceId, int faceId, int remaining) {
|
mHandler.obtainMessage(MSG_REMOVED, remaining, 0,
|
new Face(null, faceId, deviceId)).sendToTarget();
|
}
|
|
@Override
|
public void onEnumerated(long deviceId, int faceId, int remaining) {
|
// TODO: Finish. Low priority since it's not used.
|
}
|
|
@Override
|
public void onFeatureSet(boolean success, int feature) {
|
mHandler.obtainMessage(MSG_SET_FEATURE_COMPLETED, feature, 0, success).sendToTarget();
|
}
|
|
@Override
|
public void onFeatureGet(boolean success, int feature, boolean value) {
|
SomeArgs args = SomeArgs.obtain();
|
args.arg1 = success;
|
args.argi1 = feature;
|
args.arg2 = value;
|
mHandler.obtainMessage(MSG_GET_FEATURE_COMPLETED, args).sendToTarget();
|
}
|
};
|
|
/**
|
* @hide
|
*/
|
public FaceManager(Context context, IFaceService service) {
|
mContext = context;
|
mService = service;
|
if (mService == null) {
|
Slog.v(TAG, "FaceAuthenticationManagerService was null");
|
}
|
mHandler = new MyHandler(context);
|
}
|
|
/**
|
* Request authentication of a crypto object. This call operates the face recognition hardware
|
* and starts capturing images. It terminates when
|
* {@link AuthenticationCallback#onAuthenticationError(int, CharSequence)} or
|
* {@link AuthenticationCallback#onAuthenticationSucceeded(AuthenticationResult)} is called, at
|
* which point the object is no longer valid. The operation can be canceled by using the
|
* provided cancel object.
|
*
|
* @param crypto object associated with the call or null if none required.
|
* @param cancel an object that can be used to cancel authentication
|
* @param flags optional flags; should be 0
|
* @param callback an object to receive authentication events
|
* @param handler an optional handler to handle callback events
|
* @throws IllegalArgumentException if the crypto operation is not supported or is not backed
|
* by
|
* <a href="{@docRoot}training/articles/keystore.html">Android
|
* Keystore facility</a>.
|
* @throws IllegalStateException if the crypto primitive is not initialized.
|
* @hide
|
*/
|
@RequiresPermission(USE_BIOMETRIC_INTERNAL)
|
public void authenticate(@Nullable CryptoObject crypto, @Nullable CancellationSignal cancel,
|
int flags, @NonNull AuthenticationCallback callback, @Nullable Handler handler) {
|
authenticate(crypto, cancel, flags, callback, handler, mContext.getUserId());
|
}
|
|
/**
|
* Use the provided handler thread for events.
|
*/
|
private void useHandler(Handler handler) {
|
if (handler != null) {
|
mHandler = new MyHandler(handler.getLooper());
|
} else if (mHandler.getLooper() != mContext.getMainLooper()) {
|
mHandler = new MyHandler(mContext.getMainLooper());
|
}
|
}
|
|
/**
|
* Request authentication of a crypto object. This call operates the face recognition hardware
|
* and starts capturing images. It terminates when
|
* {@link AuthenticationCallback#onAuthenticationError(int, CharSequence)} or
|
* {@link AuthenticationCallback#onAuthenticationSucceeded(AuthenticationResult)} is called, at
|
* which point the object is no longer valid. The operation can be canceled by using the
|
* provided cancel object.
|
*
|
* @param crypto object associated with the call or null if none required.
|
* @param cancel an object that can be used to cancel authentication
|
* @param flags optional flags; should be 0
|
* @param callback an object to receive authentication events
|
* @param handler an optional handler to handle callback events
|
* @param userId userId to authenticate for
|
* @throws IllegalArgumentException if the crypto operation is not supported or is not backed
|
* by
|
* <a href="{@docRoot}training/articles/keystore.html">Android
|
* Keystore facility</a>.
|
* @throws IllegalStateException if the crypto primitive is not initialized.
|
* @hide
|
*/
|
public void authenticate(@Nullable CryptoObject crypto, @Nullable CancellationSignal cancel,
|
int flags, @NonNull AuthenticationCallback callback, @Nullable Handler handler,
|
int userId) {
|
if (callback == null) {
|
throw new IllegalArgumentException("Must supply an authentication callback");
|
}
|
|
if (cancel != null) {
|
if (cancel.isCanceled()) {
|
Log.w(TAG, "authentication already canceled");
|
return;
|
} else {
|
cancel.setOnCancelListener(new OnAuthenticationCancelListener(crypto));
|
}
|
}
|
|
if (mService != null) {
|
try {
|
useHandler(handler);
|
mAuthenticationCallback = callback;
|
mCryptoObject = crypto;
|
long sessionId = crypto != null ? crypto.getOpId() : 0;
|
Trace.beginSection("FaceManager#authenticate");
|
mService.authenticate(mToken, sessionId, userId, mServiceReceiver,
|
flags, mContext.getOpPackageName());
|
} catch (RemoteException e) {
|
Log.w(TAG, "Remote exception while authenticating: ", e);
|
if (callback != null) {
|
// Though this may not be a hardware issue, it will cause apps to give up or
|
// try again later.
|
callback.onAuthenticationError(FACE_ERROR_HW_UNAVAILABLE,
|
getErrorString(mContext, FACE_ERROR_HW_UNAVAILABLE,
|
0 /* vendorCode */));
|
}
|
} finally {
|
Trace.endSection();
|
}
|
}
|
}
|
|
/**
|
* Request face authentication enrollment. This call operates the face authentication hardware
|
* and starts capturing images. Progress will be indicated by callbacks to the
|
* {@link EnrollmentCallback} object. It terminates when
|
* {@link EnrollmentCallback#onEnrollmentError(int, CharSequence)} or
|
* {@link EnrollmentCallback#onEnrollmentProgress(int) is called with remaining == 0, at
|
* which point the object is no longer valid. The operation can be canceled by using the
|
* provided cancel object.
|
*
|
* @param token a unique token provided by a recent creation or verification of device
|
* credentials (e.g. pin, pattern or password).
|
* @param cancel an object that can be used to cancel enrollment
|
* @param flags optional flags
|
* @param userId the user to whom this face will belong to
|
* @param callback an object to receive enrollment events
|
* @hide
|
*/
|
@RequiresPermission(MANAGE_BIOMETRIC)
|
public void enroll(byte[] token, CancellationSignal cancel,
|
EnrollmentCallback callback, int[] disabledFeatures) {
|
if (callback == null) {
|
throw new IllegalArgumentException("Must supply an enrollment callback");
|
}
|
|
if (cancel != null) {
|
if (cancel.isCanceled()) {
|
Log.w(TAG, "enrollment already canceled");
|
return;
|
} else {
|
cancel.setOnCancelListener(new OnEnrollCancelListener());
|
}
|
}
|
|
if (mService != null) {
|
try {
|
mEnrollmentCallback = callback;
|
Trace.beginSection("FaceManager#enroll");
|
mService.enroll(mToken, token, mServiceReceiver,
|
mContext.getOpPackageName(), disabledFeatures);
|
} catch (RemoteException e) {
|
Log.w(TAG, "Remote exception in enroll: ", e);
|
if (callback != null) {
|
// Though this may not be a hardware issue, it will cause apps to give up or
|
// try again later.
|
callback.onEnrollmentError(FACE_ERROR_HW_UNAVAILABLE,
|
getErrorString(mContext, FACE_ERROR_HW_UNAVAILABLE,
|
0 /* vendorCode */));
|
}
|
} finally {
|
Trace.endSection();
|
}
|
}
|
}
|
|
/**
|
* Requests an auth token to tie sensitive operations to the confirmation of
|
* existing device credentials (e.g. pin/pattern/password).
|
*
|
* @hide
|
*/
|
@RequiresPermission(MANAGE_BIOMETRIC)
|
public long generateChallenge() {
|
long result = 0;
|
if (mService != null) {
|
try {
|
result = mService.generateChallenge(mToken);
|
} catch (RemoteException e) {
|
throw e.rethrowFromSystemServer();
|
}
|
}
|
return result;
|
}
|
|
/**
|
* Invalidates the current auth token.
|
*
|
* @hide
|
*/
|
@RequiresPermission(MANAGE_BIOMETRIC)
|
public int revokeChallenge() {
|
int result = 0;
|
if (mService != null) {
|
try {
|
result = mService.revokeChallenge(mToken);
|
} catch (RemoteException e) {
|
throw e.rethrowFromSystemServer();
|
}
|
}
|
return result;
|
}
|
|
/**
|
* @hide
|
*/
|
@RequiresPermission(MANAGE_BIOMETRIC)
|
public void setFeature(int feature, boolean enabled, byte[] token,
|
SetFeatureCallback callback) {
|
if (mService != null) {
|
try {
|
mSetFeatureCallback = callback;
|
mService.setFeature(feature, enabled, token, mServiceReceiver);
|
} catch (RemoteException e) {
|
throw e.rethrowFromSystemServer();
|
}
|
}
|
}
|
|
/**
|
* @hide
|
*/
|
@RequiresPermission(MANAGE_BIOMETRIC)
|
public void getFeature(int feature, GetFeatureCallback callback) {
|
if (mService != null) {
|
try {
|
mGetFeatureCallback = callback;
|
mService.getFeature(feature, mServiceReceiver);
|
} catch (RemoteException e) {
|
throw e.rethrowFromSystemServer();
|
}
|
}
|
}
|
|
/**
|
* Pokes the the driver to have it start looking for faces again.
|
* @hide
|
*/
|
@RequiresPermission(MANAGE_BIOMETRIC)
|
public void userActivity() {
|
if (mService != null) {
|
try {
|
mService.userActivity();
|
} catch (RemoteException e) {
|
throw e.rethrowFromSystemServer();
|
}
|
}
|
}
|
|
/**
|
* Sets the active user. This is meant to be used to select the current profile for enrollment
|
* to allow separate enrolled faces for a work profile
|
*
|
* @hide
|
*/
|
@RequiresPermission(MANAGE_BIOMETRIC)
|
@Override
|
public void setActiveUser(int userId) {
|
if (mService != null) {
|
try {
|
mService.setActiveUser(userId);
|
} catch (RemoteException e) {
|
throw e.rethrowFromSystemServer();
|
}
|
}
|
}
|
|
/**
|
* Remove given face template from face hardware and/or protected storage.
|
*
|
* @param face the face item to remove
|
* @param userId the user who this face belongs to
|
* @param callback an optional callback to verify that face templates have been
|
* successfully removed. May be null if no callback is required.
|
* @hide
|
*/
|
@RequiresPermission(MANAGE_BIOMETRIC)
|
public void remove(Face face, int userId, RemovalCallback callback) {
|
if (mService != null) {
|
try {
|
mRemovalCallback = callback;
|
mRemovalFace = face;
|
mService.remove(mToken, face.getBiometricId(), userId, mServiceReceiver);
|
} catch (RemoteException e) {
|
Log.w(TAG, "Remote exception in remove: ", e);
|
if (callback != null) {
|
callback.onRemovalError(face, FACE_ERROR_HW_UNAVAILABLE,
|
getErrorString(mContext, FACE_ERROR_HW_UNAVAILABLE,
|
0 /* vendorCode */));
|
}
|
}
|
}
|
}
|
|
/**
|
* Obtain the enrolled face template.
|
*
|
* @return the current face item
|
* @hide
|
*/
|
@RequiresPermission(MANAGE_BIOMETRIC)
|
public List<Face> getEnrolledFaces(int userId) {
|
if (mService != null) {
|
try {
|
return mService.getEnrolledFaces(userId, mContext.getOpPackageName());
|
} catch (RemoteException e) {
|
throw e.rethrowFromSystemServer();
|
}
|
}
|
return null;
|
}
|
|
/**
|
* Obtain the enrolled face template.
|
*
|
* @return the current face item
|
* @hide
|
*/
|
@RequiresPermission(MANAGE_BIOMETRIC)
|
public List<Face> getEnrolledFaces() {
|
return getEnrolledFaces(UserHandle.myUserId());
|
}
|
|
/**
|
* Determine if there is a face enrolled.
|
*
|
* @return true if a face is enrolled, false otherwise
|
*/
|
@RequiresPermission(USE_BIOMETRIC_INTERNAL)
|
@Override
|
public boolean hasEnrolledTemplates() {
|
if (mService != null) {
|
try {
|
return mService.hasEnrolledFaces(
|
UserHandle.myUserId(), mContext.getOpPackageName());
|
} catch (RemoteException e) {
|
throw e.rethrowFromSystemServer();
|
}
|
}
|
return false;
|
}
|
|
/**
|
* @hide
|
*/
|
@RequiresPermission(allOf = {
|
USE_BIOMETRIC_INTERNAL,
|
INTERACT_ACROSS_USERS})
|
@Override
|
public boolean hasEnrolledTemplates(int userId) {
|
if (mService != null) {
|
try {
|
return mService.hasEnrolledFaces(userId, mContext.getOpPackageName());
|
} catch (RemoteException e) {
|
throw e.rethrowFromSystemServer();
|
}
|
}
|
return false;
|
}
|
|
/**
|
* Determine if face authentication sensor hardware is present and functional.
|
*
|
* @return true if hardware is present and functional, false otherwise.
|
*/
|
@RequiresPermission(USE_BIOMETRIC_INTERNAL)
|
@Override
|
public boolean isHardwareDetected() {
|
if (mService != null) {
|
try {
|
long deviceId = 0; /* TODO: plumb hardware id to FPMS */
|
return mService.isHardwareDetected(deviceId, mContext.getOpPackageName());
|
} catch (RemoteException e) {
|
throw e.rethrowFromSystemServer();
|
}
|
} else {
|
Log.w(TAG, "isFaceHardwareDetected(): Service not connected!");
|
}
|
return false;
|
}
|
|
/**
|
* Retrieves the authenticator token for binding keys to the lifecycle
|
* of the calling user's face. Used only by internal clients.
|
*
|
* @hide
|
*/
|
public long getAuthenticatorId() {
|
if (mService != null) {
|
try {
|
return mService.getAuthenticatorId(mContext.getOpPackageName());
|
} catch (RemoteException e) {
|
throw e.rethrowFromSystemServer();
|
}
|
} else {
|
Log.w(TAG, "getAuthenticatorId(): Service not connected!");
|
}
|
return 0;
|
}
|
|
/**
|
* @hide
|
*/
|
@RequiresPermission(USE_BIOMETRIC_INTERNAL)
|
public void addLockoutResetCallback(final LockoutResetCallback callback) {
|
if (mService != null) {
|
try {
|
final PowerManager powerManager = mContext.getSystemService(PowerManager.class);
|
mService.addLockoutResetCallback(
|
new IBiometricServiceLockoutResetCallback.Stub() {
|
|
@Override
|
public void onLockoutReset(long deviceId,
|
IRemoteCallback serverCallback)
|
throws RemoteException {
|
try {
|
final PowerManager.WakeLock wakeLock = powerManager.newWakeLock(
|
PowerManager.PARTIAL_WAKE_LOCK,
|
"faceLockoutResetCallback");
|
wakeLock.acquire();
|
mHandler.post(() -> {
|
try {
|
callback.onLockoutReset();
|
} finally {
|
wakeLock.release();
|
}
|
});
|
} finally {
|
serverCallback.sendResult(null /* data */);
|
}
|
}
|
});
|
} catch (RemoteException e) {
|
throw e.rethrowFromSystemServer();
|
}
|
} else {
|
Log.w(TAG, "addLockoutResetCallback(): Service not connected!");
|
}
|
}
|
|
private int getCurrentUserId() {
|
try {
|
return ActivityManager.getService().getCurrentUser().id;
|
} catch (RemoteException e) {
|
throw e.rethrowFromSystemServer();
|
}
|
}
|
|
private void cancelEnrollment() {
|
if (mService != null) {
|
try {
|
mService.cancelEnrollment(mToken);
|
} catch (RemoteException e) {
|
throw e.rethrowFromSystemServer();
|
}
|
}
|
}
|
|
private void cancelAuthentication(CryptoObject cryptoObject) {
|
if (mService != null) {
|
try {
|
mService.cancelAuthentication(mToken, mContext.getOpPackageName());
|
} catch (RemoteException e) {
|
throw e.rethrowFromSystemServer();
|
}
|
}
|
}
|
|
/**
|
* @hide
|
*/
|
public static String getErrorString(Context context, int errMsg, int vendorCode) {
|
switch (errMsg) {
|
case FACE_ERROR_HW_UNAVAILABLE:
|
return context.getString(
|
com.android.internal.R.string.face_error_hw_not_available);
|
case FACE_ERROR_UNABLE_TO_PROCESS:
|
return context.getString(
|
com.android.internal.R.string.face_error_unable_to_process);
|
case FACE_ERROR_TIMEOUT:
|
return context.getString(com.android.internal.R.string.face_error_timeout);
|
case FACE_ERROR_NO_SPACE:
|
return context.getString(com.android.internal.R.string.face_error_no_space);
|
case FACE_ERROR_CANCELED:
|
return context.getString(com.android.internal.R.string.face_error_canceled);
|
case FACE_ERROR_LOCKOUT:
|
return context.getString(com.android.internal.R.string.face_error_lockout);
|
case FACE_ERROR_LOCKOUT_PERMANENT:
|
return context.getString(
|
com.android.internal.R.string.face_error_lockout_permanent);
|
case FACE_ERROR_USER_CANCELED:
|
return context.getString(com.android.internal.R.string.face_error_user_canceled);
|
case FACE_ERROR_NOT_ENROLLED:
|
return context.getString(com.android.internal.R.string.face_error_not_enrolled);
|
case FACE_ERROR_HW_NOT_PRESENT:
|
return context.getString(com.android.internal.R.string.face_error_hw_not_present);
|
case FACE_ERROR_VENDOR: {
|
String[] msgArray = context.getResources().getStringArray(
|
com.android.internal.R.array.face_error_vendor);
|
if (vendorCode < msgArray.length) {
|
return msgArray[vendorCode];
|
}
|
}
|
}
|
Slog.w(TAG, "Invalid error message: " + errMsg + ", " + vendorCode);
|
return null;
|
}
|
|
/**
|
* @hide
|
*/
|
public static String getAcquiredString(Context context, int acquireInfo, int vendorCode) {
|
switch (acquireInfo) {
|
case FACE_ACQUIRED_GOOD:
|
return null;
|
case FACE_ACQUIRED_INSUFFICIENT:
|
return context.getString(R.string.face_acquired_insufficient);
|
case FACE_ACQUIRED_TOO_BRIGHT:
|
return context.getString(R.string.face_acquired_too_bright);
|
case FACE_ACQUIRED_TOO_DARK:
|
return context.getString(R.string.face_acquired_too_dark);
|
case FACE_ACQUIRED_TOO_CLOSE:
|
return context.getString(R.string.face_acquired_too_close);
|
case FACE_ACQUIRED_TOO_FAR:
|
return context.getString(R.string.face_acquired_too_far);
|
case FACE_ACQUIRED_TOO_HIGH:
|
return context.getString(R.string.face_acquired_too_high);
|
case FACE_ACQUIRED_TOO_LOW:
|
return context.getString(R.string.face_acquired_too_low);
|
case FACE_ACQUIRED_TOO_RIGHT:
|
return context.getString(R.string.face_acquired_too_right);
|
case FACE_ACQUIRED_TOO_LEFT:
|
return context.getString(R.string.face_acquired_too_left);
|
case FACE_ACQUIRED_POOR_GAZE:
|
return context.getString(R.string.face_acquired_poor_gaze);
|
case FACE_ACQUIRED_NOT_DETECTED:
|
return context.getString(R.string.face_acquired_not_detected);
|
case FACE_ACQUIRED_TOO_MUCH_MOTION:
|
return context.getString(R.string.face_acquired_too_much_motion);
|
case FACE_ACQUIRED_RECALIBRATE:
|
return context.getString(R.string.face_acquired_recalibrate);
|
case FACE_ACQUIRED_TOO_DIFFERENT:
|
return context.getString(R.string.face_acquired_too_different);
|
case FACE_ACQUIRED_TOO_SIMILAR:
|
return context.getString(R.string.face_acquired_too_similar);
|
case FACE_ACQUIRED_PAN_TOO_EXTREME:
|
return context.getString(R.string.face_acquired_pan_too_extreme);
|
case FACE_ACQUIRED_TILT_TOO_EXTREME:
|
return context.getString(R.string.face_acquired_tilt_too_extreme);
|
case FACE_ACQUIRED_ROLL_TOO_EXTREME:
|
return context.getString(R.string.face_acquired_roll_too_extreme);
|
case FACE_ACQUIRED_FACE_OBSCURED:
|
return context.getString(R.string.face_acquired_obscured);
|
case FACE_ACQUIRED_START:
|
return null;
|
case FACE_ACQUIRED_SENSOR_DIRTY:
|
return context.getString(R.string.face_acquired_sensor_dirty);
|
case FACE_ACQUIRED_VENDOR: {
|
String[] msgArray = context.getResources().getStringArray(
|
R.array.face_acquired_vendor);
|
if (vendorCode < msgArray.length) {
|
return msgArray[vendorCode];
|
}
|
}
|
}
|
Slog.w(TAG, "Invalid acquired message: " + acquireInfo + ", " + vendorCode);
|
return null;
|
}
|
|
/**
|
* Used so BiometricPrompt can map the face ones onto existing public constants.
|
* @hide
|
*/
|
public static int getMappedAcquiredInfo(int acquireInfo, int vendorCode) {
|
switch (acquireInfo) {
|
case FACE_ACQUIRED_GOOD:
|
return BiometricConstants.BIOMETRIC_ACQUIRED_GOOD;
|
case FACE_ACQUIRED_INSUFFICIENT:
|
case FACE_ACQUIRED_TOO_BRIGHT:
|
case FACE_ACQUIRED_TOO_DARK:
|
return BiometricConstants.BIOMETRIC_ACQUIRED_INSUFFICIENT;
|
case FACE_ACQUIRED_TOO_CLOSE:
|
case FACE_ACQUIRED_TOO_FAR:
|
case FACE_ACQUIRED_TOO_HIGH:
|
case FACE_ACQUIRED_TOO_LOW:
|
case FACE_ACQUIRED_TOO_RIGHT:
|
case FACE_ACQUIRED_TOO_LEFT:
|
return BiometricConstants.BIOMETRIC_ACQUIRED_PARTIAL;
|
case FACE_ACQUIRED_POOR_GAZE:
|
case FACE_ACQUIRED_NOT_DETECTED:
|
case FACE_ACQUIRED_TOO_MUCH_MOTION:
|
case FACE_ACQUIRED_RECALIBRATE:
|
return BiometricConstants.BIOMETRIC_ACQUIRED_INSUFFICIENT;
|
case FACE_ACQUIRED_VENDOR:
|
return BiometricConstants.BIOMETRIC_ACQUIRED_VENDOR_BASE + vendorCode;
|
default:
|
return BiometricConstants.BIOMETRIC_ACQUIRED_GOOD;
|
}
|
}
|
|
/**
|
* Container for callback data from {@link FaceManager#authenticate(CryptoObject,
|
* CancellationSignal, int, AuthenticationCallback, Handler)}.
|
*/
|
public static class AuthenticationResult {
|
private Face mFace;
|
private CryptoObject mCryptoObject;
|
private int mUserId;
|
|
/**
|
* Authentication result
|
*
|
* @param crypto the crypto object
|
* @param face the recognized face data, if allowed.
|
* @hide
|
*/
|
public AuthenticationResult(CryptoObject crypto, Face face, int userId) {
|
mCryptoObject = crypto;
|
mFace = face;
|
mUserId = userId;
|
}
|
|
/**
|
* Obtain the crypto object associated with this transaction
|
*
|
* @return crypto object provided to {@link FaceManager#authenticate
|
* (CryptoObject,
|
* CancellationSignal, int, AuthenticationCallback, Handler)}.
|
*/
|
public CryptoObject getCryptoObject() {
|
return mCryptoObject;
|
}
|
|
/**
|
* Obtain the Face associated with this operation. Applications are strongly
|
* discouraged from associating specific faces with specific applications or operations.
|
*
|
* @hide
|
*/
|
public Face getFace() {
|
return mFace;
|
}
|
|
/**
|
* Obtain the userId for which this face was authenticated.
|
*
|
* @hide
|
*/
|
public int getUserId() {
|
return mUserId;
|
}
|
}
|
|
/**
|
* Callback structure provided to {@link FaceManager#authenticate(CryptoObject,
|
* CancellationSignal, int, AuthenticationCallback, Handler)}. Users of {@link
|
* FaceManager#authenticate(CryptoObject, CancellationSignal,
|
* int, AuthenticationCallback, Handler) } must provide an implementation of this for listening
|
* to face events.
|
*/
|
public abstract static class AuthenticationCallback
|
extends BiometricAuthenticator.AuthenticationCallback {
|
|
/**
|
* Called when an unrecoverable error has been encountered and the operation is complete.
|
* No further callbacks will be made on this object.
|
*
|
* @param errorCode An integer identifying the error message
|
* @param errString A human-readable error string that can be shown in UI
|
*/
|
public void onAuthenticationError(int errorCode, CharSequence errString) {
|
}
|
|
/**
|
* Called when a recoverable error has been encountered during authentication. The help
|
* string is provided to give the user guidance for what went wrong, such as
|
* "Sensor dirty, please clean it."
|
*
|
* @param helpCode An integer identifying the error message
|
* @param helpString A human-readable string that can be shown in UI
|
*/
|
public void onAuthenticationHelp(int helpCode, CharSequence helpString) {
|
}
|
|
/**
|
* Called when a face is recognized.
|
*
|
* @param result An object containing authentication-related data
|
*/
|
public void onAuthenticationSucceeded(AuthenticationResult result) {
|
}
|
|
/**
|
* Called when a face is detected but not recognized.
|
*/
|
public void onAuthenticationFailed() {
|
}
|
|
/**
|
* Called when a face image has been acquired, but wasn't processed yet.
|
*
|
* @param acquireInfo one of FACE_ACQUIRED_* constants
|
* @hide
|
*/
|
public void onAuthenticationAcquired(int acquireInfo) {
|
}
|
}
|
|
/**
|
* Callback structure provided to {@link FaceManager#enroll(long,
|
* EnrollmentCallback, CancellationSignal, int). Users of {@link #FaceAuthenticationManager()}
|
* must provide an implementation of this to {@link FaceManager#enroll(long,
|
* CancellationSignal, int, EnrollmentCallback) for listening to face enrollment events.
|
*
|
* @hide
|
*/
|
public abstract static class EnrollmentCallback {
|
|
/**
|
* Called when an unrecoverable error has been encountered and the operation is complete.
|
* No further callbacks will be made on this object.
|
*
|
* @param errMsgId An integer identifying the error message
|
* @param errString A human-readable error string that can be shown in UI
|
*/
|
public void onEnrollmentError(int errMsgId, CharSequence errString) {
|
}
|
|
/**
|
* Called when a recoverable error has been encountered during enrollment. The help
|
* string is provided to give the user guidance for what went wrong, such as
|
* "Image too dark, uncover light source" or what they need to do next, such as
|
* "Rotate face up / down."
|
*
|
* @param helpMsgId An integer identifying the error message
|
* @param helpString A human-readable string that can be shown in UI
|
*/
|
public void onEnrollmentHelp(int helpMsgId, CharSequence helpString) {
|
}
|
|
/**
|
* Called as each enrollment step progresses. Enrollment is considered complete when
|
* remaining reaches 0. This function will not be called if enrollment fails. See
|
* {@link EnrollmentCallback#onEnrollmentError(int, CharSequence)}
|
*
|
* @param remaining The number of remaining steps
|
*/
|
public void onEnrollmentProgress(int remaining) {
|
}
|
}
|
|
/**
|
* Callback structure provided to {@link #remove}. Users of {@link FaceManager}
|
* may
|
* optionally provide an implementation of this to
|
* {@link #remove(Face, int, RemovalCallback)} for listening to face template
|
* removal events.
|
*
|
* @hide
|
*/
|
public abstract static class RemovalCallback {
|
|
/**
|
* Called when the given face can't be removed.
|
*
|
* @param face The face that the call attempted to remove
|
* @param errMsgId An associated error message id
|
* @param errString An error message indicating why the face id can't be removed
|
*/
|
public void onRemovalError(Face face, int errMsgId, CharSequence errString) {
|
}
|
|
/**
|
* Called when a given face is successfully removed.
|
*
|
* @param face The face template that was removed.
|
*/
|
public void onRemovalSucceeded(Face face, int remaining) {
|
}
|
}
|
|
/**
|
* @hide
|
*/
|
public abstract static class LockoutResetCallback {
|
|
/**
|
* Called when lockout period expired and clients are allowed to listen for face
|
* authentication
|
* again.
|
*/
|
public void onLockoutReset() {
|
}
|
}
|
|
/**
|
* @hide
|
*/
|
public abstract static class SetFeatureCallback {
|
public abstract void onCompleted(boolean success, int feature);
|
}
|
|
/**
|
* @hide
|
*/
|
public abstract static class GetFeatureCallback {
|
public abstract void onCompleted(boolean success, int feature, boolean value);
|
}
|
|
private class OnEnrollCancelListener implements OnCancelListener {
|
@Override
|
public void onCancel() {
|
cancelEnrollment();
|
}
|
}
|
|
private class OnAuthenticationCancelListener implements OnCancelListener {
|
private CryptoObject mCrypto;
|
|
OnAuthenticationCancelListener(CryptoObject crypto) {
|
mCrypto = crypto;
|
}
|
|
@Override
|
public void onCancel() {
|
cancelAuthentication(mCrypto);
|
}
|
}
|
|
private class MyHandler extends Handler {
|
private MyHandler(Context context) {
|
super(context.getMainLooper());
|
}
|
|
private MyHandler(Looper looper) {
|
super(looper);
|
}
|
|
@Override
|
public void handleMessage(android.os.Message msg) {
|
Trace.beginSection("FaceManager#handleMessage: " + Integer.toString(msg.what));
|
switch (msg.what) {
|
case MSG_ENROLL_RESULT:
|
sendEnrollResult((Face) msg.obj, msg.arg1 /* remaining */);
|
break;
|
case MSG_ACQUIRED:
|
sendAcquiredResult((Long) msg.obj /* deviceId */, msg.arg1 /* acquire info */,
|
msg.arg2 /* vendorCode */);
|
break;
|
case MSG_AUTHENTICATION_SUCCEEDED:
|
sendAuthenticatedSucceeded((Face) msg.obj, msg.arg1 /* userId */);
|
break;
|
case MSG_AUTHENTICATION_FAILED:
|
sendAuthenticatedFailed();
|
break;
|
case MSG_ERROR:
|
sendErrorResult((Long) msg.obj /* deviceId */, msg.arg1 /* errMsgId */,
|
msg.arg2 /* vendorCode */);
|
break;
|
case MSG_REMOVED:
|
sendRemovedResult((Face) msg.obj, msg.arg1 /* remaining */);
|
break;
|
case MSG_SET_FEATURE_COMPLETED:
|
sendSetFeatureCompleted((boolean) msg.obj /* success */,
|
msg.arg1 /* feature */);
|
break;
|
case MSG_GET_FEATURE_COMPLETED:
|
SomeArgs args = (SomeArgs) msg.obj;
|
sendGetFeatureCompleted((boolean) args.arg1 /* success */,
|
args.argi1 /* feature */,
|
(boolean) args.arg2 /* value */);
|
args.recycle();
|
break;
|
default:
|
Log.w(TAG, "Unknown message: " + msg.what);
|
}
|
Trace.endSection();
|
}
|
}
|
|
private void sendSetFeatureCompleted(boolean success, int feature) {
|
if (mSetFeatureCallback == null) {
|
return;
|
}
|
mSetFeatureCallback.onCompleted(success, feature);
|
}
|
|
private void sendGetFeatureCompleted(boolean success, int feature, boolean value) {
|
if (mGetFeatureCallback == null) {
|
return;
|
}
|
mGetFeatureCallback.onCompleted(success, feature, value);
|
}
|
|
private void sendRemovedResult(Face face, int remaining) {
|
if (mRemovalCallback == null) {
|
return;
|
}
|
if (face == null) {
|
Log.e(TAG, "Received MSG_REMOVED, but face is null");
|
return;
|
}
|
mRemovalCallback.onRemovalSucceeded(face, remaining);
|
}
|
|
private void sendErrorResult(long deviceId, int errMsgId, int vendorCode) {
|
// emulate HAL 2.1 behavior and send real errMsgId
|
final int clientErrMsgId = errMsgId == FACE_ERROR_VENDOR
|
? (vendorCode + FACE_ERROR_VENDOR_BASE) : errMsgId;
|
if (mEnrollmentCallback != null) {
|
mEnrollmentCallback.onEnrollmentError(clientErrMsgId,
|
getErrorString(mContext, errMsgId, vendorCode));
|
} else if (mAuthenticationCallback != null) {
|
mAuthenticationCallback.onAuthenticationError(clientErrMsgId,
|
getErrorString(mContext, errMsgId, vendorCode));
|
} else if (mRemovalCallback != null) {
|
mRemovalCallback.onRemovalError(mRemovalFace, clientErrMsgId,
|
getErrorString(mContext, errMsgId, vendorCode));
|
}
|
}
|
|
private void sendEnrollResult(Face face, int remaining) {
|
if (mEnrollmentCallback != null) {
|
mEnrollmentCallback.onEnrollmentProgress(remaining);
|
}
|
}
|
|
private void sendAuthenticatedSucceeded(Face face, int userId) {
|
if (mAuthenticationCallback != null) {
|
final AuthenticationResult result =
|
new AuthenticationResult(mCryptoObject, face, userId);
|
mAuthenticationCallback.onAuthenticationSucceeded(result);
|
}
|
}
|
|
private void sendAuthenticatedFailed() {
|
if (mAuthenticationCallback != null) {
|
mAuthenticationCallback.onAuthenticationFailed();
|
}
|
}
|
|
private void sendAcquiredResult(long deviceId, int acquireInfo, int vendorCode) {
|
if (mAuthenticationCallback != null) {
|
mAuthenticationCallback.onAuthenticationAcquired(acquireInfo);
|
}
|
final String msg = getAcquiredString(mContext, acquireInfo, vendorCode);
|
final int clientInfo = acquireInfo == FACE_ACQUIRED_VENDOR
|
? (vendorCode + FACE_ACQUIRED_VENDOR_BASE) : acquireInfo;
|
if (mEnrollmentCallback != null) {
|
mEnrollmentCallback.onEnrollmentHelp(clientInfo, msg);
|
} else if (mAuthenticationCallback != null && msg != null) {
|
mAuthenticationCallback.onAuthenticationHelp(clientInfo, msg);
|
}
|
}
|
}
|