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

您所在的位置:网站首页 android自带人脸识别 Android Q 上的Biometric生物识别之Face人脸识别流程

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

2024-07-10 12:32:35| 来源: 网络整理| 查看: 265

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

Android 人脸识别身份验证堆栈是Android 10中的新实现。该实现引入了 IBiometricsFace.hal、IBiometricsFaceClientCallback.hal、和type.hal接口。

要实现Face HIDL,你必须在某个供应商专用库中实现 IBiometricsFace.hal的所有方法 人脸识别架构 BiometricPrompt API包括人脸识别、指纹识别和虹膜识别在内的所有生物识别身份验证方法。Face HAL会与以下组件交互: FaceManager

FaceManager是一个私有接口,用于维护FaceService的之间连接。Keyguard通过该接口访问具有自定义界面的人脸识别身份验证硬件。应用无权访问FaceManager,必须改为使用BiometricPrompt。

FaceService

该框架实现用于管理对人脸识别身份验证硬件的访问权限。它包含基本的注册和身份验证状态机以及各种其他辅助程序(例如枚举程序)。处于稳定性和安全性方面的考虑,不允许在此进程中运行任何供应商代码。所有供应商代码都通过Face 1.0 HIDL接口访问。

faced

这是一个Linux可执行文件,用于实现供FaceService使用的Face 1.0 HIDL 接口。它会将自身注册为 [email protected]以便FaceService能够找到它。

第二部分,人脸模块流程分析

要实现Face HIDL,你必须在某个供应商专用库中实现 IBiometricsFace.hal的所有方法

IBiometricsFace.hal中主要包括以下主要方法:setCallback(); setActiveUser(); revokeChallenge(); enroll(); cancel(); enumerate(); remove(); authenticate(); userActivity; resetLockout(); 其余的四个都是同步方法,应将其阻塞时间缩至最短以免拖延框架。它们分别是generateChallenge(); setFeature(); getFeature; getAuthentitorId()

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

一、人脸录入

人脸录入的入口在Settings中的FaceEnrollEnrolling.java中

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

在此类中的on EnrollmentProgressChange(int steps, int remaining)更新录入进度的方法中用通过传递进来的remaining来获取实际的进度;当remaining = 0 时打开录入结束界面launchFinish(mToken);

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

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

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

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

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

@Override public void onEnrollmentProgressChange(int steps, int remaining) { /*重点关注*/ mParticleCollection.onEnrollmentProgressChange(steps, remaining); }

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

/*重点关注*/ public class ParticleCollection implements BiometricEnrollSidecar.Listener { ...... @Override public void onEnrollmentProgressChange(int steps, int remaining) { if (remaining == 0) { updateState(STATE_COMPLETE); } } }

由此可以看出此类是实现了BiometricEnrollSidecar.Listener从而调用onEnrollmentProgressChange通过传入的remaining值对录入人脸的进度条进行更新的

packages/apps/Settings/src/com/android/settings/biometrics/BiometricEnrollSidecar.java

public abstract class BiometricEnrollSidecar extends InstrumentedFragment { public interface Listener { void onEnrollmentHelp(int helpMsgId, CharSequence helpString); void onEnrollmentError(int errMsgId, CharSequence errString); /*重点关注*/ void onEnrollmentProgressChange(int steps, int remaining); }

onEnrollmentProgressChange

protected void onEnrollmentProgress(int remaining) { if (mEnrollmentSteps == -1) { mEnrollmentSteps = remaining; } mEnrollmentRemaining = remaining; mDone = remaining == 0; if (mListener != null) { /*重点关注*/ mListener.onEnrollmentProgressChange(mEnrollmentSteps, remaining); } else { mQueuedEvents.add(new QueuedEnrollmentProgress(mEnrollmentSteps, remaining)); } }

底层在录制人脸的时候会在FaceManager中调用onEnrollmentProgress方法,并将进度remainiing返回过来,BiometricEnrollSidecar内部写有Listener,在使用Listener的对象将onEnrollmentProgress的值传递进去,使更多实现Listener接口的类可以接收到

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

我们再回到这个类中去看看startEnrollment录入的方法

@Override public void startEnrollment() { /*重点关注*/ super.startEnrollment(); mPreviewFragment = (FaceEnrollPreviewFragment) getSupportFragmentManager() .findFragmentByTag(TAG_FACE_PREVIEW); if (mPreviewFragment == null) { mPreviewFragment = new FaceEnrollPreviewFragment(); getSupportFragmentManager().beginTransaction().add(mPreviewFragment, TAG_FACE_PREVIEW) .commitAllowingStateLoss(); } mPreviewFragment.setListener(mListener); }

此方法中没有明显录入的方法,可见录入方法存在于他的父类中

packages/apps/Settings/src/com/android/settings/biometrics/BiometricsEnrollEnrolling.java

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

由此可知是通过给mSidecar设置setListener监听传入变化而开始录入的

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

@Override protected BiometricEnrollSidecar getSidecar() { final int[] disabledFeatures = new int[mDisabledFeatures.size()]; for (int i = 0; i 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 call public void enroll(final IBinder token, final byte[] cryptoToken, final IFaceServiceReceiver receiver, final String opPackageName, final int[] disabledFeatures) { checkPermission(MANAGE_BIOMETRIC); final boolean restricted = isRestricted(); final EnrollClientImpl client = new EnrollClientImpl(getContext(), mDaemonWrapper, mHalDeviceId, token, new ServiceListenerImpl(receiver), mCurrentUserId, 0 /* groupId */, cryptoToken, restricted, opPackageName, disabledFeatures) { @Override public int[] getAcquireIgnorelist() { return mEnrollIgnoreList; } @Override public int[] getAcquireVendorIgnorelist() { return mEnrollIgnoreListVendor; } @Override public boolean shouldVibrate() { return false; } @Override protected int statsModality() { return FaceService.this.statsModality(); } }; /*重点关注*/ enrollInternal(client, mCurrentUserId); }

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

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 finishes if (newClient != null) { Slog.w(getTag(), "Internal cleanup in progress but trying to start client " + newClient.getClass().getSuperclass().getSimpleName() + "(" + newClient.getOwnerString() + ")" + ", initiatedByClient = " + initiatedByClient); } } else { currentClient.stop(initiatedByClient); // Only post the reset runnable for non-cleanup clients. Cleanup clients should // never be forcibly stopped since they ensure synchronization between HAL and // framework. Thus, we should instead just start the pending client once cleanup // finishes instead of using the reset runnable. mHandler.removeCallbacks(mResetClientState); mHandler.postDelayed(mResetClientState, CANCEL_TIMEOUT_LIMIT); } mPendingClient = newClient; } else if (newClient != null) { // For BiometricPrompt clients, do not start until // 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 immediately mCurrentClient = 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

@Override public int start() { mEnrollmentStartTimeMs = System.currentTimeMillis(); final int timeout = (int) (ENROLLMENT_TIMEOUT_MS / MS_PER_SEC); try { final ArrayList disabledFeatures = new ArrayList(); for (int i = 0; i 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 call public void onEnrollResult(long deviceId, int faceId, int remaining) { /*重点关注*/ mHandler.obtainMessage(MSG_ENROLL_RESULT, remaining, 0, new Face(null, faceId, deviceId)).sendToTarget(); } ...

MSG_ENROLL_RESULT

@Override public void handleMessage(android.os.Message msg) { Trace.beginSection("FaceManager#handleMessage: " + Integer.toString(msg.what)); switch (msg.what) { case MSG_ENROLL_RESULT: /*重点关注*/ sendEnrollResult((Face) msg.obj, msg.arg1 /* remaining */); break; ...

sendEnrollResult

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

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

二、人脸匹配

人脸解锁的入口在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 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 handler if (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 call public 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 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 finishes if (newClient != null) { Slog.w(getTag(), "Internal cleanup in progress but trying to start client " + newClient.getClass().getSuperclass().getSimpleName() + "(" + newClient.getOwnerString() + ")" + ", initiatedByClient = " + initiatedByClient); } } else { currentClient.stop(initiatedByClient); // Only post the reset runnable for non-cleanup clients. Cleanup clients should // never be forcibly stopped since they ensure synchronization between HAL and // framework. Thus, we should instead just start the pending client once cleanup // finishes instead of using the reset runnable. mHandler.removeCallbacks(mResetClientState); mHandler.postDelayed(mResetClientState, CANCEL_TIMEOUT_LIMIT); } mPendingClient = newClient; } else if (newClient != null) { // For BiometricPrompt clients, do not start until // 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 immediately mCurrentClient = 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 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 // BiometricService will add the token to keystore listener.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 listening Slog.w(getLogTag(), "Client not listening"); result = true; } } else { if (listener != null) { vibrateError(); } // Allow system-defined limit of number of attempts before giving up final 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; } @Override public void onAuthenticationSucceededInternal(boolean requireConfirmation, byte[] token) throws RemoteException { if (getWrapperReceiver() != null) { /*重点关注*/ getWrapperReceiver().onAuthenticationSucceeded(requireConfirmation, token); } } @Override public 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 call public 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); } }

AuthenticationCallback是Fm的一个内部回调接口

public abstract static class AuthenticationCallback extends BiometricAuthenticator.AuthenticationCallback { /** * Called when an unrecoverable error has been encountered and the operation is complete. * No further callbacks will be made on this object. * * @param errorCode An integer identifying the error message * @param errString A human-readable error string that can be shown in UI */ public void onAuthenticationError(int errorCode, CharSequence errString) { } /** * Called when a recoverable error has been encountered during authentication. The help * string is provided to give the user guidance for what went wrong, such as * "Sensor dirty, please clean it." * * @param helpCode An integer identifying the error message * @param helpString A human-readable string that can be shown in UI */ public void onAuthenticationHelp(int helpCode, CharSequence helpString) { } /** * Called when a face is recognized. * * @param result An object containing authentication-related data */ /*重点关注*/ public void onAuthenticationSucceeded(AuthenticationResult result) { } /** * Called when a face is detected but not recognized. */ public void onAuthenticationFailed() { } /** * Called when a face image has been acquired, but wasn't processed yet. * * @param acquireInfo one of FACE_ACQUIRED_* constants * @hide */ public void onAuthenticationAcquired(int acquireInfo) { } }

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

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

@VisibleForTesting FaceManager.AuthenticationCallback mFaceAuthenticationCallback = new FaceManager.AuthenticationCallback() { @Override public void onAuthenticationFailed() { handleFaceAuthFailed(); } @Override public void onAuthenticationSucceeded(FaceManager.AuthenticationResult result) { Trace.beginSection("KeyguardUpdateMonitor#onAuthenticationSucceeded"); /*重点关注*/ handleFaceAuthenticated(result.getUserId()); Trace.endSection(); } @Override public void onAuthenticationHelp(int helpMsgId, CharSequence helpString) { handleFaceHelp(helpMsgId, helpString.toString()); } @Override public void onAuthenticationError(int errMsgId, CharSequence errString) { handleFaceError(errMsgId, errString.toString()); } @Override public 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 bouncer if (getUserCanSkipBouncer(userId)) { mTrustManager.unlockedByBiometricForUser(userId, BiometricSourceType.FACE); } // Don't send cancel if authentication succeeds mFaceCancelSignal = null; for (int i = 0; i /*重点关注*/ cb.onBiometricAuthenticated(userId, BiometricSourceType.FACE); } } mHandler.sendMessageDelayed(mHandler.obtainMessage(MSG_BIOMETRIC_AUTHENTICATION_CONTINUE), BIOMETRIC_CONTINUE_DELAY_MS); // Only authenticate face once when assistant is visible mAssistantVisible = 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

@Override public 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 fixed Log.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 lock mExternallyEnabled = 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() { @Override public void run() { mStatusBarWindowController.setKeyguardShowing(false); mStatusBarWindowController.setKeyguardFadingAway(true); hideBouncer(true /* destroyView */); updateStates(); } }, new Runnable() { @Override public 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



【本文地址】

公司简介

联系我们

今日新闻


点击排行

实验室常用的仪器、试剂和
说到实验室常用到的东西,主要就分为仪器、试剂和耗
不用再找了,全球10大实验
01、赛默飞世尔科技(热电)Thermo Fisher Scientif
三代水柜的量产巅峰T-72坦
作者:寞寒最近,西边闹腾挺大,本来小寞以为忙完这
通风柜跟实验室通风系统有
说到通风柜跟实验室通风,不少人都纠结二者到底是不
集消毒杀菌、烘干收纳为一
厨房是家里细菌较多的地方,潮湿的环境、没有完全密
实验室设备之全钢实验台如
全钢实验台是实验室家具中较为重要的家具之一,很多

推荐新闻


图片新闻

实验室药品柜的特性有哪些
实验室药品柜是实验室家具的重要组成部分之一,主要
小学科学实验中有哪些教学
计算机 计算器 一般 打孔器 打气筒 仪器车 显微镜
实验室各种仪器原理动图讲
1.紫外分光光谱UV分析原理:吸收紫外光能量,引起分
高中化学常见仪器及实验装
1、可加热仪器:2、计量仪器:(1)仪器A的名称:量
微生物操作主要设备和器具
今天盘点一下微生物操作主要设备和器具,别嫌我啰嗦
浅谈通风柜使用基本常识
 众所周知,通风柜功能中最主要的就是排气功能。在

专题文章

    CopyRight 2018-2019 实验室设备网 版权所有 win10的实时保护怎么永久关闭