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

java 复制代码
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。

java 复制代码
@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

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">

    <LinearLayout
        style="@style/SudContentFrame"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical"
        android:clipToPadding="false"
        android:clipChildren="false">

        <LinearLayout
             android: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" />

                <ImageView
                           android:id="@+id/circle_view"
                           android:layout_width="match_parent"
                           android:layout_height="match_parent" />
            </com.android.settings.biometrics.face.FaceSquareFrameLayout>
 
            <TextView
                      style="@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录入的方法

java 复制代码
@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() {
    @Override
    public void onEnrolled() {
        FaceEnrollEnrolling.this.launchFinish(mToken);
    }
};

startEnrollment里面创建了一个FaceEnrollPreviewFragment,然后设置了人脸录入完成的监听。此方法中没有明显录入的方法,可见录入方法存在于他的父类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);
}

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

java 复制代码
/**
 * @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

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

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

java 复制代码
/**
 * 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,继续往下看。

java 复制代码
/**
  * Receives the incoming binder calls from FaceManager.
  */
private final class FaceServiceWrapper extends IFaceService.Stub {
    @Override // Binder call
    public 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) {
            @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);
    }
}

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

java 复制代码
/**
 * 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首先检查人脸录入的数量是否达到限制。

java 复制代码
@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配置的数量和已经录入的进行对比。

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

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

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

java 复制代码
/**
 * 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
     */
    @VisibleForTesting
    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
            // <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 immediately
            mCurrentClient = newClient;
            startCurrentClient(mCurrentClient.getCookie());
        }
    }

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

java 复制代码
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()。

java 复制代码
//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

java 复制代码
//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();
    }
}

private class MyHandler extends Handler {
    @Override
    public 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)

java 复制代码
//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);
    }

这里调用FaceEnrollPreviewFragment#onEnrollmentProgressChange

java 复制代码
 //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

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

继续

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

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

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

java 复制代码
 // 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

java 复制代码
/**
 * 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

java 复制代码
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 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();
    }
}

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来开始人脸解锁监听请求。

java 复制代码
/**
 * 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

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

继续到BiometricServiceBase#authenticateInternal

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

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

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

java 复制代码
/**
 * 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。

java 复制代码
// 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

java 复制代码
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
        // <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 immediately
        mCurrentClient = 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

java 复制代码
/**
 * 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开始,这块的实现由区别了。

java 复制代码
/**
 * 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开始分析。

java 复制代码
@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 keystore
                listener.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 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();
                    } else {
                        listener.onAuthenticationFailed(getHalDeviceId());
                    }
                }
            }
            result = lockoutMode != LOCKOUT_NONE; // in a lockout mode
            if(result) { // locked out
                mAlreadyDone = 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

java 复制代码
final BiometricServiceBase.ServiceListener listener = getListener();

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

java 复制代码
public final BiometricServiceBase.ServiceListener getListener() {
    return mListener;
}

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

java 复制代码
/**
 * 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;
    }

    @Override
    public void onAuthenticationSucceededInternal(boolean requireConfirmation,
                  byte[] token, boolean isStrongBiometric) throws RemoteException {
        if (getWrapperReceiver() != null) {
            getWrapperReceiver().onAuthenticationSucceeded(
                requireConfirmation, token, isStrongBiometric);
        }
    }

    @Override
    public void onAuthenticationFailedInternal()
        throws RemoteException {
        if (getWrapperReceiver() != null) {
            getWrapperReceiver().onAuthenticationFailed();
        }
    }
}

继续到FaceManager中

java 复制代码
/**
 * 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;
    }
    
	@Override
    public 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。

java 复制代码
@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里面。

java 复制代码
//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。

java 复制代码
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(), result.isStrongBiometric());
        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

java 复制代码
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);

java 复制代码
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 bouncer
    if (getUserCanSkipBouncer(userId)) {
        mTrustManager.unlockedByBiometricForUser(userId, BiometricSourceType.FACE);
    }
    // Don't send cancel if authentication succeeds
    mFaceCancelSignal = 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 visible
    mAssistantVisible = false;

    // Report unlock with strong or non-strong biometric
    reportSuccessfulBiometricUnlock(isStrongBiometric, userId);

    Trace.endSection();
}

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

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

java 复制代码
@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。

java 复制代码
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。

java 复制代码
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()

java 复制代码
public void onWakeAndUnlocking() {
    Trace.beginSection("KeyguardViewMediator#onWakeAndUnlocking");
    mWakeAndUnlocking = true;
    keyguardDone();
    Trace.endSection();
}

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

相关推荐
雨白8 小时前
Android 快捷方式实战指南:静态、动态与固定快捷方式详解
android
hqk8 小时前
鸿蒙项目实战:手把手带你实现 WanAndroid 布局与交互
android·前端·harmonyos
LING9 小时前
RN容器启动优化实践
android·react native
恋猫de小郭11 小时前
Flutter 发布官方 Skills ,Flutter 在 AI 领域再添一助力
android·前端·flutter
Kapaseker16 小时前
一杯美式搞懂 Any、Unit、Nothing
android·kotlin
黄林晴16 小时前
你的 Android App 还没接 AI?Gemini API 接入全攻略
android
恋猫de小郭1 天前
2026 Flutter VS React Native ,同时在 AI 时代 VS Native 开发,你没见过的版本
android·前端·flutter
冬奇Lab1 天前
PowerManagerService(上):电源状态与WakeLock管理
android·源码阅读
BoomHe1 天前
Now in Android 架构模式全面分析
android·android jetpack
二流小码农2 天前
鸿蒙开发:上传一张参考图片便可实现页面功能
android·ios·harmonyos