和你一起终身学习,这里是程序员Android
经典好文推荐,通过阅读本文,您将收获以下知识点:
一、人脸识别身份验证HIDL
二、人脸模块流程分析
三、人脸录入
四、人脸匹配
五、人脸解锁屏幕
一、人脸识别身份验证HIDL
借助人脸识别身份验证功能,用户只需要将自己的面孔对准设备即可将其解锁。Android 10 增加了对一种新的人脸识别身份验证堆栈的支持,这种堆栈可安全处理摄像头帧,从而在支持的硬件上进行人脸识别身份验证时保障安全和隐私。Android 10 还提供了一种简单的安全合规实现方法,以支持通过应用集成来完成交易(例如网上银行或其他服务)。
Android 人脸识别身份验证堆栈是Android 10中的新实现。该实现引入了 IBiometricsFace.hal、IBiometricsFaceClientCallback.hal、和type.hal接口。
要实现Face HIDL,你必须在某个供应商专用库中实现 IBiometricsFace.hal的所有方法
人脸识别架构
BiometricPrompt API包括人脸识别、指纹识别和虹膜识别在内的所有生物识别身份验证方法。Face HAL会与以下组件交互:
FaceManager
FaceManager是一个私有接口,用于维护FaceService的之间连接。Keyguard通过该接口访问具有自定义界面的人脸识别身份验证硬件。应用无权访问FaceManager,必须改为使用BiometricPrompt。
FaceService
该框架实现用于管理对人脸识别身份验证硬件的访问权限。它包含基本的注册和身份验证状态机以及各种其他辅助程序(例如枚举程序)。处于稳定性和安全性方面的考虑,不允许在此进程中运行任何供应商代码。所有供应商代码都通过Face 1.0 HIDL接口访问。
faced
这是一个Linux可执行文件,用于实现供FaceService使用的Face 1.0 HIDL 接口。它会将自身注册为 IBiometricsFace@1.0以便FaceService能够找到它。
二、人脸模块流程分析
要实现Face HIDL,你必须在某个供应商专用库中实现 IBiometricsFace.hal的所有方法
IBiometricsFace.hal中主要包括以下主要方法:setCallback(); setActiveUser(); revokeChallenge(); enroll(); cancel(); enumerate(); remove(); authenticate(); userActivity; resetLockout(); 其余的四个都是同步方法,应将其阻塞时间缩至最短以免拖延框架。它们分别是generateChallenge(); setFeature(); getFeature; getAuthentitorId()
人脸模块中的录入,匹配,移除是三个大部分;
三、人脸录入
人脸录入的入口在Settings中的FaceEnrollEnrolling.java中
在这个类中没有看到明显的录入的方法,只有一些UI的加载和一些录入动画的逻辑。
在此类中的on EnrollmentProgressChange(int steps, int remaining)更新录入进度的方法中用通过传递进来的remaining来获取实际的进度;当remaining = 0 时打开录入结束界面launchFinish(mToken);
packages/apps/Settings/src/com/android/settings/biometrics/face/FaceEnrollEnrolling.java
@Override
public void onEnrollmentProgressChange(int steps, int remaining) {
if (DEBUG) {
Log.v(TAG, "Steps: " + steps + " Remaining: " + remaining);
}
/*重点关注*/
mPreviewFragment.onEnrollmentProgressChange(steps, remaining);
// TODO: Update the actual animation
showError("Steps: " + steps + " Remaining: " + remaining);
// TODO: Have this match any animations that UX comes up with
if (remaining == 0) {
launchFinish(mToken);
}
packages/apps/Settings/src/com/android/settings/biometrics/face/FaceEnrollPreviewFragment.java
@Override
public void onEnrollmentProgressChange(int steps, int remaining) {
/*重点关注*/
mAnimationDrawable.onEnrollmentProgressChange(steps, remaining);
}
packages/apps/Settings/src/com/android/settings/biometrics/face/FaceEnrollAnimationDrawable.java
@Override
public void onEnrollmentProgressChange(int steps, int remaining) {
/*重点关注*/
mParticleCollection.onEnrollmentProgressChange(steps, remaining);
}
packages/apps/Settings/src/com/android/settings/biometrics/face/ParticleCollection.java
/*重点关注*/
public class ParticleCollection implements BiometricEnrollSidecar.Listener {
......
@Override
public void onEnrollmentProgressChange(int steps, int remaining) {
if (remaining == 0) {
updateState(STATE_COMPLETE);
}
}
}
由此可以看出此类是实现了BiometricEnrollSidecar.Listener从而调用onEnrollmentProgressChange通过传入的remaining值对录入人脸的进度条进行更新的
packages/apps/Settings/src/com/android/settings/biometrics/BiometricEnrollSidecar.java
public abstract class BiometricEnrollSidecar extends InstrumentedFragment {
public interface Listener {
void onEnrollmentHelp(int helpMsgId, CharSequence helpString);
void onEnrollmentError(int errMsgId, CharSequence errString);
/*重点关注*/
void onEnrollmentProgressChange(int steps, int remaining);
}
onEnrollmentProgressChange
protected void onEnrollmentProgress(int remaining) {
if (mEnrollmentSteps == -1) {
mEnrollmentSteps = remaining;
}
mEnrollmentRemaining = remaining;
mDone = remaining == 0;
if (mListener != null) {
/*重点关注*/
mListener.onEnrollmentProgressChange(mEnrollmentSteps, remaining);
} else {
mQueuedEvents.add(new QueuedEnrollmentProgress(mEnrollmentSteps, remaining));
}
}
底层在录制人脸的时候会在FaceManager中调用onEnrollmentProgress方法,并将进度remainiing返回过来,BiometricEnrollSidecar内部写有Listener,在使用Listener的对象将onEnrollmentProgress的值传递进去,使更多实现Listener接口的类可以接收到
packages/apps/Settings/src/com/android/settings/biometrics/face/FaceEnrollEnrolling.java
我们再回到这个类中去看看startEnrollment录入的方法
@Override
public void startEnrollment() {
/*重点关注*/
super.startEnrollment();
mPreviewFragment = (FaceEnrollPreviewFragment) getSupportFragmentManager()
.findFragmentByTag(TAG_FACE_PREVIEW);
if (mPreviewFragment == null) {
mPreviewFragment = new FaceEnrollPreviewFragment();
getSupportFragmentManager().beginTransaction().add(mPreviewFragment, TAG_FACE_PREVIEW)
.commitAllowingStateLoss();
}
mPreviewFragment.setListener(mListener);
}
此方法中没有明显录入的方法,可见录入方法存在于他的父类中
packages/apps/Settings/src/com/android/settings/biometrics/BiometricsEnrollEnrolling.java
public void startEnrollment() {
mSidecar = (BiometricEnrollSidecar) getSupportFragmentManager()
.findFragmentByTag(TAG_SIDECAR);
if (mSidecar == null) {
mSidecar = getSidecar();
getSupportFragmentManager().beginTransaction().add(mSidecar, TAG_SIDECAR)
.commitAllowingStateLoss();
}
/*重点关注*/
mSidecar.setListener(this);
}
由此可知是通过给mSidecar设置setListener监听传入变化而开始录入的
packages/apps/Settings/src/com/android/settings/biometrics/face/FaceEnrollEnrolling.java
@Override
public void startEnrollment() {
super.startEnrollment();
if (mUserId != UserHandle.USER_NULL) {
mFaceManager.setActiveUser(mUserId);
}
/*重点关注*/
mFaceManager.enroll(mToken, mEnrollmentCancel,
mEnrollmentCallback, mDisabledFeatures);
}
frameworks/base/core/java/android/hardware/face/FaceManager.java
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();
}
}
}
frameworks/base/services/core/java/com/android/server/biometrics/face/FaceService.java
public void enroll(final IBinder token, final byte[] cryptoToken,
final IFaceServiceReceiver receiver, final String opPackageName,
final int[] disabledFeatures) {
checkPermission(MANAGE_BIOMETRIC);
final boolean restricted = isRestricted();
final EnrollClientImpl client = new EnrollClientImpl(getContext(), mDaemonWrapper,
mHalDeviceId, token, new ServiceListenerImpl(receiver), mCurrentUserId,
0 /* groupId */, cryptoToken, restricted, opPackageName, disabledFeatures) {
@Override
public int[] getAcquireIgnorelist() {
return mEnrollIgnoreList;
}
@Override
public int[] getAcquireVendorIgnorelist() {
return mEnrollIgnoreListVendor;
}
@Override
public boolean shouldVibrate() {
return false;
}
@Override
protected int statsModality() {
return FaceService.this.statsModality();
}
};
/*重点关注*/
enrollInternal(client, mCurrentUserId);
}
frameworks/base/services/core/java/com/android/server/biometrics/BiometricServiceBase.java
if (hasReachedEnrollmentLimit(userId)) {
return;
}
// Group ID is arbitrarily set to parent profile user ID. It just represents
// the default biometrics for the user.
if (!isCurrentUserOrProfile(userId)) {
return;
}
mHandler.post(() -> {
/*重点关注*/
startClient(client, true /* initiatedByClient */);
});
}
startClient(client, true /* initiatedByClient */);
ClientMonitor currentClient = mCurrentClient;
if (currentClient != null) {
if (DEBUG) Slog.v(getTag(), "request stop current client " +
currentClient.getOwnerString());
// This check only matters for FingerprintService, since enumerate may call back
// multiple times.
if (currentClient instanceof InternalEnumerateClient
|| currentClient instanceof InternalRemovalClient) {
// This condition means we're currently running internal diagnostics to
// remove extra templates in the hardware and/or the software
// TODO: design an escape hatch in case client never finishes
if (newClient != null) {
Slog.w(getTag(), "Internal cleanup in progress but trying to start client "
+ newClient.getClass().getSuperclass().getSimpleName()
+ "(" + newClient.getOwnerString() + ")"
+ ", initiatedByClient = " + initiatedByClient);
}
} else {
currentClient.stop(initiatedByClient);
// Only post the reset runnable for non-cleanup clients. Cleanup clients should
// never be forcibly stopped since they ensure synchronization between HAL and
// framework. Thus, we should instead just start the pending client once cleanup
// finishes instead of using the reset runnable.
mHandler.removeCallbacks(mResetClientState);
mHandler.postDelayed(mResetClientState, CANCEL_TIMEOUT_LIMIT);
}
mPendingClient = newClient;
} else if (newClient != null) {
// For BiometricPrompt clients, do not start until
// <Biometric>Service#startPreparedClient is called. BiometricService waits until all
// modalities are ready before initiating authentication.
if (newClient instanceof AuthenticationClient) {
AuthenticationClient client = (AuthenticationClient) newClient;
if (client.isBiometricPrompt()) {
if (DEBUG) Slog.v(getTag(), "Returning cookie: " + client.getCookie());