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 Studio | 连接手机设备后,启动App时出现:Waiting For DebuggerApplication (App名)...
android·adb·智能手机·android studio
叶羽西9 分钟前
Android Studio Gradle Sync timeout
android·ide·android studio
m0_7482561441 分钟前
Android 关于Tencent vConsole 添加入webView 总结
android
开发者阿伟1 小时前
Android Jetpack DataBinding源码解析与实践
android·android jetpack
AirDroid_qs2 小时前
Niushop开源商城(漏洞复现)
android·网络安全·开源
Autumn.h2 小时前
niushop开源商城靶场漏洞
android
戏谑3 小时前
Android 常用布局
android·view
拭心14 小时前
Google 提供的 Android 端上大模型组件:MediaPipe LLM 介绍
android
带电的小王17 小时前
WhisperKit: Android 端测试 Whisper -- Android手机(Qualcomm GPU)部署音频大模型
android·智能手机·whisper·qualcomm
梦想平凡17 小时前
PHP 微信棋牌开发全解析:高级教程
android·数据库·oracle