第一部分,人脸识别身份验证HIDL

借助人脸识别身份验证功能,用户只需要将自己的面孔对准设备即可将其解锁。Android 10 增加了对一种新的人脸识别身份验证堆栈的支持,这种堆栈可安全处理摄像头帧,从而在支持的硬件上进行人脸识别身份验证时保障安全和隐私。Android 10 还提供了一种简单的安全合规实现方法,以支持通过应用集成来完成交易(例如网上银行或其他服务)。

Android 人脸识别身份验证堆栈是Android 10中的新实现。该实现引入了 IBiometricsFace.halIBiometricsFaceClientCallback.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

@Overridepublic void onEnrollmentProgressChange(int steps, int remaining) {if (DEBUG) {Log.v(TAG, "Steps: " + steps + " Remaining: " + remaining);}/*重点关注*/mPreviewFragment.onEnrollmentProgressChange(steps, remaining);// TODO: Update the actual animationshowError("Steps: " + steps + " Remaining: " + remaining);// TODO: Have this match any animations that UX comes up withif (remaining == 0) {launchFinish(mToken);}

packages/apps/Settings/src/com/android/settings/biometrics/face/FaceEnrollPreviewFragment.java

    @Overridepublic void onEnrollmentProgressChange(int steps, int remaining) {/*重点关注*/mAnimationDrawable.onEnrollmentProgressChange(steps, remaining);}

packages/apps/Settings/src/com/android/settings/biometrics/face/FaceEnrollAnimationDrawable.java

@Overridepublic 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 {......@Overridepublic 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录入的方法

@Overridepublic 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

@Overrideprotected BiometricEnrollSidecar getSidecar() {final int[] disabledFeatures = new int[mDisabledFeatures.size()];for (int i = 0; i < mDisabledFeatures.size(); i++) {disabledFeatures[i] = mDisabledFeatures.get(i);}/*重点关注*/return new FaceEnrollSidecar(disabledFeatures);}

而又从FaceEnrollEnrolling.java中可知是给mSidecar设置的是FaceEnrollSidecar

packages/apps/Settings/src/com/android/settings/biometrics/face/FaceEnrollSidecar.java

FaceEnrollSidecar.java中的父类中的onStart方法去启动startEnrollment()方法

@Overridepublic 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

    @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();}}}

frameworks/base/services/core/java/com/android/server/biometrics/face/FaceService.java

        @Override // Binder callpublic 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) {@Overridepublic int[] getAcquireIgnorelist() {return mEnrollIgnoreList;}@Overridepublic int[] getAcquireVendorIgnorelist() {return mEnrollIgnoreListVendor;}@Overridepublic boolean shouldVibrate() {return false;}@Overrideprotected int statsModality() {return FaceService.this.statsModality();}};/*重点关注*/enrollInternal(client, mCurrentUserId);}

frameworks/base/services/core/java/com/android/server/biometrics/BiometricServiceBase.java

     protected void enrollInternal(EnrollClientImpl client, int userId) {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 */);

    private void startClient(ClientMonitor newClient, boolean 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 finishesif (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());mCurrentClient = newClient;if (mBiometricService == null) {mBiometricService = IBiometricService.Stub.asInterface(ServiceManager.getService(Context.BIOMETRIC_SERVICE));}try {mBiometricService.onReadyForAuthentication(client.getCookie(),client.getRequireConfirmation(), client.getTargetUserId());} catch (RemoteException e) {Slog.e(getTag(), "Remote exception", e);}return;}}// We are not a BiometricPrompt client, start the client immediatelymCurrentClient = newClient;/*重点关注*/startCurrentClient(mCurrentClient.getCookie());}}

在此将EnrollClient的对象传进去

startCurrentClient(mCurrentClient.getCookie());

protected void startCurrentClient(int cookie) {if (mCurrentClient == null) {Slog.e(getTag(), "Trying to start null client!");return;}if (DEBUG) Slog.v(getTag(), "starting client "+ mCurrentClient.getClass().getSuperclass().getSimpleName()+ "(" + mCurrentClient.getOwnerString() + ")"+ " cookie: " + cookie + "/" + mCurrentClient.getCookie());if (cookie != mCurrentClient.getCookie()) {Slog.e(getTag(), "Mismatched cookie");return;}notifyClientActiveCallbacks(true);/*重点关注*/mCurrentClient.start();}

frameworks/base/services/core/java/com/android/server/biometrics/EnrollClient.java

    @Overridepublic int start() {mEnrollmentStartTimeMs = System.currentTimeMillis();final int timeout = (int) (ENROLLMENT_TIMEOUT_MS / MS_PER_SEC);try {final ArrayList<Integer> disabledFeatures = new ArrayList<>();for (int i = 0; i < mDisabledFeatures.length; i++) {disabledFeatures.add(mDisabledFeatures[i]);}/*重点关注*/final int result = getDaemonWrapper().enroll(mCryptoToken, getGroupId(), timeout,disabledFeatures);if (result != 0) {Slog.w(getLogTag(), "startEnroll failed, result=" + result);mMetricsLogger.histogram(mConstants.tagEnrollStartError(), result);onError(getHalDeviceId(), BiometricConstants.BIOMETRIC_ERROR_HW_UNAVAILABLE,0 /* vendorCode */);return result;}} catch (RemoteException e) {Slog.e(getLogTag(), "startEnroll failed", e);}return 0; // success}

start 方法会调用faced,调用底层的人脸库,底层库返回结果后会调用onEnrollResult来反馈结果receiver,再往上层反馈。这就是人脸的录制流程。在onEnrollResult中当remaining等于0的时候完成录制,调用addBiometricForUser

FaceManager.java中注册了IFaceServiceReceiver,实现onEnrollResult方法发送 MSG_ENROLL_RESULT

frameworks/base/core/java/android/hardware/face/FaceManager.java

    private IFaceServiceReceiver mServiceReceiver = new IFaceServiceReceiver.Stub() {@Override // binder callpublic void onEnrollResult(long deviceId, int faceId, int remaining) {/*重点关注*/mHandler.obtainMessage(MSG_ENROLL_RESULT, remaining, 0,new Face(null, faceId, deviceId)).sendToTarget();}...

MSG_ENROLL_RESULT

        @Overridepublic 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;...

sendEnrollResult

    private void sendEnrollResult(Face face, int remaining) {if (mEnrollmentCallback != null) {mEnrollmentCallback.onEnrollmentProgress(remaining);}}

Settings中也注册了此回调,所以会实时更新人脸录入进度

二、人脸匹配

人脸解锁的入口在Keyguard

系统灭屏之后会调用PhoneWindowManagerstartedGoingToSleep方法,继而调用KeyguardDelegate.onStartedGoingToSleep方法。

继而又会调用KeyguardServiceWrapper.java ==》onStartedGoingToSleep()方法;再调用KeyguardService.java ==》onStartedGoingToSleep()方法;并最终在KeyguardViewMediator.java ==》dispatchStartedGoingToSleep() 达到对GoingToSleep事件的监听

frameworks/base/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java

public void dispatchStartedGoingToSleep(int why) {/*重点关注*/mHandler.sendMessage(mHandler.obtainMessage(MSG_STARTED_GOING_TO_SLEEP, why, 0));}

MSG_STARTED_GOING_TO_SLEEP

case MSG_STARTED_GOING_TO_SLEEP:/*重点关注*/handleStartedGoingToSleep(msg.arg1);break;

handleStartedGoingToSleep(msg.arg1);

    protected void handleStartedGoingToSleep(int arg1) {clearBiometricRecognized();final int count = mCallbacks.size();for (int i = 0; i < count; i++) {KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get();if (cb != null) {cb.onStartedGoingToSleep(arg1);}}mGoingToSleep = true;/*重点关注*/updateBiometricListeningState();//更新生物识别状态}

updateBiometricListeningState()

 //在这个方法中我们可以看到同时更新了指纹和人脸的状态private void updateBiometricListeningState() {updateFingerprintListeningState();/*重点关注*/updateFaceListeningState();}

updateFaceListeningState()

private void updateFaceListeningState() {// If this message exists, we should not authenticate again until this message is// consumed by the handlerif (mHandler.hasMessages(MSG_BIOMETRIC_AUTHENTICATION_CONTINUE)) {return;}mHandler.removeCallbacks(mRetryFaceAuthentication);boolean shouldListenForFace = shouldListenForFace();if (mFaceRunningState == BIOMETRIC_STATE_RUNNING && !shouldListenForFace) {stopListeningForFace();} else if (mFaceRunningState != BIOMETRIC_STATE_RUNNING&& shouldListenForFace) {/*重点关注*/startListeningForFace();}}

startListeningForFace()

private void startListeningForFace() {if (mFaceRunningState == BIOMETRIC_STATE_CANCELLING) {setFaceRunningState(BIOMETRIC_STATE_CANCELLING_RESTARTING);return;}if (DEBUG) Log.v(TAG, "startListeningForFace()");int userId = getCurrentUser();if (isUnlockWithFacePossible(userId)) {if (mFaceCancelSignal != null) {mFaceCancelSignal.cancel();}mFaceCancelSignal = new CancellationSignal();/*重点关注*/mFaceManager.authenticate(null, mFaceCancelSignal, 0,mFaceAuthenticationCallback, null, userId);setFaceRunningState(BIOMETRIC_STATE_RUNNING);}}

frameworks/base/core/java/android/hardware/face/FaceManager.java

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();}}}

frameworks/base/services/core/java/com/android/server/biometrics/face/FaceService.java

@Override // Binder callpublic void authenticate(final IBinder token, final long opId, int userId,final IFaceServiceReceiver receiver, final int flags,final String opPackageName) {checkPermission(USE_BIOMETRIC_INTERNAL);updateActiveGroup(userId, opPackageName);final boolean restricted = isRestricted();final AuthenticationClientImpl client = new FaceAuthClient(getContext(),mDaemonWrapper, mHalDeviceId, token, new ServiceListenerImpl(receiver),mCurrentUserId, 0 /* groupId */, opId, restricted, opPackageName,0 /* cookie */, false /* requireConfirmation */);/*重点关注*/authenticateInternal(client, opId, opPackageName);}

frameworks/base/services/core/java/com/android/server/biometrics/BiometricServiceBase.java

protected void authenticateInternal(AuthenticationClientImpl client, long opId,String opPackageName) {final int callingUid = Binder.getCallingUid();final int callingPid = Binder.getCallingPid();final int callingUserId = UserHandle.getCallingUserId();authenticateInternal(client, opId, opPackageName, callingUid, callingPid, callingUserId);}

frameworks/base/services/core/java/com/android/server/biometrics/BiometricServiceBase.java

protected void authenticateInternal(AuthenticationClientImpl client, long opId,String opPackageName, int callingUid, int callingPid, int callingUserId) {if (!canUseBiometric(opPackageName, true /* foregroundOnly */, callingUid, callingPid,callingUserId)) {if (DEBUG) Slog.v(getTag(), "authenticate(): reject " + opPackageName);return;}mHandler.post(() -> {mMetricsLogger.histogram(getConstants().tagAuthToken(), opId != 0L ? 1 : 0);// Get performance stats object for this user.HashMap<Integer, PerformanceStats> pmap= (opId == 0) ? mPerformanceMap : mCryptoPerformanceMap;PerformanceStats stats = pmap.get(mCurrentUserId);if (stats == null) {stats = new PerformanceStats();pmap.put(mCurrentUserId, stats);}mPerformanceStats = stats;mIsCrypto = (opId != 0);/*重点关注*/startAuthentication(client, opPackageName);});}

startAuthentication(client, opPackageName);

    private void startAuthentication(AuthenticationClientImpl client, String opPackageName) {if (DEBUG) Slog.v(getTag(), "startAuthentication(" + opPackageName + ")");int lockoutMode = getLockoutMode();if (lockoutMode != AuthenticationClient.LOCKOUT_NONE) {Slog.v(getTag(), "In lockout mode(" + lockoutMode + ") ; disallowing authentication");int errorCode = lockoutMode == AuthenticationClient.LOCKOUT_TIMED ?BiometricConstants.BIOMETRIC_ERROR_LOCKOUT :BiometricConstants.BIOMETRIC_ERROR_LOCKOUT_PERMANENT;if (!client.onError(getHalDeviceId(), errorCode, 0 /* vendorCode */)) {Slog.w(getTag(), "Cannot send permanent lockout message to client");}return;}/*重点关注*/startClient(client, true /* initiatedByClient */);//这里将AuthenticationClient传递进去}

startClient(client, true /* initiatedByClient */);

 private void startClient(ClientMonitor newClient, boolean 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 finishesif (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());mCurrentClient = newClient;if (mBiometricService == null) {mBiometricService = IBiometricService.Stub.asInterface(ServiceManager.getService(Context.BIOMETRIC_SERVICE));}try {mBiometricService.onReadyForAuthentication(client.getCookie(),client.getRequireConfirmation(), client.getTargetUserId());} catch (RemoteException e) {Slog.e(getTag(), "Remote exception", e);}return;}}// We are not a BiometricPrompt client, start the client immediatelymCurrentClient = newClient;/*重点关注*/startCurrentClient(mCurrentClient.getCookie());//这里继续将AuthenticationClient传递进去}}

startCurrentClient(mCurrentClient.getCookie());

    protected void startCurrentClient(int cookie) {if (mCurrentClient == null) {Slog.e(getTag(), "Trying to start null client!");return;}if (DEBUG) Slog.v(getTag(), "starting client "+ mCurrentClient.getClass().getSuperclass().getSimpleName()+ "(" + mCurrentClient.getOwnerString() + ")"+ " cookie: " + cookie + "/" + mCurrentClient.getCookie());if (cookie != mCurrentClient.getCookie()) {Slog.e(getTag(), "Mismatched cookie");return;}notifyClientActiveCallbacks(true);/*重点关注*/mCurrentClient.start();//这里调用的是AuthenticationClient的start方法}

frameworks/base/services/core/java/com/android/server/biometrics/AuthenticationClient.java

    public int start() {mStarted = true;onStart();try {/*重点关注*/final int result = getDaemonWrapper().authenticate(mOpId, getGroupId());if (result != 0) {Slog.w(getLogTag(), "startAuthentication failed, result=" + result);mMetricsLogger.histogram(mConstants.tagAuthStartError(), result);onError(getHalDeviceId(), BiometricConstants.BIOMETRIC_ERROR_HW_UNAVAILABLE,0 /* vendorCode */);return result;}if (DEBUG) Slog.w(getLogTag(), "client " + getOwnerString() + " is authenticating...");} catch (RemoteException e) {Slog.e(getLogTag(), "startAuthentication failed", e);return ERROR_ESRCH;}return 0; // success}

start方法会调用faced,调用底层的人脸库,底层库返回结果后会调用onAuthenticated来反馈结果给receiver,在往上层反馈

三、人脸解锁屏幕

frameworks/base/services/core/java/com/android/server/biometrics/AuthenticationClient.java

    public boolean onAuthenticated(BiometricAuthenticator.Identifier identifier,boolean authenticated, ArrayList<Byte> token) {super.logOnAuthenticated(getContext(), authenticated, mRequireConfirmation,getTargetUserId(), isBiometricPrompt());final BiometricServiceBase.ServiceListener listener = getListener();mMetricsLogger.action(mConstants.actionBiometricAuth(), authenticated);boolean result = false;try {if (DEBUG) Slog.v(getLogTag(), "onAuthenticated(" + authenticated + ")"+ ", ID:" + identifier.getBiometricId()+ ", Owner: " + getOwnerString()+ ", isBP: " + isBiometricPrompt()+ ", listener: " + listener+ ", requireConfirmation: " + mRequireConfirmation+ ", user: " + getTargetUserId());if (authenticated) {mAlreadyDone = true;if (listener != null) {vibrateSuccess();}result = true;if (shouldFrameworkHandleLockout()) {resetFailedAttempts();}onStop();final byte[] byteToken = new byte[token.size()];for (int i = 0; i < token.size(); i++) {byteToken[i] = token.get(i);}if (isBiometricPrompt() && listener != null) {// BiometricService will add the token to keystorelistener.onAuthenticationSucceededInternal(mRequireConfirmation, byteToken);} else if (!isBiometricPrompt() && listener != null) {KeyStore.getInstance().addAuthToken(byteToken);try {// Explicitly have if/else here to make it super obvious in case the code is// touched in the future.if (!getIsRestricted()) {/*重点关注*/listener.onAuthenticationSucceeded(getHalDeviceId(), identifier, getTargetUserId());} else {listener.onAuthenticationSucceeded(getHalDeviceId(), null, getTargetUserId());}} catch (RemoteException e) {Slog.e(getLogTag(), "Remote exception", e);}} else {// Client not listeningSlog.w(getLogTag(), "Client not listening");result = true;}} else {if (listener != null) {vibrateError();}// Allow system-defined limit of number of attempts before giving upfinal int lockoutMode = handleFailedAttempt();if (lockoutMode != LOCKOUT_NONE && shouldFrameworkHandleLockout()) {Slog.w(getLogTag(), "Forcing lockout (driver code should do this!), mode("+ lockoutMode + ")");stop(false);final int errorCode = lockoutMode == LOCKOUT_TIMED? BiometricConstants.BIOMETRIC_ERROR_LOCKOUT: BiometricConstants.BIOMETRIC_ERROR_LOCKOUT_PERMANENT;onError(getHalDeviceId(), errorCode, 0 /* vendorCode */);} else {// Don't send onAuthenticationFailed if we're in lockout, it causes a// janky UI on Keyguard/BiometricPrompt since "authentication failed"// will show briefly and be replaced by "device locked out" message.if (listener != null) {if (isBiometricPrompt()) {listener.onAuthenticationFailedInternal(getCookie(),getRequireConfirmation());} else {listener.onAuthenticationFailed(getHalDeviceId());}}}result = lockoutMode != LOCKOUT_NONE; // in a lockout mode}} catch (RemoteException e) {Slog.e(getLogTag(), "Remote exception", e);result = true;}return result;}

frameworks/base/services/core/java/com/android/server/biometrics/BiometricServiceBase.java

    protected interface ServiceListener {default void onEnrollResult(BiometricAuthenticator.Identifier identifier,int remaining) throws RemoteException {};void onAcquired(long deviceId, int acquiredInfo, int vendorCode) throws RemoteException;/*重点关注*/default void onAuthenticationSucceeded(long deviceId,BiometricAuthenticator.Identifier biometric, int userId) throws RemoteException {throw new UnsupportedOperationException("Stub!");}default void onAuthenticationSucceededInternal(boolean requireConfirmation, byte[] token)throws RemoteException {throw new UnsupportedOperationException("Stub!");}default void onAuthenticationFailed(long deviceId) throws RemoteException {throw new UnsupportedOperationException("Stub!");}default void onAuthenticationFailedInternal(int cookie, boolean requireConfirmation)throws RemoteException {throw new UnsupportedOperationException("Stub!");}void onError(long deviceId, int error, int vendorCode, int cookie) throws RemoteException;default void onRemoved(BiometricAuthenticator.Identifier identifier,int remaining) throws RemoteException {};default void onEnumerated(BiometricAuthenticator.Identifier identifier,int remaining) throws RemoteException {};}

onAuthenticationSucceeded

/*** Wraps the callback interface from Service -> BiometricPrompt*/protected abstract class BiometricServiceListener implements ServiceListener {private IBiometricServiceReceiverInternal mWrapperReceiver;public BiometricServiceListener(IBiometricServiceReceiverInternal wrapperReceiver) {mWrapperReceiver = wrapperReceiver;}public IBiometricServiceReceiverInternal getWrapperReceiver() {return mWrapperReceiver;}@Overridepublic void onAuthenticationSucceededInternal(boolean requireConfirmation, byte[] token)throws RemoteException {if (getWrapperReceiver() != null) {/*重点关注*/getWrapperReceiver().onAuthenticationSucceeded(requireConfirmation, token);}}@Overridepublic void onAuthenticationFailedInternal(int cookie, boolean requireConfirmation)throws RemoteException {if (getWrapperReceiver() != null) {getWrapperReceiver().onAuthenticationFailed(cookie, requireConfirmation);}}}

frameworks/base/core/java/android/hardware/face/FaceManager.java

private IFaceServiceReceiver mServiceReceiver = new IFaceServiceReceiver.Stub() {......@Override // binder callpublic void onAuthenticationSucceeded(long deviceId, Face face, int userId) {/*重点关注*/mHandler.obtainMessage(MSG_AUTHENTICATION_SUCCEEDED, userId, 0, face).sendToTarget();}

MSG_AUTHENTICATION_SUCCEEDED


case MSG_AUTHENTICATION_SUCCEEDED:/*重点关注*/sendAuthenticatedSucceeded((Face) msg.obj, msg.arg1 /* userId */);break;

sendAuthenticatedSucceeded

private void sendAuthenticatedSucceeded(Face face, int userId) {if (mAuthenticationCallback != null) {final AuthenticationResult result =new AuthenticationResult(mCryptoObject, face, userId);/*重点关注*/mAuthenticationCallback.onAuthenticationSucceeded(result);}}

AuthenticationCallbackFm的一个内部回调接口

public abstract static class AuthenticationCallbackextends 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) {}}

frameworks/base/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java

AuthenticationCallback接口在KeyguardUpdateMonitor.java中实现,用于监听FaceService中人脸的解锁状态

@VisibleForTestingFaceManager.AuthenticationCallback mFaceAuthenticationCallback= new FaceManager.AuthenticationCallback() {@Overridepublic void onAuthenticationFailed() {handleFaceAuthFailed();}@Overridepublic void onAuthenticationSucceeded(FaceManager.AuthenticationResult result) {Trace.beginSection("KeyguardUpdateMonitor#onAuthenticationSucceeded");/*重点关注*/handleFaceAuthenticated(result.getUserId());Trace.endSection();}@Overridepublic void onAuthenticationHelp(int helpMsgId, CharSequence helpString) {handleFaceHelp(helpMsgId, helpString.toString());}@Overridepublic void onAuthenticationError(int errMsgId, CharSequence errString) {handleFaceError(errMsgId, errString.toString());}@Overridepublic void onAuthenticationAcquired(int acquireInfo) {handleFaceAcquired(acquireInfo);}};

handleFaceAuthenticated

private void handleFaceAuthenticated(int authUserId) {Trace.beginSection("KeyGuardUpdateMonitor#handlerFaceAuthenticated");try {final int userId;try {userId = ActivityManager.getService().getCurrentUser().id;} catch (RemoteException e) {Log.e(TAG, "Failed to get current user id: ", e);return;}if (userId != authUserId) {Log.d(TAG, "Face authenticated for wrong user: " + authUserId);return;}if (isFaceDisabled(userId)) {Log.d(TAG, "Face authentication disabled by DPM for userId: " + userId);return;}/*重点关注*/onFaceAuthenticated(userId);} finally {setFaceRunningState(BIOMETRIC_STATE_STOPPED);}Trace.endSection();}

onFaceAuthenticated(userId)

protected void onFaceAuthenticated(int userId) {Trace.beginSection("KeyGuardUpdateMonitor#onFaceAuthenticated");mUserFaceAuthenticated.put(userId, true);// Update/refresh trust state only if user can skip bouncerif (getUserCanSkipBouncer(userId)) {mTrustManager.unlockedByBiometricForUser(userId, BiometricSourceType.FACE);}// Don't send cancel if authentication succeedsmFaceCancelSignal = null;for (int i = 0; i < mCallbacks.size(); i++) {/*重点关注*/KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get();if (cb != null) {/*重点关注*/cb.onBiometricAuthenticated(userId,BiometricSourceType.FACE);}}mHandler.sendMessageDelayed(mHandler.obtainMessage(MSG_BIOMETRIC_AUTHENTICATION_CONTINUE),BIOMETRIC_CONTINUE_DELAY_MS);// Only authenticate face once when assistant is visiblemAssistantVisible = false;Trace.endSection();}

这里开始调用接口将解锁成功消息层层传递直至keyguard解锁,与指纹解锁逻辑一致

可以看到在onFaceAuthenticated(userId)方法中调用了KeyguardUpdateMonitorCallback这个抽象类的onBiometricAuthenticated()抽象方法,而BiometricUnlockController extends KeyguardUpdateMonitorCallback,并注册了回调mUpdateMonitor.registerCallback(this)

frameworks/base/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java

    /*** Called when a biometric is recognized.* @param userId the user id for which the biometric sample was authenticated* @param biometricSourceType*//*重点关注*/public void onBiometricAuthenticated(int userId, BiometricSourceType biometricSourceType) { }

frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java

    @Overridepublic void onBiometricAuthenticated(int userId, BiometricSourceType biometricSourceType) {Trace.beginSection("BiometricUnlockController#onBiometricAuthenticated");if (mUpdateMonitor.isGoingToSleep()) {mPendingAuthenticatedUserId = userId;mPendingAuthenticatedBioSourceType = biometricSourceType;Trace.endSection();return;}mMetricsLogger.write(new LogMaker(MetricsEvent.BIOMETRIC_AUTH).setType(MetricsEvent.TYPE_SUCCESS).setSubtype(toSubtype(biometricSourceType)));/*重点关注*/startWakeAndUnlock(calculateMode(biometricSourceType));}

startWakeAndUnlock(calculateMode(biometricSourceType));

    public void startWakeAndUnlock(int mode) {// TODO(b/62444020): remove when this bug is fixedLog.v(TAG, "startWakeAndUnlock(" + mode + ")");boolean wasDeviceInteractive = mUpdateMonitor.isDeviceInteractive();mMode = mode;mHasScreenTurnedOnSinceAuthenticating = false;if (mMode == MODE_WAKE_AND_UNLOCK_PULSING && pulsingOrAod()) {// If we are waking the device up while we are pulsing the clock and the// notifications would light up first, creating an unpleasant animation.// Defer changing the screen brightness by forcing doze brightness on our window// until the clock and the notifications are faded out.mStatusBarWindowController.setForceDozeBrightness(true);}// During wake and unlock, we need to draw black before waking up to avoid abrupt// brightness changes due to display state transitions.boolean alwaysOnEnabled = DozeParameters.getInstance(mContext).getAlwaysOn();boolean delayWakeUp = mode == MODE_WAKE_AND_UNLOCK && alwaysOnEnabled && mWakeUpDelay > 0;Runnable wakeUp = ()-> {if (!wasDeviceInteractive) {if (DEBUG_BIO_WAKELOCK) {Log.i(TAG, "bio wakelock: Authenticated, waking up...");}mPowerManager.wakeUp(SystemClock.uptimeMillis(), PowerManager.WAKE_REASON_GESTURE,"android.policy:BIOMETRIC");}if (delayWakeUp) {/*重点关注*/mKeyguardViewMediator.onWakeAndUnlocking();}Trace.beginSection("release wake-and-unlock");releaseBiometricWakeLock();Trace.endSection();};if (!delayWakeUp) {wakeUp.run();}switch (mMode) {case MODE_DISMISS_BOUNCER:Trace.beginSection("MODE_DISMISS");mStatusBarKeyguardViewManager.notifyKeyguardAuthenticated(false /* strongAuth */);Trace.endSection();break;case MODE_UNLOCK:case MODE_SHOW_BOUNCER:Trace.beginSection("MODE_UNLOCK or MODE_SHOW_BOUNCER");if (!wasDeviceInteractive) {mPendingShowBouncer = true;} else {showBouncer();}Trace.endSection();break;case MODE_WAKE_AND_UNLOCK_FROM_DREAM:case MODE_WAKE_AND_UNLOCK_PULSING:case MODE_WAKE_AND_UNLOCK:if (mMode == MODE_WAKE_AND_UNLOCK_PULSING) {Trace.beginSection("MODE_WAKE_AND_UNLOCK_PULSING");mMediaManager.updateMediaMetaData(false /* metaDataChanged */,true /* allowEnterAnimation */);} else if (mMode == MODE_WAKE_AND_UNLOCK){Trace.beginSection("MODE_WAKE_AND_UNLOCK");} else {Trace.beginSection("MODE_WAKE_AND_UNLOCK_FROM_DREAM");mUpdateMonitor.awakenFromDream();}mStatusBarWindowController.setStatusBarFocusable(false);if (delayWakeUp) {mHandler.postDelayed(wakeUp, mWakeUpDelay);} else {mKeyguardViewMediator.onWakeAndUnlocking();}if (mStatusBar.getNavigationBarView() != null) {mStatusBar.getNavigationBarView().setWakeAndUnlocking(true);}Trace.endSection();break;case MODE_ONLY_WAKE:case MODE_NONE:break;}mStatusBar.notifyBiometricAuthModeChanged();Trace.endSection();}

frameworks/base/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java

    public void onWakeAndUnlocking() {Trace.beginSection("KeyguardViewMediator#onWakeAndUnlocking");mWakeAndUnlocking = true;/*重点关注*/keyguardDone();Trace.endSection();}

keyguardDone();

    public void keyguardDone() {Trace.beginSection("KeyguardViewMediator#keyguardDone");if (DEBUG) Log.d(TAG, "keyguardDone()");userActivity();EventLog.writeEvent(70000, 2);/*重点关注*/Message msg = mHandler.obtainMessage(KEYGUARD_DONE);mHandler.sendMessage(msg);Trace.endSection();}

KEYGUARD_DONE

                case KEYGUARD_DONE:Trace.beginSection("KeyguardViewMediator#handleMessage KEYGUARD_DONE");/*重点关注*/handleKeyguardDone();Trace.endSection();break;

handleKeyguardDone();

    private void handleKeyguardDone() {Trace.beginSection("KeyguardViewMediator#handleKeyguardDone");final int currentUser = KeyguardUpdateMonitor.getCurrentUser();mUiOffloadThread.submit(() -> {if (mLockPatternUtils.isSecure(currentUser)) {mLockPatternUtils.getDevicePolicyManager().reportKeyguardDismissed(currentUser);}});if (DEBUG) Log.d(TAG, "handleKeyguardDone");synchronized (this) {resetKeyguardDonePendingLocked();}mUpdateMonitor.clearBiometricRecognized();if (mGoingToSleep) {Log.i(TAG, "Device is going to sleep, aborting keyguardDone");return;}if (mExitSecureCallback != null) {try {mExitSecureCallback.onKeyguardExitResult(true /* authenciated */);} catch (RemoteException e) {Slog.w(TAG, "Failed to call onKeyguardExitResult()", e);}mExitSecureCallback = null;// after succesfully exiting securely, no need to reshow// the keyguard when they've released the lockmExternallyEnabled = true;mNeedToReshowWhenReenabled = false;updateInputRestricted();}/*重点关注*/handleHide();Trace.endSection();}

handleHide();

    private void handleHide() {Trace.beginSection("KeyguardViewMediator#handleHide");// It's possible that the device was unlocked in a dream state. It's time to wake up.if (mAodShowing) {PowerManager pm = mContext.getSystemService(PowerManager.class);pm.wakeUp(SystemClock.uptimeMillis(), PowerManager.WAKE_REASON_GESTURE,"com.android.systemui:BOUNCER_DOZING");}synchronized (KeyguardViewMediator.this) {if (DEBUG) Log.d(TAG, "handleHide");if (mustNotUnlockCurrentUser()) {// In split system user mode, we never unlock system user. The end user has to// switch to another user.// TODO: We should stop it early by disabling the swipe up flow. Right now swipe up// still completes and makes the screen blank.if (DEBUG) Log.d(TAG, "Split system user, quit unlocking.");return;}mHiding = true;if (mShowing && !mOccluded) {mKeyguardGoingAwayRunnable.run();} else {/*重点关注*/handleStartKeyguardExitAnimation(SystemClock.uptimeMillis() + mHideAnimation.getStartOffset(),mHideAnimation.getDuration());}}Trace.endSection();}

handleStartKeyguardExitAnimation

    private void handleStartKeyguardExitAnimation(long startTime, long fadeoutDuration) {Trace.beginSection("KeyguardViewMediator#handleStartKeyguardExitAnimation");if (DEBUG) Log.d(TAG, "handleStartKeyguardExitAnimation startTime=" + startTime+ " fadeoutDuration=" + fadeoutDuration);synchronized (KeyguardViewMediator.this) {if (!mHiding) {// Tell ActivityManager that we canceled the keyguardExitAnimation.setShowingLocked(mShowing, mAodShowing, true /* force */);return;}mHiding = false;if (mWakeAndUnlocking && mDrawnCallback != null) {// Hack level over 9000: To speed up wake-and-unlock sequence, force it to report// the next draw from here so we don't have to wait for window manager to signal// this to our ViewRootImpl.mStatusBarKeyguardViewManager.getViewRootImpl().setReportNextDraw();notifyDrawn(mDrawnCallback);mDrawnCallback = null;}// only play "unlock" noises if not on a call (since the incall UI// disables the keyguard)if (TelephonyManager.EXTRA_STATE_IDLE.equals(mPhoneState)) {playSounds(false);}mWakeAndUnlocking = false;setShowingLocked(false, mAodShowing);mDismissCallbackRegistry.notifyDismissSucceeded();/*重点关注*/mStatusBarKeyguardViewManager.hide(startTime, fadeoutDuration);resetKeyguardDonePendingLocked();mHideAnimationRun = false;adjustStatusBarLocked();sendUserPresentBroadcast();}Trace.endSection();}

frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java

    /*** Hides the keyguard view*/public void hide(long startTime, long fadeoutDuration) {mShowing = false;mKeyguardMonitor.notifyKeyguardState(mShowing, mKeyguardMonitor.isSecure(), mKeyguardMonitor.isOccluded());launchPendingWakeupAction();if (KeyguardUpdateMonitor.getInstance(mContext).needsSlowUnlockTransition()) {fadeoutDuration = KEYGUARD_DISMISS_DURATION_LOCKED;}long uptimeMillis = SystemClock.uptimeMillis();long delay = Math.max(0, startTime + HIDE_TIMING_CORRECTION_MS - uptimeMillis);if (mStatusBar.isInLaunchTransition() ) {mStatusBar.fadeKeyguardAfterLaunchTransition(new Runnable() {@Overridepublic void run() {mStatusBarWindowController.setKeyguardShowing(false);mStatusBarWindowController.setKeyguardFadingAway(true);hideBouncer(true /* destroyView */);updateStates();}}, new Runnable() {@Overridepublic void run() {mStatusBar.hideKeyguard();mStatusBarWindowController.setKeyguardFadingAway(false);mViewMediatorCallback.keyguardGone();executeAfterKeyguardGoneAction();}});} else {executeAfterKeyguardGoneAction();boolean wakeUnlockPulsing =mBiometricUnlockController.getMode() == MODE_WAKE_AND_UNLOCK_PULSING;if (wakeUnlockPulsing) {delay = 0;fadeoutDuration = 240;}mStatusBar.setKeyguardFadingAway(startTime, delay, fadeoutDuration);mBiometricUnlockController.startKeyguardFadingAway();/*重点关注*/hideBouncer(true /* destroyView */);if (wakeUnlockPulsing) {mStatusBar.fadeKeyguardWhilePulsing();wakeAndUnlockDejank();} else {boolean staying = mStatusBar.hideKeyguard();if (!staying) {mStatusBarWindowController.setKeyguardFadingAway(true);// hide() will happen asynchronously and might arrive after the scrims// were already hidden, this means that the transition callback won't// be triggered anymore and StatusBarWindowController will be forever in// the fadingAway state.mStatusBar.updateScrimController();wakeAndUnlockDejank();} else {mStatusBar.finishKeyguardFadingAway();mBiometricUnlockController.finishKeyguardFadingAway();}}updateStates();mStatusBarWindowController.setKeyguardShowing(false);mViewMediatorCallback.keyguardGone();}StatsLog.write(StatsLog.KEYGUARD_STATE_CHANGED,StatsLog.KEYGUARD_STATE_CHANGED__STATE__HIDDEN);}

hideBouncer

    private void hideBouncer(boolean destroyView) {if (mBouncer == null) {return;}/*重点关注*/mBouncer.hide(destroyView);cancelPendingWakeupAction();}

frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java

    public void hide(boolean destroyView) {if (isShowing()) {StatsLog.write(StatsLog.KEYGUARD_BOUNCER_STATE_CHANGED,StatsLog.KEYGUARD_BOUNCER_STATE_CHANGED__STATE__HIDDEN);mDismissCallbackRegistry.notifyDismissCancelled();}mIsScrimmed = false;mFalsingManager.onBouncerHidden();mCallback.onBouncerVisiblityChanged(false /* shown */);cancelShowRunnable();if (mKeyguardView != null) {mKeyguardView.cancelDismissAction();mKeyguardView.cleanUp();}mIsAnimatingAway = false;if (mRoot != null) {mRoot.setVisibility(View.INVISIBLE);if (destroyView) {// We have a ViewFlipper that unregisters a broadcast when being detached, which may// be slow because of AM lock contention during unlocking. We can delay it a bit./*重点关注*/mHandler.postDelayed(mRemoveViewRunnable, 50);}}}

mRemoveViewRunnable

private final Runnable mRemoveViewRunnable = this::removeView;

removeView;

    protected void removeView() {if (mRoot != null && mRoot.getParent() == mContainer) {/*重点关注*/mContainer.removeView(mRoot);mRoot = null;}}

至此锁屏界面移除的逻辑基本clear

Android Q 上的Biometric生物识别之Face人脸识别流程相关推荐

  1. 适配Android Q上读取多媒体文件

    Android Q版本出来也有一段时间了,但是大部分我们都没有去适配过它,首选说一下Android Q版,最大的亮点集中在隐私安全和智能交互两方面,其中在隐私安全方面Android Q增加了外部存储策 ...

  2. 人脸识别相比较其他生物识别技术,人脸识别主要有什么优缺点

    人脸识别:非强制性和谨防刻意伪装 与指纹识别相较来看,人脸识别所使用的数据量更多,从而更加精确.而且与指纹需要接触不同,人脸隔空识别,除了特定事项的认证,不要求验证者的注意力.这也是为什么在明星演唱会 ...

  3. 风口上的人脸识别,动态人脸识别市场迎来爆发期。

    人脸识别已经成为人工智能技术的落地风口之一,随着人工智能和深度学习技术的逐渐成熟,人脸识别的商业化落地开始全面加速,人脸支付.人脸门禁.刷脸考勤.刷脸登录,越来越多的智能场景被解锁. 人脸识别包含有动 ...

  4. 人脸识别(3)---静态人脸识别和动态人脸识别的区别

    静态人脸识别和动态人脸识别的区别 人脸识别,是基于人的脸部特征信息进行身份识别的一种生物识别技术.作为一种新型而且发展较快的技术,很多人对这门技术并没有清晰的理解和认识.比如说,人脸识别有哪些种类,人 ...

  5. 人脸识别接口_人脸识别双模摄像头解析,免费搭配活体检测

    人脸识别,是基于人的脸部特征信息进行身份识别的一种生物识别技术.用摄像机或摄像头采集含有人脸的图像或视频流,并自动在图像中检测和跟踪人脸,进而对检测到的人脸进行脸部识别的一系列相关技术,通常也叫做人像 ...

  6. 人脸识别与膜虹识别_虹膜识别技术和人脸识别技术的区别是什么

    虹膜识别和人脸识别有何不同?虹膜识别技术和人脸识别技术的区别介绍.在生物特征识别技术的分支里,人脸识别和虹膜识别是两个容易混淆的概念,很多用户并不清楚两者的区别和技术特点,常常误以为是同一个概念,本文 ...

  7. 基于神经网络的人脸识别,神经网络实现人脸识别

    深度学习是怎么识别人脸的 深度学习是机器学习研究中的一个新的领域,其动机在于建立.模拟人脑进行分析学习的神经网络,它模仿人脑的机制来解释数据. 卷积神经网络(CNN)局部连接传统的神经网络是全连接,即 ...

  8. 全黑的环境也能人脸识别?红外人脸识别技术助力人脸识别更自由

    人脸识别是近些年来在生物特征识别领域中最常用的一种模态,在公共安全领域得到了广泛应用.同时,人脸识别方式也是多样化发展,如静态人脸识别.动态人脸识别.3D结构光人脸识别等,其实各类人脸识别方式大同小异 ...

  9. 专业的面相识别的企业|人脸识别离线SDK |人证比对API

    基本上国内每家公司都会说自己的算法牛,实际上有几家有人脸核心算法呢?国内在完全从事算法研究的总工程师人数到目前(2016年)总计不到100人,不过也没有现在问题也不大,中科院计算所山世光教授已经开源了 ...

最新文章

  1. 白给的性能不要?cvpr2021-Diverse branch block
  2. 3D Object Detection——BEV-based methods
  3. swift 4 字符串截取
  4. 洛谷 - P3690 【模板】Link Cut Tree (动态树)(LCT模板)
  5. python移位操作困惑
  6. 论文笔记(A Neural Influence Diffusion Model for Social Recommendation)
  7. OpenEIM以前在合作伙伴做的时候
  8. python图像增强_【Tool】Augmentor和imgaug——python图像数据增强库
  9. 【BZOJ-2400】Spoj839Optimal Marks 最小割 + DFS
  10. python老师 招聘_崩了,Python玩大了! 程序员:牛,不可惜!
  11. Nginx学习之如何搭建文件防盗链服务
  12. ArcGIS制图表达Representation实战篇4-自由式制图表达
  13. Sublime Text 模板插件SublimeTmpl
  14. bilibili开源弹幕库UML类图
  15. JAVA 房屋出租系统(韩顺平)
  16. 除了巨沃、富勒WMS,还有什么更好用的仓库管理系统?
  17. 烂到不想考研!大学糟糕宿舍大盘点!
  18. Pyhton零基础投喂(综合练习:2:论⽂作者统计)
  19. 计算机网络ip地址划分方法,ip地址怎么划分 ip地址划分方法【图文】
  20. 老祖宗的老话大全收藏

热门文章

  1. JavaScript混淆安全加固
  2. C++中关于switch的一些理解
  3. csv文件转换为excel文件
  4. science 计算机论文,计算机科学毕业论文
  5. ROS2网络课程资料分享2019.10.26
  6. 移动手持PDA是什么?
  7. java下载excel文件损坏
  8. D2D,让通信更简单!
  9. 阿里云实时数仓搭建与组件选型
  10. FFmpeg的HEVC解码器源代码简单分析:解析器(Parser)部分