Android人脸解锁源码解析

news/2024/9/14 2:06:40/ 标签: android

1 人脸解锁相关类介绍

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

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

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

下面我们从应用到底层依次来看。

2 人脸录入

人脸录入的入口在Settings中,其重要负责人脸录入一些UI的加载和一些录入动画的逻辑。

2.1 人脸录入

先来看录入的入口类FaceEnrollEnrolling

public class FaceEnrollEnrolling extends BiometricsEnrollEnrolling- public abstract class BiometricsEnrollEnrolling extends BiometricEnrollBase- public abstract class BiometricEnrollBase extends InstrumentedActivity- public abstract class InstrumentedActivity extends ObservableActivity - public class ObservableActivity extends FragmentActivity- androidx.fragment.app.FragmentActivity;

从继承关系可以看到,

是一个activity。

@Override
protected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.face_enroll_enrolling);setHeaderText(R.string.security_settings_face_enroll_repeat_title);mErrorText = findViewById(R.id.error_text);mLinearOutSlowInInterpolator = AnimationUtils.loadInterpolator(this, android.R.interpolator.linear_out_slow_in);mFooterBarMixin = getLayout().getMixin(FooterBarMixin.class);mFooterBarMixin.setSecondaryButton(new FooterButton.Builder(this).setText(R.string.security_settings_face_enroll_enrolling_skip).setListener(this::onSkipButtonClick).setButtonType(FooterButton.ButtonType.SKIP).setTheme(R.style.SudGlifButton_Secondary).build());....startEnrollment();
}

先来看布局文件face_enroll_enrolling.xml

<com.google.android.setupdesign.GlifLayout                                       xmlns:android="http://schemas.android.com/apk/res/android"android:id="@+id/setup_wizard_layout"android:layout_width="match_parent"android:layout_height="match_parent"style="?attr/face_layout_theme"><LinearLayoutstyle="@style/SudContentFrame"android:layout_width="match_parent"android:layout_height="match_parent"android:orientation="vertical"android:clipToPadding="false"android:clipChildren="false"><LinearLayoutandroid:layout_width="match_parent"android:layout_height="0dp"android:layout_weight="1"android:gravity="center"android:orientation="vertical"><com.android.settings.biometrics.face.FaceSquareFrameLayout               android:layout_width="match_parent"                                                   android:layout_height="match_parent"android:layout_weight="1">                                           <com.android.settings.biometrics.face.FaceSquareTextureView           					  android:id="@+id/texture_view"android:layout_width="wrap_content"android:layout_height="wrap_content"android:contentDescription="@null" /><ImageViewandroid:id="@+id/circle_view"android:layout_width="match_parent"android:layout_height="match_parent" /></com.android.settings.biometrics.face.FaceSquareFrameLayout><TextViewstyle="@style/TextAppearance.ErrorText"android:id="@+id/error_text"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_gravity="center_horizontal|bottom"android:accessibilityLiveRegion="polite"android:gravity="center"android:visibility="invisible"/></LinearLayout></LinearLayout>
</com.google.android.setupdesign.GlifLayout>

它的布局问题主要包含了:id/texture_view:相机预览图,id/circle_view: 进度动画以及错误提示语。

我们再回到这个类中去看看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);
}private ParticleCollection.Listener mListener = new ParticleCollection.Listener() {@Overridepublic void onEnrolled() {FaceEnrollEnrolling.this.launchFinish(mToken);}
};

startEnrollment里面创建了一个FaceEnrollPreviewFragment,然后设置了人脸录入完成的监听。此方法中没有明显录入的方法,可见录入方法存在于他的父类BiometricsEnrollEnrolling中。

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

这里创建了一个BiometricEnrollSidecar,通过给mSidecar设置setListener监听传入变化而开始录入的。

/*** @return an instance of the biometric enroll sidecar*/
protected abstract BiometricEnrollSidecar getSidecar();

我们这里是人脸解锁,所以BiometricEnrollSidecar就是FaceEnrollSidecar

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

@Override
public void startEnrollment() {super.startEnrollment();mFaceManager.enroll(mUserId, mToken, mEnrollmentCancel,mEnrollmentCallback, mDisabledFeatures);
}

这样就调到了frameworks/base/core/java/android/hardware/face/FaceManager.java的enroll来录入了,到这里就正式进入系统流程了。

/*** Request face authentication enrollment. * This call operates the face authentication hardware* and starts capturing images. Progress will be indicated by callbacks to the* {@link EnrollmentCallback} object. It terminates when* {@link EnrollmentCallback#onEnrollmentError(int, CharSequence)} or* {@link EnrollmentCallback#onEnrollmentProgress(int) is called with remaining == 0,* at which point the object is no longer valid. * The operation can be canceled by using the* provided cancel object.** @param token a unique token provided by a recent creation *        or verification of device credentials (e.g. pin, pattern or password).* @param cancel   an object that can be used to cancel enrollment* @param flags    optional flags* @param userId   the user to whom this face will belong to* @param callback an object to receive enrollment events* @hide*/@RequiresPermission(MANAGE_BIOMETRIC)
public void enroll(int userId, 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(userId, 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();}}
}

这里直接调用的FaceService#enroll,继续往下看。

/*** Receives the incoming binder calls from FaceManager.*/
private final class FaceServiceWrapper extends IFaceService.Stub {@Override // Binder callpublic void enroll(int userId, final IBinder token, final byte[] cryptoToken,final IFaceServiceReceiver receiver,final String opPackageName, final int[] disabledFeatures) {checkPermission(MANAGE_BIOMETRIC);updateActiveGroup(userId, opPackageName);mHandler.post(() -> {mNotificationManager.cancelAsUser(NOTIFICATION_TAG, NOTIFICATION_ID, UserHandle.CURRENT);});final boolean restricted = isRestricted();final EnrollClientImpl client = new EnrollClientImpl(getContext(), mDaemonWrapper,mHalDeviceId, token, new ServiceListenerImpl(receiver), mCurrentUserId, 0 /* groupId */, cryptoToken, restricted, opPackageName, disabledFeatures, ENROLL_TIMEOUT_SEC) {@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);}
}

这里先检查有没有MANAGE_BIOMETRIC的权限,然后向IBiometricsFace设置setActiveUser,这里重点是enrollInternal。

/*** Calls from the Manager. These are still on the calling binder's thread.*/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 */);});
}

enrollInternal首先检查人脸录入的数量是否达到限制。

@Override
protected boolean hasReachedEnrollmentLimit(int userId) {final int limit = getContext().getResources().getInteger(com.android.internal.R.integer.config_faceMaxTemplatesPerUser);final int enrolled = FaceService.this.getEnrolledTemplates(userId).size();if (enrolled >= limit) {Slog.w(TAG, "Too many faces registered, user: " + userId);return true;}return false;
}

对比的方法是将res配置的数量和已经录入的进行对比。

<!-- Limit for the number of face templates per user -->
<integer name="config_faceMaxTemplatesPerUser">1</integer>

从res可以看到,人脸最大配置个数为1,也就是最大只能录入一个人脸数据。what?不给女朋友机会还是保护女朋友隐私?

最后从binder线程,切换到主线程中执行startClient。

/*** Calls the HAL to switch states to the new task. If there's already a current task,* it calls cancel() and sets mPendingClient to begin when the current task finishes* ({@link BiometricConstants#BIOMETRIC_ERROR_CANCELED}).** @param newClient the new client that wants to connect* @param initiatedByClient true for authenticate, remove and enroll*/@VisibleForTestingvoid 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());}}

这里如果已经有task,就先cancel调当前的,然后将newClient设置到mPendingClient,并设置一个3s的延迟消息来延迟执行mPendingClient,最后执行startCurrentClient。

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() + ")"+ " targetUserId: " + mCurrentClient.getTargetUserId()+ " currentUserId: " + mCurrentUserId+ " cookie: " + cookie + "/" + mCurrentClient.getCookie());if (cookie != mCurrentClient.getCookie()) {Slog.e(getTag(), "Mismatched cookie");return;}int status = mCurrentClient.start();if (status == 0) {notifyClientActiveCallbacks(true);} else {mCurrentClient.onError(getHalDeviceId(), BIOMETRIC_ERROR_HW_UNAVAILABLE,0 /* vendorCode */);removeClient(mCurrentClient);}
}

还是把mCurrentClient的对象传进去了。然后是 mCurrentClient.start()。

//frameworks/base/services/core/java/com/android/server/biometrics/EnrollClient.java
@Override
public int start() {mEnrollmentStartTimeMs = System.currentTimeMillis();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(), mTimeoutSec,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
}

EnrollClient#start方法会通过getDaemonWrapper().enroll调用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();}
}private class MyHandler extends Handler {@Overridepublic void handleMessage(android.os.Message msg) {switch (msg.what) {case MSG_ENROLL_RESULT:sendEnrollResult((Face) msg.obj, msg.arg1 /* remaining */);break;}}
}private void sendEnrollResult(Face face, int remaining) {if (mEnrollmentCallback != null) {mEnrollmentCallback.onEnrollmentProgress(remaining);}
}

2.2 录入进度

​ 在前面FaceEnrollEnrolling类中的onEnrollmentProgressChange(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 animationshowError("Steps: " + steps + " Remaining: " + remaining);// TODO: Have this match any animations that UX comes up withif (remaining == 0) {launchFinish(mToken);}

这里调用FaceEnrollPreviewFragment#onEnrollmentProgressChange

 //packages/apps/Settings/src/com/android/settings/biometrics/face/FaceEnrollPreviewFragment.java
@Override
public void onEnrollmentProgressChange(int steps, int remaining) {mAnimationDrawable.onEnrollmentProgressChange(steps, remaining);
}

这里继续调用FaceEnrollAnimationDrawable#onEnrollmentProgressChange

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

继续

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

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

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);}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接口的类可以接收到。

3 人脸解锁

3.1 人脸监听

在灭屏之后,PowerManagerService的Notifier线程会调用PhoneWindowManager#startedGoingToSleep通知startedGoingToSleep。

 // Called on the PowerManager's Notifier thread.
@Override
public void startedGoingToSleep(int why) {if (DEBUG_WAKEUP) {Slog.i(TAG, "Started going to sleep... (why="+ WindowManagerPolicyConstants.offReasonToString(why) + ")");}mGoingToSleep = true;mRequestedOrGoingToSleep = true;if (mKeyguardDelegate != null) {mKeyguardDelegate.onStartedGoingToSleep(why);}
}

调用KeyguardDelegate.onStartedGoingToSleep方法,继而又会调用KeyguardServiceWrapper.java#onStartedGoingToSleep方法,再调用KeyguardService#onStartedGoingToSleep方法;并最终到达了KeyguardViewMediator#onStartedGoingToSleep

/*** Called to let us know the screen was turned off.* @param why either {@link WindowManagerPolicyConstants#OFF_BECAUSE_OF_USER} or*   {@link WindowManagerPolicyConstants#OFF_BECAUSE_OF_TIMEOUT}.*/
public void onStartedGoingToSleep(int why) {if (DEBUG) Log.d(TAG, "onStartedGoingToSleep(" + why + ")");...mUpdateMonitor.dispatchStartedGoingToSleep(why);notifyStartedGoingToSleep();
}

然后到KeyguardUpdateMonitor#dispatchStartedGoingToSleep

public void dispatchStartedGoingToSleep(int why) {mHandler.sendMessage(mHandler.obtainMessage(MSG_STARTED_GOING_TO_SLEEP, why, 0));
}protected void handleStartedGoingToSleep(int arg1) {Assert.isMainThread();mLockIconPressed = false;clearBiometricRecognized();for (int i = 0; i < mCallbacks.size(); i++) {KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get();if (cb != null) {cb.onStartedGoingToSleep(arg1);}}mGoingToSleep = true;updateBiometricListeningState();
}private void updateBiometricListeningState() {updateFingerprintListeningState();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();}
}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);}
}

这里先创建了一个CancellationSignal对象,这个类主要是给用户提供了一个cancel的接口来取消或者监听取消操作。

然后调用FaceManager#authenticate来开始人脸解锁监听请求。

/*** Request authentication of a crypto object. * This call operates the face recognition hardware* and starts capturing images. It terminates when* {@link AuthenticationCallback#onAuthenticationError(int, CharSequence)} or* {@link AuthenticationCallback#onAuthenticationSucceeded(AuthenticationResult)} * is called, at which point the object is no longer valid. * The operation can be canceled by using the* provided cancel object.** @param crypto   object associated with the call or null if none required.* @param cancel   an object that can be used to cancel authentication* @param flags    optional flags; should be 0* @param callback an object to receive authentication events* @param handler  an optional handler to handle callback events* @param userId   userId to authenticate for* @throws IllegalArgumentException if the crypto operation is not supported * or is not backed by Android Keystore facility* @throws IllegalStateException    if the crypto primitive is not initialized.* @hide*/
public void authenticate(@Nullable CryptoObject crypto, @Nullable CancellationSignal cancel,int flags, @NonNull AuthenticationCallback callback,@Nullable Handler handler, int userId) {if (callback == null) {throw new IllegalArgumentException("Must supply an authentication callback");}if (cancel != null) {if (cancel.isCanceled()) {Log.w(TAG, "authentication already canceled");return;} else {cancel.setOnCancelListener(new OnAuthenticationCancelListener(crypto));}}if (mService != null) {try {useHandler(handler);mAuthenticationCallback = callback;mCryptoObject = crypto;long sessionId = crypto != null ? crypto.getOpId() : 0;Trace.beginSection("FaceManager#authenticate");mService.authenticate(mToken, sessionId, userId, mServiceReceiver,flags, mContext.getOpPackageName());} catch (RemoteException e) {Log.w(TAG, "Remote exception while authenticating: ", e);if (callback != null) {// Though this may not be a hardware issue, it will cause apps to give up or// try again later.callback.onAuthenticationError(FACE_ERROR_HW_UNAVAILABLE,getErrorString(mContext, FACE_ERROR_HW_UNAVAILABLE,0 /* vendorCode */));}} finally {Trace.endSection();}}
}

然后调用FaceService#authenticate

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

继续到BiometricServiceBase#authenticateInternal

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

首先判断是否可以进行人脸解锁

/*** Helper methods.*//*** @param opPackageName name of package for caller* @param requireForeground only allow this call while app is in the foreground* @return true if caller can use the biometric API*/
protected boolean canUseBiometric(String opPackageName, boolean requireForeground,int uid, int pid, int userId) {checkUseBiometricPermission();if (Binder.getCallingUid() == Process.SYSTEM_UID) {return true; // System process (BiometricService, etc) is always allowed}if (isKeyguard(opPackageName)) {return true; // Keyguard is always allowed}if (!isCurrentUserOrProfile(userId)) {Slog.w(getTag(), "Rejecting " + opPackageName + "; not a current user or profile");return false;}if (!checkAppOps(uid, opPackageName)) {Slog.w(getTag(), "Rejecting " + opPackageName + "; permission denied");return false;}if (requireForeground && !(Utils.isForeground(uid, pid) || isCurrentClient(opPackageName))) {Slog.w(getTag(), "Rejecting " + opPackageName + "; not in foreground");return false;}return true;
}

可以看到,能进行人脸解锁的条件:系统用户、在锁屏界面、当前用户匹配、有OP_USE_BIOMETRIC权限、处于前台。

然后调用startAuthentication。

// Should be done on a handler thread - not on the Binder's thread.
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 */);
}

继续走startClient

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());}
}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() + ")"+ " targetUserId: " + mCurrentClient.getTargetUserId()+ " currentUserId: " + mCurrentUserId+ " cookie: " + cookie + "/" + mCurrentClient.getCookie());if (cookie != mCurrentClient.getCookie()) {Slog.e(getTag(), "Mismatched cookie");return;}int status = mCurrentClient.start();if (status == 0) {notifyClientActiveCallbacks(true);} else {mCurrentClient.onError(getHalDeviceId(), BIOMETRIC_ERROR_HW_UNAVAILABLE,0 /* vendorCode */);removeClient(mCurrentClient);}
}

上面这些流程,在人脸录入的时候都走过,这里区别是在mCurrentClient.start()开始的,也就是ClientMonitor的子类不一样。人脸录入的时候走的是EnrollClient#start(),而这里是AuthenticationClient#start()。这点需要注意。

进入到AuthenticationClient#start

/*** Start authentication*/
@Override
public int start() {mStarted = true;onStart();try {mStartTimeMs = System.currentTimeMillis();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
}

同样的,这里调到了getDaemonWrapper().authenticate(mOpId, getGroupId());

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

不过从Android T开始,这块的实现由区别了。

/*** Start authentication*/
@Override
public void start(@NonNull ClientMonitorCallback callback) {super.start(callback);final @LockoutTracker.LockoutMode int lockoutMode;if (mShouldUseLockoutTracker) {lockoutMode = mLockoutTracker.getLockoutModeForUser(getTargetUserId());} else {lockoutMode = getBiometricContext().getAuthSessionCoordinator().getLockoutStateFor(getTargetUserId(), mSensorStrength);}if (lockoutMode != LockoutTracker.LOCKOUT_NONE) {Slog.v(TAG, "In lockout mode(" + lockoutMode + ") ; disallowing authentication");int errorCode = lockoutMode == LockoutTracker.LOCKOUT_TIMED? BiometricConstants.BIOMETRIC_ERROR_LOCKOUT: BiometricConstants.BIOMETRIC_ERROR_LOCKOUT_PERMANENT;onError(errorCode, 0 /* vendorCode */);return;}if (mTaskStackListener != null) {mActivityTaskManager.registerTaskStackListener(mTaskStackListener);}Slog.d(TAG, "Requesting auth for " + getOwnerString());mStartTimeMs = System.currentTimeMillis();mAuthAttempted = true;startHalOperation();
}

3.2 人脸认证

底层库返回结果后会调用onAuthenticated来反馈结果给receiver,在往上层反馈。

我们从AuthenticationClient#onAuthenticated开始分析。

@Override
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());// Ensure authentication only succeeds if the client activity is on top or is keyguard.boolean isBackgroundAuth = false;if (authenticated && !Utils.isKeyguard(getContext(), getOwnerString())) {try {final List<ActivityManager.RunningTaskInfo> tasks =ActivityTaskManager.getService().getTasks(1);if (tasks == null || tasks.isEmpty()) {Slog.e(TAG, "No running tasks reported");isBackgroundAuth = true;} else {final ComponentName topActivity = tasks.get(0).topActivity;if (topActivity == null) {Slog.e(TAG, "Unable to get top activity");isBackgroundAuth = true;} else {final String topPackage = topActivity.getPackageName();if (!topPackage.contentEquals(getOwnerString())) {Slog.e(TAG, "Background authentication detected, top: " + topPackage+ ", client: " + this);isBackgroundAuth = true;}}}} catch (RemoteException e) {Slog.e(TAG, "Unable to get running tasks", e);isBackgroundAuth = true;}}// Fail authentication if we can't confirm the client activity is on top.if (isBackgroundAuth) {Slog.e(TAG, "Failing possible background authentication");authenticated = false;// SafetyNet logging for exploitation attempts of b/159249069.final ApplicationInfo appInfo = getContext().getApplicationInfo();EventLog.writeEvent(0x534e4554, "159249069", appInfo != null ? appInfo.uid : -1,"Attempted background authentication");}if (authenticated) {// SafetyNet logging for b/159249069 if constraint is violated.if (isBackgroundAuth) {final ApplicationInfo appInfo = getContext().getApplicationInfo();EventLog.writeEvent(0x534e4554, "159249069", appInfo != null ? appInfo.uid : -1,"Successful background authentication!");}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,isStrongBiometric());} else if (!isBiometricPrompt() && listener != null) {if (isStrongBiometric()) {KeyStore.getInstance().addAuthToken(byteToken);} else {Slog.d(getLogTag(), "Skipping addAuthToken");}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();} else {listener.onAuthenticationFailed(getHalDeviceId());}}}result = lockoutMode != LOCKOUT_NONE; // in a lockout modeif(result) { // locked outmAlreadyDone = true;}}} catch (RemoteException e) {Slog.e(getLogTag(), "Remote exception", e);result = true;}return result;
}

如果认证结果返回但是当前不是锁屏界面,并且判断是isBackgroundAuth的时候,就会将认证解锁authenticated设置为false,这时会进行认错误震动,如果是BiometricPrompt,就回调onAuthenticationFailedInternal,否则回调onAuthenticationFailed。

如果authenticated设置为true,会进行认证成功震动,并且重置错误次数resetFailedAttempts,如果是BiometricPrompt就回调onAuthenticationSucceededInternal,否则如果是isStrongBiometric,会向KeyStore中加入认证成功的记录byteToken,最后回调onAuthenticationSucceeded。

再来看listener

final BiometricServiceBase.ServiceListener listener = getListener();

这里是BiometricServiceBase.ServiceListener,getListener而调用的是父类的getListener。

public final BiometricServiceBase.ServiceListener getListener() {return mListener;
}

它是在ClientMonitor构造函数里面赋值的。我们知道ClientMonitor是在BiometricServiceBase中赋值的,因此,我们来看看BiometricServiceBase中的流程。

/*** Wraps the callback interface from Service -> Manager*/
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, boolean isStrongBiometric) throws RemoteException {throw new UnsupportedOperationException("Stub!");}default void onAuthenticationFailed(long deviceId) throws RemoteException {throw new UnsupportedOperationException("Stub!");}default void onAuthenticationFailedInternal()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 {};
}/*** 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, boolean isStrongBiometric) throws RemoteException {if (getWrapperReceiver() != null) {getWrapperReceiver().onAuthenticationSucceeded(requireConfirmation, token, isStrongBiometric);}}@Overridepublic void onAuthenticationFailedInternal()throws RemoteException {if (getWrapperReceiver() != null) {getWrapperReceiver().onAuthenticationFailed();}}
}

继续到FaceManager中

/*** Receives callbacks from the ClientMonitor implementations. The results are forwarded to* the FaceManager.*/
private class ServiceListenerImpl implements ServiceListener {private IFaceServiceReceiver mFaceServiceReceiver;public ServiceListenerImpl(IFaceServiceReceiver receiver) {mFaceServiceReceiver = receiver;}@Overridepublic void onAuthenticationSucceeded(long deviceId,BiometricAuthenticator.Identifier biometric, int userId) throws RemoteException {if (mFaceServiceReceiver != null) {if (biometric == null || biometric instanceof Face) {mFaceServiceReceiver.onAuthenticationSucceeded(deviceId, (Face) biometric, userId, isStrongBiometric());} else {Slog.e(TAG, "onAuthenticationSucceeded received non-face biometric");}}}
}

mFaceServiceReceiver是一个IFaceServiceReceiver对象,它是在ServiceListenerImpl的构造函数里面传过来的。它的创建是在前面介绍的FaceManager#authenticate。

@Override // Binder call
public void authenticate(final IBinder token, final long opId, int userId,final IFaceServiceReceiver receiver, final int flags,final String opPackageName) {final AuthenticationClientImpl client = new FaceAuthClient(getContext(),mDaemonWrapper, mHalDeviceId, token, new ServiceListenerImpl(receiver),mCurrentUserId, 0 /* groupId */, opId, restricted, opPackageName,0 /* cookie */, false /* requireConfirmation */);
}

可以看到,这个receiver就是传递进去的IFaceServiceReceiver,而receiver是从客户端传入的,也就是前面SystemUI里面。

//framework/base/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
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);}
}

3.3 人脸解锁

因此,再来看SystemUI的KeyguardUpdateMonitor中的mFaceAuthenticationCallback。

FaceManager.AuthenticationCallback mFaceAuthenticationCallback= new FaceManager.AuthenticationCallback() {@Overridepublic void onAuthenticationFailed() {handleFaceAuthFailed();}@Overridepublic void onAuthenticationSucceeded(FaceManager.AuthenticationResult result) {Trace.beginSection("KeyguardUpdateMonitor#onAuthenticationSucceeded");handleFaceAuthenticated(result.getUserId(), result.isStrongBiometric());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, boolean isStrongBiometric) {Trace.beginSection("KeyGuardUpdateMonitor#handlerFaceAuthenticated");try {if (mGoingToSleep) {Log.d(TAG, "Aborted successful auth because device is going to sleep.");return;}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;}if (DEBUG_FACE) Log.d(TAG, "Face auth succeeded for user " + userId);onFaceAuthenticated(userId, isStrongBiometric);} finally {setFaceRunningState(BIOMETRIC_STATE_STOPPED);}Trace.endSection();
}

继续走onFaceAuthenticated(userId, isStrongBiometric);

protected void onFaceAuthenticated(int userId, boolean isStrongBiometric) {Trace.beginSection("KeyGuardUpdateMonitor#onFaceAuthenticated");Assert.isMainThread();mUserFaceAuthenticated.put(userId,new BiometricAuthenticated(true, isStrongBiometric));// 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,isStrongBiometric);}}// Only authenticate face once when assistant is visiblemAssistantVisible = false;// Report unlock with strong or non-strong biometricreportSuccessfulBiometricUnlock(isStrongBiometric, userId);Trace.endSection();
}

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

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

@Override
public void onBiometricAuthenticated(int userId, BiometricSourceType biometricSourceType, boolean isStrongBiometric) {Trace.beginSection("BiometricUnlockController#onBiometricAuthenticated");if (mUpdateMonitor.isGoingToSleep()) {mPendingAuthenticated = new PendingAuthenticated(userId, biometricSourceType, isStrongBiometric);Trace.endSection();return;}mBiometricType = biometricSourceType;mMetricsLogger.write(new LogMaker(MetricsEvent.BIOMETRIC_AUTH).setType(MetricsEvent.TYPE_SUCCESS).setSubtype(toSubtype(biometricSourceType)));Optional.ofNullable(BiometricUiEvent.SUCCESS_EVENT_BY_SOURCE_TYPE.get(biometricSourceType)).ifPresent(UI_EVENT_LOGGER::log);boolean unlockAllowed = mKeyguardBypassController.onBiometricAuthenticated(biometricSourceType, isStrongBiometric);if (unlockAllowed) {mKeyguardViewMediator.userActivity();startWakeAndUnlock(biometricSourceType, isStrongBiometric);} else {Log.d(TAG, "onBiometricAuthenticated aborted by bypass controller");}
}

直接看startWakeAndUnlock。

public void startWakeAndUnlock(BiometricSourceType biometricSourceType,boolean isStrongBiometric) {startWakeAndUnlock(calculateMode(biometricSourceType, isStrongBiometric));
}public void startWakeAndUnlock(@WakeAndUnlockMode int mode) {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.mNotificationShadeWindowController.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 = mDozeParameters.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 && mMode != MODE_NONE) {wakeUp.run();}switch (mMode) {case MODE_DISMISS_BOUNCER:case MODE_UNLOCK_FADING:Trace.beginSection("MODE_DISMISS_BOUNCER or MODE_UNLOCK_FADING");mKeyguardViewController.notifyKeyguardAuthenticated(false /* strongAuth */);Trace.endSection();break;case MODE_UNLOCK_COLLAPSING:case MODE_SHOW_BOUNCER:Trace.beginSection("MODE_UNLOCK_COLLAPSING 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();}mNotificationShadeWindowController.setNotificationShadeFocusable(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();
}

​ 如果当前是在或者要进入Bouncer界面,就走mKeyguardViewController.notifyKeyguardAuthenticated。

public void notifyKeyguardAuthenticated(boolean strongAuth) {ensureView();mKeyguardView.finish(strongAuth, KeyguardUpdateMonitor.getCurrentUser());
}@Override
public void finish(boolean strongAuth, int targetUserId) {// If there's a pending runnable because the user interacted with a widget// and we're leaving keyguard, then run it.boolean deferKeyguardDone = false;if (mDismissAction != null) {deferKeyguardDone = mDismissAction.onDismiss();mDismissAction = null;mCancelAction = null;}if (mViewMediatorCallback != null) {if (deferKeyguardDone) {mViewMediatorCallback.keyguardDonePending(strongAuth, targetUserId);} else {mViewMediatorCallback.keyguardDone(strongAuth, targetUserId);}}
}private void tryKeyguardDone() {if (DEBUG) {Log.d(TAG, "tryKeyguardDone: pending - " + mKeyguardDonePending + ", animRan - "+ mHideAnimationRun + " animRunning - " + mHideAnimationRunning);}if (!mKeyguardDonePending && mHideAnimationRun && !mHideAnimationRunning) {handleKeyguardDone();} else if (!mHideAnimationRun) {if (DEBUG) Log.d(TAG, "tryKeyguardDone: starting pre-hide animation");mHideAnimationRun = true;mHideAnimationRunning = true;mKeyguardViewControllerLazy.get().startPreHideAnimation(mHideAnimationFinishedRunnable);}
}

如果是MODE_WAKE_AND_UNLOCK,就走mKeyguardViewMediator.onWakeAndUnlocking()

public void onWakeAndUnlocking() {Trace.beginSection("KeyguardViewMediator#onWakeAndUnlocking");mWakeAndUnlocking = true;keyguardDone();Trace.endSection();
}

后面解锁流程就不看了,本文主要是介绍人脸相关的流程。


http://www.ppmy.cn/news/1475788.html

相关文章

vscode连接到WSL子系统报错/bin/ea1445cc7016315d0f5728f8e8b12a45dc0a7286/node: not found

子系统单独启动没有问题&#xff0c;vscode启动wsl子系统报错 报错如下&#xff1a; /home/sophia/.vscode-server/bin/ea1445cc7016315d0f5728f8e8b12a45dc0a7286/bin/code-server: 12: /home/sophia/.vscode-server/bin/ea1445cc7016315d0f5728f8e8b12a45dc0a7286/node: n…

git github gitee 三者关系

Git&#xff1a; Git 是一个分布式版本控制系统&#xff0c;用于跟踪源代码的更改。它由 Linus Torvalds 于 2005 年开发&#xff0c;目的是更好地管理 Linux 内核开发。Git 是一个命令行工具&#xff0c;具有以下特点&#xff1a; 分布式&#xff1a;每个开发者的工作目录都是…

B树:深入解析与实战应用

在数据结构和算法的世界中&#xff0c;B树&#xff08;B-tree&#xff09;无疑是一颗璀璨的明星。它不仅广泛应用于数据库和文件系统的索引结构中&#xff0c;而且在许多需要高效数据检索的场景中发挥着重要作用。本文将从B树的基本概念入手&#xff0c;通过图文结合的方式&…

海外媒体宣发:尼日利亚媒体通稿报道发布-大舍传媒

尼日利亚媒体概述 尼日利亚&#xff0c;作为非洲人口最多的国家&#xff0c;拥有多元化的媒体环境。在这个国家&#xff0c;你可以找到各种类型的媒体&#xff0c;包括传统媒体和新媒体。传统媒体主要包括报纸、电视和广播&#xff0c;而新媒体则主要是互联网和社交媒体。 尼…

Java+springboot+vue智慧班牌小程序源码,智慧班牌系统可以提供哪些服务?

智慧班牌全套源码&#xff0c;系统技术架构&#xff1a;Javaspringbootvue element-ui小程序电子班牌&#xff1a;Java Android演示正版授权。 智慧班牌在智慧校园的数字化建设中提供了多种服务&#xff0c;这些服务不仅丰富了校园的信息展示方式&#xff0c;还促进了家校互动…

Open-TeleVision——通过VR沉浸式感受人形机器人视野:兼备远程控制和深度感知能力

前言 7.3日&#xff0c;我司七月在线(集AI大模型职教、应用开发、机器人解决方案为一体的科技公司)的「大模型机器人(具身智能)线下营」群里的一学员发了《Open-TeleVision: Teleoperation with Immersive Active Visual Feedback》这篇论文的链接 我当时快速看了一遍&#x…

ASP.NET MVC-制作可排序的表格组件-PagedList版

环境&#xff1a; win10 参考&#xff1a; 学习ASP.NET MVC(十一)——分页 - DotNet菜园 - 博客园 https://www.cnblogs.com/chillsrc/p/6554697.html ASP.NET MVCEF框架实现分页_ef 异步分页-CSDN博客 https://blog.csdn.net/qq_40052237/article/details/106599528 本文略去…

ATE电源芯片测试方案之效率曲线评估芯片性能

在电子产品的设计中&#xff0c;电源管理芯片的效率优化是提升能效和延长使用寿命的关键。因此&#xff0c;探究电源管理芯片在不同工作条件下的效率变化&#xff0c;并通过效率曲线进行可视化表达&#xff0c;对于电源管理技术的进步至关重要。 电源管理芯片的效率曲线 鉴于电…

【C++深入学习】类和对象(一)

欢迎来到HarperLee的学习笔记&#xff01; 博主主页传送门&#xff1a;HarperLee博客主页&#xff01; 欢迎各位大佬交流学习&#xff01; 本篇本章正式进入C的类和对象部分&#xff0c;本部分知识分为三小节。复习&#xff1a; 结构体复习–内存对齐编译和链接函数栈桢的创建…

OpenCV solvePnP位姿估计

目录 一、概述 二、实现代码 2.1solvePnP函数 2.1.1输入参数 2.1.2输出参数 2.2完整代码 三、实现效果 3.1标定板位姿 3.2标定板到相机的变换矩阵 一、概述 完成相机标定后&#xff0c;可以通过检测标定板在图像中的位置来计算标定板在相机坐标系下的位姿&#xff08;…

vue 项目代码架构

Vue项目的代码架构通常遵循一定的组织结构和约定&#xff0c;以提高项目的可维护性、可扩展性和可读性。以下是对Vue项目代码架构的详细解析&#xff1a; 一、项目目录结构 Vue项目的目录结构通常包括以下几个关键部分&#xff1a; 根目录&#xff1a; package.json&#x…

如何使用这个XMLHttpRequest?

ajax含义:async javascript and XML;是异步的JS和XML&#xff1b;是实现页面局部刷新的技术(是一门独立的技术)。 为什么在js内能够使用呢&#xff1f;是因为ajax在浏览器内内置了一个核心对象&#xff0c;--》XMLHttpRequest&#xff08;低版本的IE浏览器没有&#xff09; 步…

Jetson-AGX-Orin gstreamer+rtmp+http-flv 推拉流

Jetson-AGX-Orin gstreamerrtmphttp-flv 推拉流 Orin是ubuntu20.04 ARM64架构的系统&#xff0c;自带gstreamer 1、 测试摄像头 测试摄像头可以用v4l2-ctl命令或者用gst-launch-1.0 #用v4l2-ctl测试摄像头,有尖角符号持续打印则正常 v4l2-ctl -d /dev/video0 --set-fmt-vid…

MySQL篇:事务

1.四大特性 首先&#xff0c;事务的四大特性&#xff1a;ACID&#xff08;原子性&#xff0c;一致性&#xff0c;隔离性&#xff0c;持久性&#xff09; 在InnoDB引擎中&#xff0c;是怎么来保证这四个特性的呢&#xff1f; 持久性是通过 redo log &#xff08;重做日志&…

暗黑魅力:Xcode全面拥抱应用暗黑模式开发指南

暗黑魅力&#xff1a;Xcode全面拥抱应用暗黑模式开发指南 随着苹果在iOS 13和iPadOS 13中引入暗黑模式&#xff0c;用户可以根据自己的喜好或环境光线选择不同的界面主题。作为开发者&#xff0c;支持暗黑模式不仅能提升用户体验&#xff0c;还能彰显应用的专业性。Xcode提供了…

AI数字人直播saas系统源码分析与解读,哪家部署的系统更具优势?

随着AI数字人直播的应用潜力持续展现&#xff0c;越来越多的创业者都有了打造AI数字人直播saas系统&#xff0c;从而通过为各大企业提供AI数字人直播等服务来获得收益。在此背景下&#xff0c;各大数字人源码厂商所部署的AI数字人直播saas系统源码质量成为了众多创业者的重点关…

概率论原理精解【3】

文章目录 向量值向量值函数导数向量值函数定义性质应用示例 向量值函数的导数定义性质应用 向量值 向量值函数导数 D n ⊂ R n , 向量值函数 f : D n → R m D^n \subset R^n,向量值函数f:D^n\rightarrow R^m Dn⊂Rn,向量值函数f:Dn→Rm 1. 向量值函数 f ( f 1 , f 2 , . . …

外呼系统用回拨模式打电话有什么优势

外呼系统采用回拨模式的优势主要体现在以下几个方面&#xff1a; 1. 提高工作效率 - 回拨模式允许通过一键拨打客户电话&#xff0c;一旦客户接听&#xff0c;即可立即进行沟通&#xff0c;大大缩短了拨号等待时间和无效通话时间。 - 与传统的外呼方式相比&#xff0c;回拨模式…

OSU!题解(概率dp)

题目&#xff1a;OSU! - 洛谷 思路&#xff1a; 设E()表示截止到i所获得的分数&#xff1b; 对于到i点的每一个l&#xff0c;如果第i1点为1&#xff0c;那么会新增分数3*l^23*l1; 就有递推公式方程&#xff1a; E()E()p[i1]p*(3*l^23*l1);(p代表截止到i获得长度l的概率)&a…

策划分销策略中的AI智能名片O2O商城小程序应用探索

摘要&#xff1a;在数字经济时代&#xff0c;企业面临着前所未有的市场竞争压力&#xff0c;传统的分销模式已难以满足快速变化的市场需求。随着人工智能&#xff08;AI&#xff09;技术的不断成熟与移动互联网的普及&#xff0c;AI智能名片O2O商城小程序作为一种新兴的分销工具…