第一部分、Fingerprint HIDL
在配有指纹传感器的设备上,用户可以注册一个或多个指纹,并使用这些指纹来解锁设备以及执行其他任务。Android 会利用 Fingerprint HIDL(硬件接口定义语言)连接到供应商专用库和指纹硬件(例如指纹传感器)。
要实现Fingerprint HIDL,你必须在某个供应商专用库中实现 IBiometricsFingerprint.hal
指纹匹配条件
设备的指纹传感器通常处于闲置状态。不过,为了响应对authenticate 或 enroll 的调用,指纹传感器会监听触摸操作(并且屏幕可能会在用户触摸传感器时被唤醒)。指纹匹配的概要流程包括以下步骤:

1.用户将手指放在传感器上。
2.供应商专用库会根据当前已经注册指纹模板集来判断是否匹配。
3.匹配结果会传递到FingerprintService。

指纹匹配架构
Fingerprint HAL会与以下组件交互 :

· BiometricManager 会在应用进程中与应用直接交互,每个应用都有一个 IBiometricsFringerprint.hal 实例。
· FingerprintService 在系统进程中运行,可处理与 Fingerprint HAL 之间的通信。
· Fingerprint HAL 是 IBiometricsFringerprint HIDL 接口 C/C++实现。它包含可与设备专与硬件进行通信的供应商专用库。
· Keystore API 和 KeyMaster 组件提供由硬件支持的加密功能,以便在安全环境(例如可信执行环境( TEE ))中安全地存储密钥。

第二部分、指纹模块流程分析
要实现Fingerprint HIDL,你必须在某个供应商专用库中实现IBiometricsFingerprint.hal.

IBiometricsFingerprint.hal中主要包含以下主要方法:enroll();preEnroll();getAuthenticatorld();cancel();enumerate();remove();setActiveGroup();authenticate();setNotify();postEnroll()。

指纹模块中的录入,匹配,移除是三个大部分;

一、指纹录入
指纹录入的入口在Settings中的FingerprintEnrollEnrolling.java类中

在这个类中没有看到明显的录入的方法,只有一些UI的加载和一些录入动画的逻辑。

在此类中的updateProgress(boolean animate)更新录入进度的方法中用

int progress = getProgress(mSidecar.getEnrollmentSteps(), mSidecar.getEnrollmentRemaining());

来获取实际的进度;
此类中没有mSidecar的获取方法,但是此类继承自BiometricsEnrolling,我们去他的父类中找找

BiometricsEnrollEnrolling.java

在此类中我们可以看到mSidecar的获取逻辑

     public void startEnrollment() {mSidecar = (BiometricEnrollSidecar) getSupportFragmentManager().findFragmentByTag(TAG_SIDECAR);if (mSidecar == null) {mSidecar = getSidecar();/*重点关注*/getSupportFragmentManager().beginTransaction().add(mSidecar, TAG_SIDECAR).commitAllowingStateLoss();}mSidecar.setListener(this);}

通过BiometricEnrollSider强转类型或是getSider()方法,而getSider()在此类中是一个抽象方法,具体在子类中实现,具体在他的子类中也是返回了一个继承自BiometricEnrollSidecarFingerprintEnrollSider对象

FingerprintEnrollSidecar.java

在此类中我们终于发现了核心代码在该类的startEnrollment()方法中使用mFingerprintManager.enroll(mToken,mEnrollmentCancel,0,mUserId,mEnrollmentCallback);来监听录入过程中的onEnrollmentProgress,onEnrollmentHelp,onEnrollmentError的录入状态

而FingerprintEnrollSidecar的父类是BiometricEnrollSidecar,该类是继承自InstrumentedFragment,所以说明该类为一个Fragment,该类的生命周期也遵循Fragment;

在BiometricEnrollSidecar的onStart方法中有执行startEnrollment()
frameworks/base/core/java/android/hardware/fingerprint/FingerprintManager.java

从调用FingerprintManager.enroll方法开始录入的

  public void enroll(byte [] token, CancellationSignal cancel, int flags,int userId, EnrollmentCallback callback) {if (userId == UserHandle.USER_CURRENT) {userId = getCurrentUserId();}if (callback == null) {throw new IllegalArgumentException("Must supply an enrollment callback");}if (cancel != null) {if (cancel.isCanceled()) {Slog.w(TAG, "enrollment already canceled");return;} else {cancel.setOnCancelListener(new OnEnrollCancelListener());}}if (mService != null) try {mEnrollmentCallback = callback;/*重点关注*/mService.enroll(mToken, token, userId, mServiceReceiver, flags,mContext.getOpPackageName());} catch (RemoteException e) {Slog.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(FINGERPRINT_ERROR_HW_UNAVAILABLE,getErrorString(mContext, FINGERPRINT_ERROR_HW_UNAVAILABLE,0 /* vendorCode */));}}}

FingerprintManagerenroll方法可以看出主要是通过调用mService.enroll开始进行录入的,而这个mService也就是传进来的

 FIngerprintManager(Context context, IFingerprintService){...mService = service;    ...}

的实现类的参数。

frameworks /base /services/ core/ java com/ android /server /biometrics/ fingerprint /FingerprintService.java

FingerprintService中注册了IFingerprintService
private final class FingerprintServiceWrapper extends IFingerPrintService.Stub实现了enroll方法

         public void enroll(final IBinder token, final byte[] cryptoToken, final int userId,final IFingerprintServiceReceiver receiver, final int flags,final String opPackageName) {checkPermission(MANAGE_FINGERPRINT);final boolean restricted = isRestricted();final int groupId = userId; // default group for fingerprint enrollmentfinal EnrollClientImpl client = new EnrollClientImpl(getContext(), mDaemonWrapper,mHalDeviceId, token, new ServiceListenerImpl(receiver), mCurrentUserId, groupId,cryptoToken, restricted, opPackageName, new int[0] /* disabledFeatures */) {@Overridepublic boolean shouldVibrate() {return true;}@Overrideprotected int statsModality() {return FingerprintService.this.statsModality();}};/*重点关注*/enrollInternal(client, userId);}

在FingerprintService的父类BiometricServiceBase中

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 方法会调用fingerprintd,调用底层的指纹库,底层库返回结果后会调用onEnrollResult来反馈结果receiver,再往上层反馈。这就是指纹的录制流程。在onEnrollResult中当remaining等于0的时候完成录制,调用addBiometricForUser。

FingerprintManager.java中注册了IFingerprintServiceReceiver,实现onEnrollResult方法发送 MSG_ENROLL_RESULT
frameworks/base/core/java/android/hardware/fingerprint/FingerprintManager.java

   private IFingerprintServiceReceiver mServiceReceiver = new IFingerprintServiceReceiver.Stub() {@Override // binder callpublic void onEnrollResult(long deviceId, int fingerId, int groupId, int remaining) {/*重点关注*/mHandler.obtainMessage(MSG_ENROLL_RESULT, remaining, 0,new Fingerprint(null, groupId, fingerId, deviceId)).sendToTarget();}.......};}
 @Overridepublic void handleMessage(android.os.Message msg) {switch (msg.what) {case MSG_ENROLL_RESULT:/*重点关注*/sendEnrollResult((Fingerprint) msg.obj, msg.arg1 /* remaining */);break;.......}}};

调用回调方法

 private void sendEnrollResult(Fingerprint fp, int remaining) {if (mEnrollmentCallback != null) {/*重点关注*/mEnrollmentCallback.onEnrollmentProgress(remaining);}}

再看看Settings
在FingerprintEnrollSidecar中通过实现录入的回调接口FingerprintManager.EnrollmentCallback来监听录入过程中onEnrollmentProgress, onEnrollmentHelp, onEnrollmentError的录入状态
并在他的父类BiometricEnrollSidercar写有一个内部接口Listener
packages/apps/Settings/src/com/android/settings/biometrics/BiometricEnrollSidecar.java

 public interface Listener {void onEnrollmentHelp(int helpMsgId, CharSequence helpString);void onEnrollmentError(int errMsgId, CharSequence errString);void onEnrollmentProgressChange(int steps, int remaining);}

BiometricsEnrollEnrolling 实现了 FingerprintEnrollSidecar.Listener,从而实现了 CallBack

二、指纹匹配
指纹解锁的入口在Keyguard中

系统灭屏之后会调用PhoneWindowManager的startedGoingToSleep方法,继而调用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();}

updateFingerprintListeningState()

private void updateFingerprintListeningState() {// 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(mRetryFingerprintAuthentication);boolean shouldListenForFingerprint = shouldListenForFingerprint();boolean runningOrRestarting = mFingerprintRunningState == BIOMETRIC_STATE_RUNNING|| mFingerprintRunningState == BIOMETRIC_STATE_CANCELLING_RESTARTING;if (runningOrRestarting && !shouldListenForFingerprint) {stopListeningForFingerprint();} else if (!runningOrRestarting && shouldListenForFingerprint) {/*重点关注*/startListeningForFingerprint();}}

startListeningForFingerprint();

private void startListeningForFingerprint() {if (mFingerprintRunningState == BIOMETRIC_STATE_CANCELLING) {setFingerprintRunningState(BIOMETRIC_STATE_CANCELLING_RESTARTING);return;}if (mFingerprintRunningState == BIOMETRIC_STATE_CANCELLING_RESTARTING) {// Waiting for restart via handleFingerprintError().return;}if (DEBUG) Log.v(TAG, "startListeningForFingerprint()");int userId = getCurrentUser();if (isUnlockWithFingerprintPossible(userId)) {if (mFingerprintCancelSignal != null) {mFingerprintCancelSignal.cancel();}mFingerprintCancelSignal = new CancellationSignal();/*重点关注*/mFpm.authenticate(null, mFingerprintCancelSignal, 0, mFingerprintAuthenticationCallback,null, userId);setFingerprintRunningState(BIOMETRIC_STATE_RUNNING);}}

frameworks/base/core/java/android/hardware/fingerprint/FingerprintManager.java

    @RequiresPermission(anyOf = {USE_BIOMETRIC, USE_FINGERPRINT})public void authenticate(@Nullable CryptoObject crypto, @Nullable CancellationSignal cancel,int flags, @NonNull AuthenticationCallback callback, Handler handler, int userId) {if (callback == null) {throw new IllegalArgumentException("Must supply an authentication callback");}if (cancel != null) {if (cancel.isCanceled()) {Slog.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;/*重点关注*///这里又是调用FingerprintService的authenticate方法mService.authenticate(mToken, sessionId, userId, mServiceReceiver, flags,mContext.getOpPackageName());} catch (RemoteException e) {Slog.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(FINGERPRINT_ERROR_HW_UNAVAILABLE,getErrorString(mContext, FINGERPRINT_ERROR_HW_UNAVAILABLE,0 /* vendorCode */));}}}

frameworks/base/services/core/java/com/android/server/biometrics/fingerprint/FingerprintService.java

       @Override // Binder callpublic void authenticate(final IBinder token, final long opId, final int groupId,final IFingerprintServiceReceiver receiver, final int flags,final String opPackageName) {updateActiveGroup(groupId, opPackageName);final boolean restricted = isRestricted();final AuthenticationClientImpl client = new FingerprintAuthClient(getContext(),mDaemonWrapper, mHalDeviceId, token, new ServiceListenerImpl(receiver),mCurrentUserId, 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, 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方法会调用fingerprintd,调用底层的指纹库,底层库返回结果后会调用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/fingerprint/FingerprintManager.java

 private IFingerprintServiceReceiver mServiceReceiver = new IFingerprintServiceReceiver.Stub() {@Override // binder callpublic void onEnrollResult(long deviceId, int fingerId, int groupId, int remaining) {mHandler.obtainMessage(MSG_ENROLL_RESULT, remaining, 0,new Fingerprint(null, groupId, fingerId, deviceId)).sendToTarget();}@Override // binder callpublic void onAcquired(long deviceId, int acquireInfo, int vendorCode) {mHandler.obtainMessage(MSG_ACQUIRED, acquireInfo, vendorCode,deviceId).sendToTarget();}@Override // binder call/*重点关注*/public void onAuthenticationSucceeded(long deviceId, Fingerprint fp, int userId) {mHandler.obtainMessage(MSG_AUTHENTICATION_SUCCEEDED, userId, 0, fp).sendToTarget();}@Override // binder callpublic void onAuthenticationFailed(long deviceId) {mHandler.obtainMessage(MSG_AUTHENTICATION_FAILED).sendToTarget();}@Override // binder callpublic void onError(long deviceId, int error, int vendorCode) {mHandler.obtainMessage(MSG_ERROR, error, vendorCode, deviceId).sendToTarget();}@Override // binder callpublic void onRemoved(long deviceId, int fingerId, int groupId, int remaining) {mHandler.obtainMessage(MSG_REMOVED, remaining, 0,new Fingerprint(null, groupId, fingerId, deviceId)).sendToTarget();}@Override // binder callpublic void onEnumerated(long deviceId, int fingerId, int groupId, int remaining) {// TODO: propagate remainingmHandler.obtainMessage(MSG_ENUMERATED, fingerId, groupId, deviceId).sendToTarget();}};}

MSG_AUTHENTICATION_SUCCEEDED

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

sendAuthenticatedSucceeded((Fingerprint) msg.obj, msg.arg1 /* userId */);

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

AuthenticationCallbackFpm的一个内部回调接口

public static abstract 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*/@Overridepublic 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*/@Overridepublic void onAuthenticationHelp(int helpCode, CharSequence helpString) { }/*** Called when a fingerprint is recognized.* @param result An object containing authentication-related data*//*重点关注*/public void onAuthenticationSucceeded(AuthenticationResult result) { }/*** Called when a fingerprint is valid but not recognized.*/@Overridepublic void onAuthenticationFailed() { }/*** Called when a fingerprint image has been acquired, but wasn't processed yet.** @param acquireInfo one of FINGERPRINT_ACQUIRED_* constants* @hide*/@Overridepublic void onAuthenticationAcquired(int acquireInfo) {}};

AuthenticationCallback接口在KeyguardUpdateMonitor.java中实现,用于监听FingerprintService中指纹的解锁状态

    private FingerprintManager.AuthenticationCallback mFingerprintAuthenticationCallback= new AuthenticationCallback() {@Overridepublic void onAuthenticationFailed() {handleFingerprintAuthFailed();}@Overridepublic void onAuthenticationSucceeded(AuthenticationResult result) {Trace.beginSection("KeyguardUpdateMonitor#onAuthenticationSucceeded");/*重点关注*/handleFingerprintAuthenticated(result.getUserId());Trace.endSection();}@Overridepublic void onAuthenticationHelp(int helpMsgId, CharSequence helpString) {handleFingerprintHelp(helpMsgId, helpString.toString());}@Overridepublic void onAuthenticationError(int errMsgId, CharSequence errString) {handleFingerprintError(errMsgId, errString.toString());}@Overridepublic void onAuthenticationAcquired(int acquireInfo) {handleFingerprintAcquired(acquireInfo);}};

handleFingerprintAuthenticated(result.getUserId());

   private void handleFingerprintAuthenticated(int authUserId) {Trace.beginSection("KeyGuardUpdateMonitor#handlerFingerPrintAuthenticated");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, "Fingerprint authenticated for wrong user: " + authUserId);return;}if (isFingerprintDisabled(userId)) {Log.d(TAG, "Fingerprint disabled by DPM for userId: " + userId);return;}/*重点关注*/onFingerprintAuthenticated(userId);} finally {setFingerprintRunningState(BIOMETRIC_STATE_STOPPED);}Trace.endSection();}

onFingerprintAuthenticated(userId);

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

可以看到在onFingerprintAuthenticated()方法中调用了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生物识别之Fingerprint指纹识别流程相关推荐

  1. Android Q 上的Biometric生物识别之Face人脸识别流程

    第一部分,人脸识别身份验证HIDL 借助人脸识别身份验证功能,用户只需要将自己的面孔对准设备即可将其解锁.Android 10 增加了对一种新的人脸识别身份验证堆栈的支持,这种堆栈可安全处理摄像头帧, ...

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

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

  3. 苹果高通携手推人脸识别!手机指纹识别将被打入冷宫

    本文讲的是 苹果高通携手推人脸识别!手机指纹识别将被打入冷宫, 现在的智能手机中都会标配一个功能,那就是指纹识别,小到千元机大到旗舰,这个功能早已被大家所习惯,手指轻轻一触碰,完全省去了输入密码的繁琐 ...

  4. 什么是浏览器指纹识别?浏览器指纹识别如何预防

    当我们访问网站时,网站会使用一些特殊脚本收集我们的信息(如浏览器版本,时区,默认语言等),这就是浏览器指纹识别.浏览器指纹识别可以帮助网站准确地识别出用户.本文将简单介绍一下浏览器指纹识别的工作原理以 ...

  5. 视频图像处理平台对比_情绪管理考勤机人脸识别原理与指纹识别性能对比-微幼科技...

    随着AI人工智能技术发展越来越成熟,生物识别技术也越来越成熟.包括指纹识别.眼纹识别.虹膜识别.视网膜识别.声纹识别和人脸识别等,这些识别方式一般用于确认身份信息.考勤和支付等.那么,情绪管理考勤机人 ...

  6. 渗透测试 | 几款常用的CMS识别「Web指纹识别」扫描脚本工具(含下载地址)

    在对「靶标资产」进行渗透测试的前期,通常需要对「靶标资产」进行相关的信息收集,而对「靶标资产」进行Web指纹信息扫描也是信息收集当中很关键的一部分. 能否有效识别出「靶标资产」的Web指纹信息,主要还 ...

  7. 射频指纹识别(RF指纹识别)

    射频指纹识别(RF指纹识别)是什么? 射频指纹识别是一种识别设备或信号器的过程,通过查看其传输的属性(包括特定的无线电频率)来发起无线电传输.每个信号发起者基于其发送信号的位置和配置具有其自己的特定& ...

  8. android 切换字体崩溃,androidx - 在Android 10 / Android Q上使用捆绑的ttf字体时崩溃 - 堆栈内存溢出...

    将我的Android应用的目标级别从28更新为29(Android 10)后,该应用在Pixel 3(使用Android 10)上崩溃了. 使用的版本 Android Gradle插件3.5.0 摇篮 ...

  9. 微信小程序 SOTER 生物认证DEMO,指纹识别

    今天项目尝试使用微信小程序指纹认证功能 以下为测试demo index.js Page({/*** 页面的初始数据*/data: {isfingerPrint : false, //可否使用指纹识别 ...

  10. python实现指纹识别毕业论文_指纹识别系统大学本科毕业论文

    I 指纹识别系统 摘要 指纹的唯一性和不变性决定了它在身份认证中的重要地位. 它是模式识 别领域中使用最早,也是应用最广的生物鉴定技术.随着低价位指纹采集仪 的出现和高可靠性算法的实现, 自动指纹识别 ...

最新文章

  1. 地图区域划分转换成数学模型解决问题
  2. 战神背光键盘如何关系_?复工了?换个键盘先!0-2000元键盘推荐
  3. nginx配置文件讲解(二)
  4. 上海公布公交卡成本 押金拟从30元调至20元
  5. 160个Crackme018
  6. Android测试中被测应用挂了怎么办?
  7. 反射获取有参数的成员方法并运行
  8. JSF JQUERY 使用datepicker
  9. 系统测试:单元测试相关知识笔记
  10. 目标检测第7步:如何在Windows 10下,配置Pycharm中的YOLOv5(5.0)虚拟环境?
  11. EntityFramework进阶——数据变更冲突
  12. pg数据库有雷锋?用户已有权限为何无故消失?
  13. java操作redis并发_使用Redis incr解决并发问题的操作
  14. springboot整合redis实现分布式锁思想
  15. k8s环境问题及解决方案
  16. python下载特别慢-Python3 用 urllib 下载图片非常慢,会是什么原因呢?
  17. Atitit uuid 的概念与实现与最佳实践总结 目录 1. 概念与组成 1 1.1. 在空间(Space)与时间(Time)上的唯一性 1 1.2. UUID 1 1.3. 组成 2 1.3.1
  18. 黑马程序员Git教程笔记
  19. 量化感知训练_《量化健身 动作精讲》:专业解读健美身材的秘密
  20. vue部署后饿了么组件的图片不见了

热门文章

  1. DNS域名详细解析过程(最全面,看这一篇就够)
  2. T检验、F检验、Z检验、卡方检验
  3. 后台管理系统 - 页面布局设计
  4. 函数求和代码 matlab,一个简单求和函数的matlab实现(带程序耗时功能)
  5. 段式液晶结构你了解多少?
  6. 十五、移动端vw+rem等比缩放布局开发的详细步骤:包含px与rem的单位换算、二倍图以及如何使用UI给的设计稿等(开发工具HBuilder)
  7. 【网络】路由器和交换机区别,什么是网关
  8. 【整理】基因编辑最新进展
  9. 天玑9200领跑背后,高端芯片掀起蝴蝶效应
  10. TCO14, I bought a watch last year