人脸注册,解锁,响应,一网打尽

2023-05-16

现代智能手机,基本上都有人脸解锁功能,那他是怎么实现的哦?下面从代码角度来分析下他。

先上流程图

人脸解锁,都要先录入(这部分后面会出其他博客),再注册,再人脸解锁,响应,下面从代码角度来分析他。

先从锁屏部分的类KeyguardUpdateMonitor入手,下面是人脸服务注册方法。

private void startListeningForFace() {
        final int userId = getCurrentUser();
        final boolean unlockPossible = isUnlockWithFacePossible(userId);
        if (mFaceCancelSignal != null) {
            Log.e(TAG, "Cancellation signal is not null, high chance of bug in face auth lifecycle"
                    + " management. Face state: " + mFaceRunningState
                    + ", unlockPossible: " + unlockPossible);
        }

        if (mFaceRunningState == BIOMETRIC_STATE_CANCELLING) {
            setFaceRunningState(BIOMETRIC_STATE_CANCELLING_RESTARTING);
            return;
        } else if (mFaceRunningState == BIOMETRIC_STATE_CANCELLING_RESTARTING) {
            // Waiting for ERROR_CANCELED before requesting auth again
            return;
        }
        if (DEBUG) Log.v(TAG, "startListeningForFace(): " + mFaceRunningState);

        if (unlockPossible) {
            mFaceCancelSignal = new CancellationSignal();

            // This would need to be updated for multi-sensor devices
            final boolean supportsFaceDetection = !mFaceSensorProperties.isEmpty()
                    && mFaceSensorProperties.get(0).supportsFaceDetection;
            mFaceAuthUserId = userId;
            if (isEncryptedOrLockdown(userId) && supportsFaceDetection) {
                mFaceManager.detectFace(mFaceCancelSignal, mFaceDetectionCallback, userId);
            } else {
                final boolean isBypassEnabled = mKeyguardBypassController != null
                        && mKeyguardBypassController.isBypassEnabled();
                mFaceManager.authenticate(null /* crypto */, mFaceCancelSignal,
                        mFaceAuthenticationCallback, null /* handler */, userId, isBypassEnabled);
            }
            setFaceRunningState(BIOMETRIC_STATE_RUNNING);
        }
    }

第31行,人脸注册,这里要注意下变量mFaceAuthenticationCallback,这是回调接口对象,底层设别结果的回传信息,会通过这个变量对象告知用户人脸解锁成功或失败或错误,等等。

    @VisibleForTesting
    final FaceManager.AuthenticationCallback mFaceAuthenticationCallback
            = new FaceManager.AuthenticationCallback() {

                @Override
                public void onAuthenticationFailed() {
                    handleFaceAuthFailed();
                    if (mKeyguardBypassController != null) {
                        mKeyguardBypassController.setUserHasDeviceEntryIntent(false);
                    }
                }

                @Override
                public void onAuthenticationSucceeded(FaceManager.AuthenticationResult result) {
                    Trace.beginSection("KeyguardUpdateMonitor#onAuthenticationSucceeded");
                    handleFaceAuthenticated(result.getUserId(), result.isStrongBiometric());
                    Trace.endSection();

                    if (mKeyguardBypassController != null) {
                        mKeyguardBypassController.setUserHasDeviceEntryIntent(false);
                    }
                }

                @Override
                public void onAuthenticationHelp(int helpMsgId, CharSequence helpString) {
                    handleFaceHelp(helpMsgId, helpString.toString());
                }

                @Override
                public void onAuthenticationError(int errMsgId, CharSequence errString) {
                    handleFaceError(errMsgId, errString.toString());
                    if (mKeyguardBypassController != null) {
                        mKeyguardBypassController.setUserHasDeviceEntryIntent(false);
                    }
                }

                @Override
                public void onAuthenticationAcquired(int acquireInfo) {
                    handleFaceAcquired(acquireInfo);
                }
    };

后面在设别结果回传的时候,再讨论。

回到代码mFaceManager#authenticate 部分,讨论下变量mFaceManager是如何定义的。

在KeyguardUpdateMonitor类的构造函数中定义的,如下,显然这是一个系统服务。

if (mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_FACE)) {
            mFaceManager = (FaceManager) context.getSystemService(Context.FACE_SERVICE);
            mFaceSensorProperties = mFaceManager.getSensorPropertiesInternal();
        }

搜索关键字Context.FACE_SERVICE,在SystemServiceRegistry类中实现了系统服务注册。

registerService(Context.FACE_SERVICE, FaceManager.class,
                new CachedServiceFetcher<FaceManager>() {
                    @Override
                    public FaceManager createService(ContextImpl ctx)
                            throws ServiceNotFoundException {
                        final IBinder binder;
                        if (ctx.getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.O) {
                            binder = ServiceManager.getServiceOrThrow(Context.FACE_SERVICE);
                        } else {
                            binder = ServiceManager.getService(Context.FACE_SERVICE);
                        }
                        IFaceService service = IFaceService.Stub.asInterface(binder);
                        return new FaceManager(ctx.getOuterContext(), service);
                    }
                });

注意第12,13行,会获得跨进程对象FaceService对象实例,然后绑定到FaceManager系统对象实例中,然后在mFaceManager#authenticate方法中,使用mService 变量,调用到FaceService对象实例下的authenticate方法。

 @RequiresPermission(USE_BIOMETRIC_INTERNAL)
    public void authenticate(@Nullable CryptoObject crypto, @Nullable CancellationSignal cancel,
            @NonNull AuthenticationCallback callback, @Nullable Handler handler, int userId,
            boolean isKeyguardBypassEnabled) {
        if (callback == null) {
            throw new IllegalArgumentException("Must supply an authentication callback");
        }

        if (cancel != null && cancel.isCanceled()) {
            Slog.w(TAG, "authentication already canceled");
            return;
        }

        if (mService != null) {
            try {
                useHandler(handler);
                mAuthenticationCallback = callback;
                mCryptoObject = crypto;
                final long operationId = crypto != null ? crypto.getOpId() : 0;
                Trace.beginSection("FaceManager#authenticate");
                final long authId = mService.authenticate(mToken, operationId, userId,
                        mServiceReceiver, mContext.getOpPackageName(), isKeyguardBypassEnabled);
                if (cancel != null) {
                    cancel.setOnCancelListener(new OnAuthenticationCancelListener(authId));
                }
            } catch (RemoteException e) {
                Slog.w(TAG, "Remote exception while authenticating: ", e);
                // 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();
            }
        }
    }

搜索IFaceService service = IFaceService.Stub.asInterface(binder);

可以判断出mService 就是跨进程对象实例FaceService

第22行,注意mServiceReceiver,这是一个回调接口对象,应该是跨进程的,后面会讲到。

来到FaceService#authenticate

@Override // Binder call
        public long authenticate(final IBinder token, final long operationId, int userId,
                final IFaceServiceReceiver receiver, final String opPackageName,
                boolean isKeyguardBypassEnabled) {
            Utils.checkPermission(getContext(), USE_BIOMETRIC_INTERNAL);

            // TODO(b/152413782): If the sensor supports face detect and the device is encrypted or
            //  lockdown, something wrong happened. See similar path in FingerprintService.

            final boolean restricted = false; // Face APIs are private
            final int statsClient = Utils.isKeyguard(getContext(), opPackageName)
                    ? BiometricsProtoEnums.CLIENT_KEYGUARD
                    : BiometricsProtoEnums.CLIENT_UNKNOWN;

            // Keyguard check must be done on the caller's binder identity, since it also checks
            // permission.
            final boolean isKeyguard = Utils.isKeyguard(getContext(), opPackageName);

            final Pair<Integer, ServiceProvider> provider = getSingleProvider();
            if (provider == null) {
                Slog.w(TAG, "Null provider for authenticate");
                return -1;
            }

            return provider.second.scheduleAuthenticate(provider.first, token, operationId, userId,
                    0 /* cookie */,
                    new ClientMonitorCallbackConverter(receiver), opPackageName, restricted,
                    statsClient, isKeyguard, isKeyguardBypassEnabled);
        }

第25行,provider.second,看看是什么对象。

先从第19行开始看起

final Pair<Integer, ServiceProvider> provider = getSingleProvider();

@Nullable
    private Pair<Integer, ServiceProvider> getSingleProvider() {
        final List<FaceSensorPropertiesInternal> properties = getSensorProperties();
        if (properties.size() != 1) {
            Slog.e(TAG, "Multiple sensors found: " + properties.size());
            return null;
        }

        // Theoretically we can just return the first provider, but maybe this is easier to
        // understand.
        final int sensorId = properties.get(0).sensorId;
        for (ServiceProvider provider : mServiceProviders) {
            if (provider.containsSensor(sensorId)) {
                return new Pair<>(sensorId, provider);
            }
        }

        Slog.e(TAG, "Single sensor, but provider not found");
        return null;
    }

看看第12行的mServiceProviders集合变量在哪儿赋值的。 

private void addHidlProviders(@NonNull List<FaceSensorPropertiesInternal> hidlSensors) {
            for (FaceSensorPropertiesInternal hidlSensor : hidlSensors) {
                mServiceProviders.add(
                        new Face10(getContext(), hidlSensor, mLockoutResetDispatcher));
            }
        }

从第4行代码,我们可以判断出provider.second 就是Face10对象实例。

provider.second.scheduleAuthenticate就是Face10对象实例下的scheduleAuthenticate方法。

@Override
    public void scheduleAuthenticate(int sensorId, @NonNull IBinder token, long operationId,
            int userId, int cookie, @NonNull ClientMonitorCallbackConverter receiver,
            @NonNull String opPackageName, long requestId, boolean restricted, int statsClient,
            boolean allowBackgroundAuthentication, boolean isKeyguardBypassEnabled) {
        mHandler.post(() -> {
            scheduleUpdateActiveUserWithoutHandler(userId);

            final boolean isStrongBiometric = Utils.isStrongBiometric(mSensorId);
            final FaceAuthenticationClient client = new FaceAuthenticationClient(mContext,
                    mLazyDaemon, token, requestId, receiver, userId, operationId, restricted,
                    opPackageName, cookie, false /* requireConfirmation */, mSensorId,
                    isStrongBiometric, statsClient, mLockoutTracker, mUsageStats,
                    allowBackgroundAuthentication, isKeyguardBypassEnabled);
            mScheduler.scheduleClientMonitor(client);
        });
    }

 第15行,看看变量mScheduler在哪儿定义的。

@VisibleForTesting
    Face10(@NonNull Context context,
            @NonNull FaceSensorPropertiesInternal sensorProps,
            @NonNull LockoutResetDispatcher lockoutResetDispatcher,
            @NonNull BiometricScheduler scheduler) {
        mSensorProperties = sensorProps;
        mContext = context;
        mSensorId = sensorProps.sensorId;
        mScheduler = scheduler;
        mHandler = new Handler(Looper.getMainLooper());
        mUsageStats = new UsageStats(context);
        mAuthenticatorIds = new HashMap<>();
        mLazyDaemon = Face10.this::getDaemon;
        mLockoutTracker = new LockoutHalImpl();
        mHalResultController = new HalResultController(sensorProps.sensorId, context, mHandler,
                mScheduler, mLockoutTracker, lockoutResetDispatcher);
        mHalResultController.setCallback(() -> {
            mDaemon = null;
            mCurrentUserId = UserHandle.USER_NULL;
        });

        try {
            ActivityManager.getService().registerUserSwitchObserver(mUserSwitchObserver, TAG);
        } catch (RemoteException e) {
            Slog.e(TAG, "Unable to register user switch observer");
        }
    }

第9行,在Face10构造函数中赋值的,需要继续追踪,Face10对象实例在哪儿实现的,在

FaceService#addHidlProviders,会调用的,

 private void addHidlProviders(@NonNull List<FaceSensorPropertiesInternal> hidlSensors) {
            for (FaceSensorPropertiesInternal hidlSensor : hidlSensors) {
                mServiceProviders.add(
                        new Face10(getContext(), hidlSensor, mLockoutResetDispatcher));
            }
        }
public Face10(@NonNull Context context, @NonNull FaceSensorPropertiesInternal sensorProps,
            @NonNull LockoutResetDispatcher lockoutResetDispatcher) {
        this(context, sensorProps, lockoutResetDispatcher,
                new BiometricScheduler(TAG, BiometricScheduler.SENSOR_TYPE_FACE,
                        null /* gestureAvailabilityTracker */));
    }

搜索前面的代码mScheduler.scheduleClientMonitor(client)

可以确定mScheduler 就是第4行的new BiometricScheduler对象实例。

那继续往下看mScheduler.scheduleClientMonitor(client),实际上就是

public void scheduleClientMonitor(@NonNull BaseClientMonitor clientMonitor) {
        scheduleClientMonitor(clientMonitor, null /* clientFinishCallback */);
    }
  public void scheduleClientMonitor(@NonNull BaseClientMonitor clientMonitor,
            @Nullable BaseClientMonitor.Callback clientCallback) {
        // If the incoming operation should interrupt preceding clients, mark any interruptable
        // pending clients as canceling. Once they reach the head of the queue, the scheduler will
        // send ERROR_CANCELED and skip the operation.
        if (clientMonitor.interruptsPrecedingClients()) {
            for (Operation operation : mPendingOperations) {
                if (operation.mClientMonitor instanceof Interruptable
                        && operation.mState != Operation.STATE_WAITING_IN_QUEUE_CANCELING) {
                    Slog.d(getTag(), "New client incoming, marking pending client as canceling: "
                            + operation.mClientMonitor);
                    operation.mState = Operation.STATE_WAITING_IN_QUEUE_CANCELING;
                }
            }
        }

        mPendingOperations.add(new Operation(clientMonitor, clientCallback));
        Slog.d(getTag(), "[Added] " + clientMonitor
                + ", new queue size: " + mPendingOperations.size());

        // If the new operation should interrupt preceding clients, and if the current operation is
        // cancellable, start the cancellation process.
        if (clientMonitor.interruptsPrecedingClients()
                && mCurrentOperation != null
                && mCurrentOperation.mClientMonitor instanceof Interruptable
                && mCurrentOperation.mState == Operation.STATE_STARTED) {
            Slog.d(getTag(), "[Cancelling Interruptable]: " + mCurrentOperation);
            cancelInternal(mCurrentOperation);
        }

        startNextOperationIfIdle();
    }

第31行,继续往下看

{
        if (mCurrentOperation != null) {
            Slog.v(getTag(), "Not idle, current operation: " + mCurrentOperation);
            return;
        }
        if (mPendingOperations.isEmpty()) {
            Slog.d(getTag(), "No operations, returning to idle");
            return;
        }

        mCurrentOperation = mPendingOperations.poll();
        final BaseClientMonitor currentClient = mCurrentOperation.mClientMonitor;
        Slog.d(getTag(), "[Polled] " + mCurrentOperation);

        // If the operation at the front of the queue has been marked for cancellation, send
        // ERROR_CANCELED. No need to start this client.
        if (mCurrentOperation.mState == Operation.STATE_WAITING_IN_QUEUE_CANCELING) {
            Slog.d(getTag(), "[Now Cancelling] " + mCurrentOperation);
            if (!(currentClient instanceof Interruptable)) {
                throw new IllegalStateException("Mis-implemented client or scheduler, "
                        + "trying to cancel non-interruptable operation: " + mCurrentOperation);
            }

            final Interruptable interruptable = (Interruptable) currentClient;
            interruptable.cancelWithoutStarting(getInternalCallback());
            // Now we wait for the client to send its FinishCallback, which kicks off the next
            // operation.
            return;
        }

        if (mGestureAvailabilityDispatcher != null
                && mCurrentOperation.mClientMonitor instanceof AcquisitionClient) {
            mGestureAvailabilityDispatcher.markSensorActive(
                    mCurrentOperation.mClientMonitor.getSensorId(),
                    true /* active */);
        }

        // Not all operations start immediately. BiometricPrompt waits for its operation
        // to arrive at the head of the queue, before pinging it to start.
        final boolean shouldStartNow = currentClient.getCookie() == 0;
        if (shouldStartNow) {
            if (mCurrentOperation.isUnstartableHalOperation()) {
                final HalClientMonitor<?> halClientMonitor =
                        (HalClientMonitor<?>) mCurrentOperation.mClientMonitor;
                // Note down current length of queue
                final int pendingOperationsLength = mPendingOperations.size();
                final Operation lastOperation = mPendingOperations.peekLast();
                Slog.e(getTag(), "[Unable To Start] " + mCurrentOperation
                        + ". Last pending operation: " + lastOperation);

                // For current operations, 1) unableToStart, which notifies the caller-side, then
                // 2) notify operation's callback, to notify applicable system service that the
                // operation failed.
                halClientMonitor.unableToStart();
                if (mCurrentOperation.mClientCallback != null) {
                    mCurrentOperation.mClientCallback.onClientFinished(
                            mCurrentOperation.mClientMonitor, false /* success */);
                }

                // Then for each operation currently in the pending queue at the time of this
                // failure, do the same as above. Otherwise, it's possible that something like
                // setActiveUser fails, but then authenticate (for the wrong user) is invoked.
                for (int i = 0; i < pendingOperationsLength; i++) {
                    final Operation operation = mPendingOperations.pollFirst();
                    if (operation == null) {
                        Slog.e(getTag(), "Null operation, index: " + i
                                + ", expected length: " + pendingOperationsLength);
                        break;
                    }
                    if (operation.isHalOperation()) {
                        ((HalClientMonitor<?>) operation.mClientMonitor).unableToStart();
                    }
                    if (operation.mClientCallback != null) {
                        operation.mClientCallback.onClientFinished(operation.mClientMonitor,
                                false /* success */);
                    }
                    Slog.w(getTag(), "[Aborted Operation] " + operation);
                }

                // It's possible that during cleanup a new set of operations came in. We can try to
                // run these. A single request from the manager layer to the service layer may
                // actually be multiple operations (i.e. updateActiveUser + authenticate).
                mCurrentOperation = null;
                startNextOperationIfIdle();
            } else {
                Slog.d(getTag(), "[Starting] " + mCurrentOperation);
                currentClient.start(getInternalCallback());
                mCurrentOperation.mState = Operation.STATE_STARTED;
            }
        } else {
            try {
                mBiometricService.onReadyForAuthentication(currentClient.getCookie());
            } catch (RemoteException e) {
                Slog.e(getTag(), "Remote exception when contacting BiometricService", e);
            }
            Slog.d(getTag(), "Waiting for cookie before starting: " + mCurrentOperation);
            mCurrentOperation.mState = Operation.STATE_WAITING_FOR_COOKIE;
        }
    }

第87行,变量currentClient是哪个对象

搜索前面的代码,发现下面2行代码

mCurrentOperation = mPendingOperations.poll();

final BaseClientMonitor currentClient = mCurrentOperation.mClientMonitor

继续搜索

mPendingOperations.add(new Operation(clientMonitor, clientCallback));

可以确定clientMonitor 我们需要的。

scheduleClientMonitor方法中,有变量clientMonitor,可以确定是传递过来的

就是FaceAuthenticationClient 对象实例。因此currentClient.start就是调用FaceAuthenticationClient下的start方法,另外注意下第87行的start下的参数,应该后面在分析回调的时候,会讨论到。现在来看FaceAuthenticationClient下的start方法。

 @Override
    public void start(@NonNull Callback callback) {
        super.start(callback);
        mState = STATE_STARTED;
    }

第3行,继续追踪父类AuthenticationClient

 /**
     * Start authentication
     */
    @Override
    public void start(@NonNull Callback callback) {
        super.start(callback);

        final @LockoutTracker.LockoutMode int lockoutMode =
                mLockoutTracker.getLockoutModeForUser(getTargetUserId());
        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();
    }

第27行,startHalOperation()最终调用的是FaceAuthenticationClient#startHalOperation()

@Override
    protected void startHalOperation() {
        try {
            if (mSensorPrivacyManager != null
                    && mSensorPrivacyManager
                    .isSensorPrivacyEnabled(SensorPrivacyManager.Sensors.CAMERA,
                    getTargetUserId())) {
                onError(BiometricConstants.BIOMETRIC_ERROR_HW_UNAVAILABLE,
                        0 /* vendorCode */);
                mCallback.onClientFinished(this, false /* success */);
            } else {
                mCancellationSignal = getFreshDaemon().authenticate(mOperationId);
            }
        } catch (RemoteException e) {
            Slog.e(TAG, "Remote exception when requesting auth", e);
            onError(BiometricFaceConstants.FACE_ERROR_HW_UNAVAILABLE, 0 /* vendorCode */);
            mCallback.onClientFinished(this, false /* success */);
        }
    }

第12行getFreshDaemon().authenticate(mOperationId);

getFreshDaemon()实际上是调用Face10#getFreshDaemon

    private synchronized IBiometricsFace getDaemon() {
        if (mTestHalEnabled) {
            final TestHal testHal = new TestHal(mContext, mSensorId);
            testHal.setCallback(mHalResultController);
            return testHal;
        }

        if (mDaemon != null) {
            return mDaemon;
        }

        Slog.d(TAG, "Daemon was null, reconnecting, current operation: "
                + mScheduler.getCurrentClient());

        try {
            mDaemon = IBiometricsFace.getService();
        } catch (java.util.NoSuchElementException e) {
            // Service doesn't exist or cannot be opened.
            Slog.w(TAG, "NoSuchElementException", e);
        } catch (RemoteException e) {
            Slog.e(TAG, "Failed to get face HAL", e);
        }

        if (mDaemon == null) {
            Slog.w(TAG, "Face HAL not available");
            return null;
        }

        mDaemon.asBinder().linkToDeath(this, 0 /* flags */);

        // HAL ID for these HIDL versions are only used to determine if callbacks have been
        // successfully set.
        long halId = 0;
        try {
            halId = mDaemon.setCallback(mHalResultController).value;
        } catch (RemoteException e) {
            Slog.e(TAG, "Failed to set callback for face HAL", e);
            mDaemon = null;
        }

        Slog.d(TAG, "Face HAL ready, HAL ID: " + halId);
        if (halId != 0) {
            scheduleLoadAuthenticatorIds();
            scheduleInternalCleanup(ActivityManager.getCurrentUser(), null /* callback */);
            scheduleGetFeature(mSensorId, new Binder(),
                    ActivityManager.getCurrentUser(),
                    BiometricFaceConstants.FEATURE_REQUIRE_ATTENTION, null,
                    mContext.getOpPackageName());
        } else {
            Slog.e(TAG, "Unable to set callback");
            mDaemon = null;
        }

        return mDaemon;
    }

第35行后面,是涉及到人脸设别后的结果回调相关。后面会介绍,现在继续第16行。是跨进程获取BiometricsFace.cpp对象实例。然后继续其BiometricsFace#authenticate方法。

Return<Status> BiometricsFace::authenticate(uint64_t /* operationId */) {
    mClientCallback->onError(kDeviceId, mUserId, FaceError::HW_UNAVAILABLE, 0 /* vendorCode */);
    return Status::OK;
}

现在,基本上人脸注册部分讲完了,继续往下说下设别结果如何回传的

搜索下,前面的代码mDaemon.setCallback(mHalResultController)

mHalResultController这是一个回调接口对象,设别结果从他往上回传。

mHalResultController = new HalResultController(sensorProps.sensorId, context, mHandler,
                mScheduler, mLockoutTracker, lockoutResetDispatcher);
        

继续往下看这个类,实际上是个跨进程的回调接口对象

public static class HalResultController extends IBiometricsFaceClientCallback.Stub {
        /**
         * Interface to sends results to the HalResultController's owner.
         */
        public interface Callback {
            /**
             * Invoked when the HAL sends ERROR_HW_UNAVAILABLE.
             */
            void onHardwareUnavailable();
        }

        private final int mSensorId;
        @NonNull private final Context mContext;
        @NonNull private final Handler mHandler;
        @NonNull private final BiometricScheduler mScheduler;
        @Nullable private Callback mCallback;
        @NonNull private final LockoutHalImpl mLockoutTracker;
        @NonNull private final LockoutResetDispatcher mLockoutResetDispatcher;

        HalResultController(int sensorId, @NonNull Context context, @NonNull Handler handler,
                @NonNull BiometricScheduler scheduler, @NonNull LockoutHalImpl lockoutTracker,
                @NonNull LockoutResetDispatcher lockoutResetDispatcher) {
            mSensorId = sensorId;
            mContext = context;
            mHandler = handler;
            mScheduler = scheduler;
            mLockoutTracker = lockoutTracker;
            mLockoutResetDispatcher = lockoutResetDispatcher;
        }

        public void setCallback(@Nullable Callback callback) {
            mCallback = callback;
        }

        @Override
        public void onEnrollResult(long deviceId, int faceId, int userId, int remaining) {
            mHandler.post(() -> {
                final CharSequence name = FaceUtils.getLegacyInstance(mSensorId)
                        .getUniqueName(mContext, userId);
                final Face face = new Face(name, faceId, deviceId);

                final BaseClientMonitor client = mScheduler.getCurrentClient();
                if (!(client instanceof FaceEnrollClient)) {
                    Slog.e(TAG, "onEnrollResult for non-enroll client: "
                            + Utils.getClientName(client));
                    return;
                }

                final FaceEnrollClient enrollClient = (FaceEnrollClient) client;
                enrollClient.onEnrollResult(face, remaining);
            });
        }

        @Override
        public void onAuthenticated(long deviceId, int faceId, int userId,
                ArrayList<Byte> token) {
            mHandler.post(() -> {
                final BaseClientMonitor client = mScheduler.getCurrentClient();
                if (!(client instanceof AuthenticationConsumer)) {
                    Slog.e(TAG, "onAuthenticated for non-authentication consumer: "
                            + Utils.getClientName(client));
                    return;
                }

                final AuthenticationConsumer authenticationConsumer =
                        (AuthenticationConsumer) client;
                final boolean authenticated = faceId != 0;
                final Face face = new Face("", faceId, deviceId);
                authenticationConsumer.onAuthenticated(face, authenticated, token);
            });
        }

        @Override
        public void onAcquired(long deviceId, int userId, int acquiredInfo,
                int vendorCode) {
            mHandler.post(() -> {
                final BaseClientMonitor client = mScheduler.getCurrentClient();
                if (!(client instanceof AcquisitionClient)) {
                    Slog.e(TAG, "onAcquired for non-acquire client: "
                            + Utils.getClientName(client));
                    return;
                }

                final AcquisitionClient<?> acquisitionClient =
                        (AcquisitionClient<?>) client;
                acquisitionClient.onAcquired(acquiredInfo, vendorCode);
            });
        }

        @Override
        public void onError(long deviceId, int userId, int error, int vendorCode) {
            mHandler.post(() -> {
                final BaseClientMonitor client = mScheduler.getCurrentClient();
                Slog.d(TAG, "handleError"
                        + ", client: " + (client != null ? client.getOwnerString() : null)
                        + ", error: " + error
                        + ", vendorCode: " + vendorCode);
                if (!(client instanceof ErrorConsumer)) {
                    Slog.e(TAG, "onError for non-error consumer: " + Utils.getClientName(
                            client));
                    return;
                }

                final ErrorConsumer errorConsumer = (ErrorConsumer) client;
                errorConsumer.onError(error, vendorCode);

                if (error == BiometricConstants.BIOMETRIC_ERROR_HW_UNAVAILABLE) {
                    Slog.e(TAG, "Got ERROR_HW_UNAVAILABLE");
                    if (mCallback != null) {
                        mCallback.onHardwareUnavailable();
                    }
                }
            });
        }

        @Override
        public void onRemoved(long deviceId, ArrayList<Integer> removed, int userId) {
            mHandler.post(() -> {
                final BaseClientMonitor client = mScheduler.getCurrentClient();
                if (!(client instanceof RemovalConsumer)) {
                    Slog.e(TAG, "onRemoved for non-removal consumer: "
                            + Utils.getClientName(client));
                    return;
                }

                final RemovalConsumer removalConsumer = (RemovalConsumer) client;

                if (!removed.isEmpty()) {
                    // Convert to old fingerprint-like behavior, where remove() receives
                    // one removal at a time. This way, remove can share some more common code.
                    for (int i = 0; i < removed.size(); i++) {
                        final int id = removed.get(i);
                        final Face face = new Face("", id, deviceId);
                        final int remaining = removed.size() - i - 1;
                        Slog.d(TAG, "Removed, faceId: " + id + ", remaining: " + remaining);
                        removalConsumer.onRemoved(face, remaining);
                    }
                } else {
                    removalConsumer.onRemoved(null, 0 /* remaining */);
                }

                Settings.Secure.putIntForUser(mContext.getContentResolver(),
                        Settings.Secure.FACE_UNLOCK_RE_ENROLL, 0, UserHandle.USER_CURRENT);
            });
        }

        @Override
        public void onEnumerate(long deviceId, ArrayList<Integer> faceIds, int userId) {
            mHandler.post(() -> {
                final BaseClientMonitor client = mScheduler.getCurrentClient();
                if (!(client instanceof EnumerateConsumer)) {
                    Slog.e(TAG, "onEnumerate for non-enumerate consumer: "
                            + Utils.getClientName(client));
                    return;
                }

                final EnumerateConsumer enumerateConsumer = (EnumerateConsumer) client;

                if (!faceIds.isEmpty()) {
                    // Convert to old fingerprint-like behavior, where enumerate() receives one
                    // template at a time. This way, enumerate can share some more common code.
                    for (int i = 0; i < faceIds.size(); i++) {
                        final Face face = new Face("", faceIds.get(i), deviceId);
                        enumerateConsumer.onEnumerationResult(face, faceIds.size() - i - 1);
                    }
                } else {
                    // For face, the HIDL contract is to receive an empty list when there are no
                    // templates enrolled. Send a null identifier since we don't consume them
                    // anywhere, and send remaining == 0 so this code can be shared with Face@1.1
                    enumerateConsumer.onEnumerationResult(null /* identifier */, 0);
                }
            });
        }

        @Override
        public void onLockoutChanged(long duration) {
            mHandler.post(() -> {
                Slog.d(TAG, "onLockoutChanged: " + duration);
                final @LockoutTracker.LockoutMode int lockoutMode;
                if (duration == 0) {
                    lockoutMode = LockoutTracker.LOCKOUT_NONE;
                } else if (duration == -1 || duration == Long.MAX_VALUE) {
                    lockoutMode = LockoutTracker.LOCKOUT_PERMANENT;
                } else {
                    lockoutMode = LockoutTracker.LOCKOUT_TIMED;
                }

                mLockoutTracker.setCurrentUserLockoutMode(lockoutMode);

                if (duration == 0) {
                    mLockoutResetDispatcher.notifyLockoutResetCallbacks(mSensorId);
                }
            });
        }
    }

第55和69行,设别结果回传到这里。

搜索mScheduler.scheduleClientMonitor(client)。

最终能确定第69行的client就是FaceAuthenticationClient对象实例

然后调用其下的onAuthenticated方法,回传到这里

@Override
    public void onAuthenticated(BiometricAuthenticator.Identifier identifier,
            boolean authenticated, ArrayList<Byte> token) {
        super.onAuthenticated(identifier, authenticated, token);

        mState = STATE_STOPPED;
        mUsageStats.addEvent(new UsageStats.AuthenticationEvent(
                getStartTimeMs(),
                System.currentTimeMillis() - getStartTimeMs() /* latency */,
                authenticated,
                0 /* error */,
                0 /* vendorError */,
                getTargetUserId()));
    }

第4行,看其父类的方法

@Override
    public void onAuthenticated(BiometricAuthenticator.Identifier identifier,
            boolean authenticated, ArrayList<Byte> hardwareAuthToken) {
        super.logOnAuthenticated(getContext(), authenticated, mRequireConfirmation,
                getTargetUserId(), isBiometricPrompt());

        final ClientMonitorCallbackConverter listener = getListener();

        if (DEBUG) Slog.v(TAG, "onAuthenticated(" + authenticated + ")"
                + ", ID:" + identifier.getBiometricId()
                + ", Owner: " + getOwnerString()
                + ", isBP: " + isBiometricPrompt()
                + ", listener: " + listener
                + ", requireConfirmation: " + mRequireConfirmation
                + ", user: " + getTargetUserId()
                + ", clientMonitor: " + toString());

        final PerformanceTracker pm = PerformanceTracker.getInstanceForSensorId(getSensorId());
        if (isCryptoOperation()) {
            pm.incrementCryptoAuthForUser(getTargetUserId(), authenticated);
        } else {
            pm.incrementAuthForUser(getTargetUserId(), authenticated);
        }

        if (mAllowBackgroundAuthentication) {
            Slog.w(TAG, "Allowing background authentication,"
                    + " this is allowed only for platform or test invocations");
        }

        // Ensure authentication only succeeds if the client activity is on top.
        boolean isBackgroundAuth = false;
        if (!mAllowBackgroundAuthentication && authenticated
                && !Utils.isKeyguard(getContext(), getOwnerString())
                && !Utils.isSystem(getContext(), getOwnerString())) {
            final List<ActivityManager.RunningTaskInfo> tasks =
                    mActivityTaskManager.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: " + getOwnerString());
                        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!");
            }

            markAlreadyDone();

            if (mTaskStackListener != null) {
                mActivityTaskManager.unregisterTaskStackListener(mTaskStackListener);
            }

            final byte[] byteToken = new byte[hardwareAuthToken.size()];
            for (int i = 0; i < hardwareAuthToken.size(); i++) {
                byteToken[i] = hardwareAuthToken.get(i);
            }

            if (mIsStrongBiometric) {
                mBiometricManager.resetLockoutTimeBound(getToken(),
                        getContext().getOpPackageName(),
                        getSensorId(), getTargetUserId(), byteToken);
            }

            final CoexCoordinator coordinator = CoexCoordinator.getInstance();
            coordinator.onAuthenticationSucceeded(SystemClock.uptimeMillis(), this,
                    new CoexCoordinator.Callback() {
                @Override
                public void sendAuthenticationResult(boolean addAuthTokenIfStrong) {
                    if (addAuthTokenIfStrong && mIsStrongBiometric) {
                        final int result = KeyStore.getInstance().addAuthToken(byteToken);
                        Slog.d(TAG, "addAuthToken: " + result);
                    } else {
                        Slog.d(TAG, "Skipping addAuthToken");
                    }

                    if (listener != null) {
                        try {
                            // Explicitly have if/else here to make it super obvious in case the
                            // code is touched in the future.
                            if (!mIsRestricted) {
                                listener.onAuthenticationSucceeded(getSensorId(),
                                        identifier,
                                        byteToken,
                                        getTargetUserId(),
                                        mIsStrongBiometric);
                            } else {
                                listener.onAuthenticationSucceeded(getSensorId(),
                                        null /* identifier */,
                                        byteToken,
                                        getTargetUserId(),
                                        mIsStrongBiometric);
                            }
                        } catch (RemoteException e) {
                            Slog.e(TAG, "Unable to notify listener", e);
                        }
                    } else {
                        Slog.w(TAG, "Client not listening");
                    }
                }

                @Override
                public void sendHapticFeedback() {
                    if (listener != null && mShouldVibrate) {
                        vibrateSuccess();
                    }
                }

                @Override
                public void handleLifecycleAfterAuth() {
                    AuthenticationClient.this.handleLifecycleAfterAuth(true /* authenticated */);
                }

                @Override
                public void sendAuthenticationCanceled() {
                    sendCancelOnly(listener);
                }
            });
        } else {
            // Allow system-defined limit of number of attempts before giving up
            final @LockoutTracker.LockoutMode int lockoutMode =
                    handleFailedAttempt(getTargetUserId());
            if (lockoutMode != LockoutTracker.LOCKOUT_NONE) {
                markAlreadyDone();
            }

            final CoexCoordinator coordinator = CoexCoordinator.getInstance();
            coordinator.onAuthenticationRejected(SystemClock.uptimeMillis(), this, lockoutMode,
                    new CoexCoordinator.Callback() {
                @Override
                public void sendAuthenticationResult(boolean addAuthTokenIfStrong) {
                    if (listener != null) {
                        try {
                            listener.onAuthenticationFailed(getSensorId());
                        } catch (RemoteException e) {
                            Slog.e(TAG, "Unable to notify listener", e);
                        }
                    }
                }

                @Override
                public void sendHapticFeedback() {
                    if (listener != null && mShouldVibrate) {
                        vibrateError();
                    }
                }

                @Override
                public void handleLifecycleAfterAuth() {
                    AuthenticationClient.this.handleLifecycleAfterAuth(false /* authenticated */);
                }

                @Override
                public void sendAuthenticationCanceled() {
                    sendCancelOnly(listener);
                }
            });
        }
    }

然后回传到第109行,listener.onAuthenticationSucceeded

看看listener对象实例是什么

FaceService#authenticate方法中,即new ClientMonitorCallbackConverter(receiver)对象实例

所以,listener.onAuthenticationSucceeded调用的是ClientMonitorCallbackConverter#onAuthenticationSucceeded,代码如下

public class ClientMonitorCallbackConverter {
    private IBiometricSensorReceiver mSensorReceiver; // BiometricService
    private IFaceServiceReceiver mFaceServiceReceiver; // FaceManager
    private IFingerprintServiceReceiver mFingerprintServiceReceiver; // FingerprintManager

    public ClientMonitorCallbackConverter(IBiometricSensorReceiver sensorReceiver) {
        mSensorReceiver = sensorReceiver;
    }

    public ClientMonitorCallbackConverter(IFaceServiceReceiver faceServiceReceiver) {
        mFaceServiceReceiver = faceServiceReceiver;
    }

    public ClientMonitorCallbackConverter(IFingerprintServiceReceiver fingerprintServiceReceiver) {
        mFingerprintServiceReceiver = fingerprintServiceReceiver;
    }

    // The following apply to all clients

    void onAcquired(int sensorId, int acquiredInfo, int vendorCode) throws RemoteException {
        if (mSensorReceiver != null) {
            mSensorReceiver.onAcquired(sensorId, acquiredInfo, vendorCode);
        } else if (mFaceServiceReceiver != null) {
            mFaceServiceReceiver.onAcquired(acquiredInfo, vendorCode);
        } else if (mFingerprintServiceReceiver != null) {
            mFingerprintServiceReceiver.onAcquired(acquiredInfo, vendorCode);
        }
    }

    void onAuthenticationSucceeded(int sensorId, BiometricAuthenticator.Identifier identifier,
            byte[] token, int userId, boolean isStrongBiometric) throws RemoteException {
        if (mSensorReceiver != null) {
            mSensorReceiver.onAuthenticationSucceeded(sensorId, token);
        } else if (mFaceServiceReceiver != null) {
            mFaceServiceReceiver.onAuthenticationSucceeded((Face) identifier, userId,
                    isStrongBiometric);
        } else if (mFingerprintServiceReceiver != null) {
            mFingerprintServiceReceiver.onAuthenticationSucceeded((Fingerprint) identifier, userId,
                    isStrongBiometric);
        }
    }

    void onAuthenticationFailed(int sensorId) throws RemoteException {
        if (mSensorReceiver != null) {
            mSensorReceiver.onAuthenticationFailed(sensorId);
        } else if (mFaceServiceReceiver != null) {
            mFaceServiceReceiver.onAuthenticationFailed();
        } else if (mFingerprintServiceReceiver != null) {
            mFingerprintServiceReceiver.onAuthenticationFailed();
        }
    }

    public void onError(int sensorId, int cookie, int error, int vendorCode)
            throws RemoteException {
        if (mSensorReceiver != null) {
            mSensorReceiver.onError(sensorId, cookie, error, vendorCode);
        } else if (mFaceServiceReceiver != null) {
            mFaceServiceReceiver.onError(error, vendorCode);
        } else if (mFingerprintServiceReceiver != null) {
            mFingerprintServiceReceiver.onError(error, vendorCode);
        }
    }

    // The following only apply to IFingerprintServiceReceiver and IFaceServiceReceiver

    public void onDetected(int sensorId, int userId, boolean isStrongBiometric)
            throws RemoteException {
        if (mFaceServiceReceiver != null) {
            mFaceServiceReceiver.onFaceDetected(sensorId, userId, isStrongBiometric);
        } else if (mFingerprintServiceReceiver != null) {
            mFingerprintServiceReceiver.onFingerprintDetected(sensorId, userId, isStrongBiometric);
        }
    }

    void onEnrollResult(BiometricAuthenticator.Identifier identifier, int remaining)
            throws RemoteException {
        if (mFaceServiceReceiver != null) {
            mFaceServiceReceiver.onEnrollResult((Face) identifier, remaining);
        } else if (mFingerprintServiceReceiver != null) {
            mFingerprintServiceReceiver.onEnrollResult((Fingerprint) identifier, remaining);
        }
    }

    void onRemoved(BiometricAuthenticator.Identifier identifier, int remaining)
            throws RemoteException {
        if (mFaceServiceReceiver != null) {
            mFaceServiceReceiver.onRemoved((Face) identifier, remaining);
        } else if (mFingerprintServiceReceiver != null) {
            mFingerprintServiceReceiver.onRemoved((Fingerprint) identifier, remaining);
        }
    }

    /** Called when a challenged has been generated. */
    public void onChallengeGenerated(int sensorId, int userId, long challenge)
            throws RemoteException {
        if (mFaceServiceReceiver != null) {
            mFaceServiceReceiver.onChallengeGenerated(sensorId, userId, challenge);
        } else if (mFingerprintServiceReceiver != null) {
            mFingerprintServiceReceiver.onChallengeGenerated(sensorId, userId, challenge);
        }
    }

    public void onFeatureSet(boolean success, int feature) throws RemoteException {
        if (mFaceServiceReceiver != null) {
            mFaceServiceReceiver.onFeatureSet(success, feature);
        }
    }

    public void onFeatureGet(boolean success, int[] features, boolean[] featureState)
            throws RemoteException {
        if (mFaceServiceReceiver != null) {
            mFaceServiceReceiver.onFeatureGet(success, features, featureState);
        }
    }

    // Fingerprint-specific callbacks for FingerprintManager only

    public void onUdfpsPointerDown(int sensorId) throws RemoteException {
        if (mFingerprintServiceReceiver != null) {
            mFingerprintServiceReceiver.onUdfpsPointerDown(sensorId);
        }
    }

    public void onUdfpsPointerUp(int sensorId) throws RemoteException {
        if (mFingerprintServiceReceiver != null) {
            mFingerprintServiceReceiver.onUdfpsPointerUp(sensorId);
        }
    }

    // Face-specific callbacks for FaceManager only

    /**
     * Called each time a new frame is received during face authentication.
     *
     * @param frame Information about the current frame.
     *
     * @throws RemoteException If the binder call to {@link IFaceServiceReceiver} fails.
     */
    public void onAuthenticationFrame(@NonNull FaceAuthenticationFrame frame)
            throws RemoteException {
        if (mFaceServiceReceiver != null) {
            mFaceServiceReceiver.onAuthenticationFrame(frame);
        }
    }

    /**
     * Called each time a new frame is received during face enrollment.
     *
     * @param frame Information about the current frame.
     *
     * @throws RemoteException If the binder call to {@link IFaceServiceReceiver} fails.
     */
    public void onEnrollmentFrame(@NonNull FaceEnrollFrame frame) throws RemoteException {
        if (mFaceServiceReceiver != null) {
            mFaceServiceReceiver.onEnrollmentFrame(frame);
        }
    }
}

第35行的mFaceServiceReceiver 就是reveiever对象实例。其对应的源码在FaceManager.java下。

程序回调到第14行的onAuthenticationSucceeded

private final IFaceServiceReceiver mServiceReceiver = new IFaceServiceReceiver.Stub() {

        @Override // binder call
        public void onEnrollResult(Face face, int remaining) {
            mHandler.obtainMessage(MSG_ENROLL_RESULT, remaining, 0, face).sendToTarget();
        }

        @Override // binder call
        public void onAcquired(int acquireInfo, int vendorCode) {
            mHandler.obtainMessage(MSG_ACQUIRED, acquireInfo, vendorCode).sendToTarget();
        }

        @Override // binder call
        public void onAuthenticationSucceeded(Face face, int userId, boolean isStrongBiometric) {
            mHandler.obtainMessage(MSG_AUTHENTICATION_SUCCEEDED, userId,
                    isStrongBiometric ? 1 : 0, face).sendToTarget();
        }

        @Override // binder call
        public void onFaceDetected(int sensorId, int userId, boolean isStrongBiometric) {
            mHandler.obtainMessage(MSG_FACE_DETECTED, sensorId, userId, isStrongBiometric)
                    .sendToTarget();
        }

        @Override // binder call
        public void onAuthenticationFailed() {
            mHandler.obtainMessage(MSG_AUTHENTICATION_FAILED).sendToTarget();
        }

        @Override // binder call
        public void onError(int error, int vendorCode) {
            mHandler.obtainMessage(MSG_ERROR, error, vendorCode).sendToTarget();
        }

        @Override // binder call
        public void onRemoved(Face face, int remaining) {
            mHandler.obtainMessage(MSG_REMOVED, remaining, 0, face).sendToTarget();
        }

        @Override
        public void onFeatureSet(boolean success, int feature) {
            mHandler.obtainMessage(MSG_SET_FEATURE_COMPLETED, feature, 0, success).sendToTarget();
        }

        @Override
        public void onFeatureGet(boolean success, int[] features, boolean[] featureState) {
            SomeArgs args = SomeArgs.obtain();
            args.arg1 = success;
            args.arg2 = features;
            args.arg3 = featureState;
            mHandler.obtainMessage(MSG_GET_FEATURE_COMPLETED, args).sendToTarget();
        }

        @Override
        public void onChallengeGenerated(int sensorId, int userId, long challenge) {
            mHandler.obtainMessage(MSG_CHALLENGE_GENERATED, sensorId, userId, challenge)
                    .sendToTarget();
        }

        @Override
        public void onAuthenticationFrame(FaceAuthenticationFrame frame) {
            mHandler.obtainMessage(MSG_AUTHENTICATION_FRAME, frame).sendToTarget();
        }

        @Override
        public void onEnrollmentFrame(FaceEnrollFrame frame) {
            mHandler.obtainMessage(MSG_ENROLLMENT_FRAME, frame).sendToTarget();
        }
    }

第15行,通过mHandler跳转到

 case MSG_AUTHENTICATION_SUCCEEDED:
                    sendAuthenticatedSucceeded((Face) msg.obj, msg.arg1 /* userId */,
                            msg.arg2 == 1 /* isStrongBiometric */);
                    break;
private void sendAuthenticatedSucceeded(Face face, int userId, boolean isStrongBiometric) {
        if (mAuthenticationCallback != null) {
            final AuthenticationResult result =
                    new AuthenticationResult(mCryptoObject, face, userId, isStrongBiometric);
            mAuthenticationCallback.onAuthenticationSucceeded(result);
        }
    }

这里的mAuthenticationCallback就是文章开头部分有介绍,搜索

mAuthenticationCallback = callback

我们继续追踪callback变量,他实际上就是KeyguardUpdateMonitor#startListeningForFace方法中传入了这个对象。其对应的变量是mFaceAuthenticationCallback。

然后找到其对应的方法onAuthenticationSucceeded,即设别成功告知了上层用户。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

人脸注册,解锁,响应,一网打尽 的相关文章

  • JVM优化相关命令

    一 性能调优 性能调优包含多个层次 xff0c 比如 xff1a 架构调优 代码调优 JVM调优 数据库调优 操作系统调优等等 架构调优和代码调优是JVM调优的基础 xff0c 其中架构调优是对系统影响最大的 二 何时进行JVM调优 遇到以
  • Java多线程

    一 xff0c 单 CPU 如何同时运行多个程序 假设我们同时开了多个程序 xff1a Word xff0c IE xff0c QQ xff0c Winamp xff0c 对于操作系统来说 xff0c 这意味着 有四个进程要同时运行 为了解
  • BW性能监控利器——ST13总结

    题记 xff1a BW 的小工具 xff0c ST13 xff0c 近来每每使用 xff0c 都颇有感慨 xff0c 故总结如下 xff0c 以备后用 1 Process Chain xff1a ST13 gt BW TOOLS gt Pr
  • IO字节流与字符流

    一 xff0c 字节流 1 xff0c InputStream OutputStream 下所有字节流的父类 xff0c 也就是在装饰模式中扮演 武器 这个角色的类 所有输入字节流的父类是 InputStream xff0c 所有输出字节流
  • CentOS7.6镜像下载

    一 xff0c 直接下载 https mirrors aliyun com centos vault 7 6 1810 isos x86 64 二 xff0c 官网下载 https www centos org download
  • Nginx支持quic协议及gcc版本升级

    第一种方式 xff1a Nginx官方nginx quic搭建 通过部署Nginx官方的QUIC分支来实现的浏览器和nginx quic服务器粗略的HTTP3通信 1 下载BoringSSL BoringSSL 是由谷歌开发 从 OpenS
  • Nginx添加自定义HTTP头字段

    nginx代码配置 以下配置的是https类型的监听器 xff0c 添加了多个proxy set header nginx常用变量https zhuanlan zhihu com p 619398840 在后端server节点上使用tcpd
  • Mybatis 和 Mybatis Plus 的区别

    Mybatis Plus Mybatis Plus是一个Mybatis的增强工具 xff0c 只是在Mybatis的基础上做了增强却不做改变 xff0c MyBatis Plus支持所有Mybatis原生的特性 xff0c 所以引入Myba
  • linux下备份一个目录下所有文件及目录

    一 关于Linux备份文件和应用的几个命令 xff1a tar和cp 在工作中 xff0c 经常来备份文件和系统应用 xff0c 常用到的主要是tar和cp命令 xff0c 分别介绍如下 xff1a 一 tar命令 xff0c 这个现在经常
  • linux服务器IO性能诊断

    1 在 Linux 服务器排查问题时 xff0c 一般会通过 top vmstat free netstat df h 等命令排查 CPU 内存 网络和磁盘等问题 有的时候我们需要更进一步了解磁盘 I O 的使用情况 基本命令 xff1a
  • 记 Ubuntu 19.04更改ip地址

    前言 从ubuntu从17 10开始 xff0c 已经不再在 etc network interfaces里配置IP xff0c 即使配置了也不会生效 xff0c 而是改成netplan方式 xff0c 配置写在 etc netplan 文
  • 批量安装centos7服务器

    利用PXE自动化安装centos7 前言 PXE的功能及原理 大概解释一下意思就是 xff1a 启动计算机的时候如果没有插入U盘以及光驱等介质的话 xff0c boot启动项是有一个从PXE启动的选项 xff0c 如果都没有则会从pxe启动
  • KairosDB 1.13安装手记

    PS xff1a 为了处理监控数据 xff0c 我们需要一个时间序列数据库 xff0c OpenTSDB是前驱 xff0c 但是是基于Hbase实现的 xff0c 后来有了一个基于Cassandra的实现 xff0c 就是KairosDB
  • Ubuntu 18.04 配置网卡聚合绑定与桥接

    Ubuntu 18 04 配置网卡聚合绑定与桥接 单网卡配置ip和多网卡配置ip 在之前的博客已经写过了 xff0c 这里写一下进阶的一些配置吧 Ubuntu 配置ip博客 xff1a https blog csdn net liuhaoy
  • ansible playbook 检查文件是否存在

    register 在ansible的playbook中task之间的相互传递变量 当我们需要判断对执行了某个操作或者某个命令后 xff0c 如何做相应的响应处理 xff08 执行其他 ansible 语句 xff09 xff0c 则一般会用
  • Ubuntu打开虚拟机报错could not open /dev/vmmon:?????? please make sure that the kernel moduel vmmon is load

    首先查看模块是否启动 etc init d vmware status 如果未启动使用start启动 etc init d vmware start 检查模块是否启动成功 etc init d vmware status 如果是网卡那一项f
  • linux 通过ip add 配置GRE隧道

    配置两台主机的 lo地址 xff0c 用来测试用 xff0c 如果不做gre的话 xff0c 互相是ping不同对方的回环地址的 注意环境是 主机1的ip xff1a 192 168 1 1 lo地址 xff1a 1 1 1 1 主机2的i
  • ansible-playbook debug输出区别与用法

    ansible playbook debug中var输出和msg输出的区别 msg xff1a 调试输出的消息 var xff1a 将某个任务执行的输出作为变量传递给debug模块 使用var的时候 xff0c 引用变量无需加上大括号 使用
  • 利用PXE批量进入救援模式修复多台主机的boot分区

    利用PXE自动化安装centos7 前言 PXE的功能及原理 大概解释一下意思就是 xff1a 启动计算机的时候如果没有插入U盘以及光驱等介质的话 xff0c boot启动项是有一个从PXE启动的选项 xff0c 如果都没有则会从pxe启动
  • shell 脚本 自动把登录失败次数超过5次的丢入iptables

    span class token shebang important bin bash span ip span class token operator 61 span span class token variable span cla

随机推荐

  • VMware 磁盘管理 虚拟机版本降级

    VMware降级在之前的文章里 卸载vmware 15版本虚拟机 xff0c 安装vmware14 最近在做虚拟机重命名 43 磁盘文件重命名 xff0c 在这里碰到了几次棘手的问题 VMware 虚拟机版本降级 如果你拿过来就是一个高版本
  • nginx 反向代理 解析域名变成ipv6

    今天碰到一个问题 xff0c 反向代理的域名解析成ipv6了 xff0c 然后主机不通ipv6 xff0c 就导致有时候能访问有时候链接超时的诡异情况 解决方法1 xff1a 通过关闭主机的ipv6来实现 图解centos7如何关闭ipv6
  • STANet

    A Spatial Temporal Attention Based Method and a New Dataset for Remote Sensing Image Change Detection 摘要 进行遥感图像变化检测 xff0
  • 传统行业的IT如何转向DEVOPS,运维如何转向SRE

    题记 xff1a 在菊厂这几年 xff0c 亲历了传统行业的IT部门如何在数字化转型的滚滚洪流中 xff0c 被裹挟着四处寻找光明 从15年至今 xff0c 参加了各式各样的培训 xff0c 最早是CI CD xff0c 后来推DEVOPS
  • 《Invertible Denoising Network: A Light Solution for Real Noise Removal 》论文阅读

    摘要 可逆网络在图像去噪方面有各种各样的好处 xff0c 因为它们是轻量级的 xff0c 信息无损的 xff0c 并且在反向传播过程中节省内存 然而 xff0c 应用可逆模型去噪具有挑战性 xff0c 因为输入是有噪声的 xff0c 而反向
  • IEEE Geoscience and Remote Sensing Letters (GRSL)从投稿到录用过程分享

    时间进度条 一定是你最想最快看到的 2021 5 14 投稿分配文章编号2021 6 16 返回修改意见2021 7 01 提交修订版论文 2021 7 28 收到录用通知2021 7 30 提交最终版论文 2021 8 07 校稿 202
  • C语言实例3——输入某年某月某日,判断这一天是这一年的第几天?

    题目 xff1a 输入某年某月某日 xff0c 判断这一天是这一年的第几天 xff1f 程序分析 xff1a 以3月5日为例 xff0c 应该先把前两个月的加起来 xff0c 然后再加上5天即本年的第几天 xff0c 特殊情况 xff0c
  • 顺序栈——9种基本操作和实现(C语言)

    栈是仅限定在表尾进行插入和删除操作的线性表 xff0c 九种栈的基本操作 xff1b 分别是构造 销毁 清空 栈长 栈顶 插入 删除 遍历 下面就是代码实现 xff1a 头文件 include lt stdio h gt include l
  • C语言实例12——输入两个正整数m和n,求其最大公约数和最小公倍数。

    题目 xff1a 输入两个正整数m和n xff0c 求其最大公约数和最小公倍数 程序分析 xff1a 利用辗除法 include lt stdio h gt int main int a b num1 num2 temp printf 34
  • 《数据库原理》— 数据库系统概论第五版习题解析

    数据库系统概论前七章习题解析 第1章 绪论 1 xff0e 试述数据 数据库 数据库系统 数据库管理系统的概念 答 xff1a l xff09 数据 xff08 Data xff1a 描述事物的符号记录称为数据 数据的种类有数字 文字 图形
  • 《图形图像处理》— 使用matlab对图像进行二值化和灰度化处理

    用matlab对图像进行二值化处理 gt gt m 61 imread 39 d image logo jpg 39 gt gt imshow n gt gt n 61 graythresh data gt gt im2bw m n 用ma
  • 线程池的使用

    一 简述 在开发中 xff0c 频繁的创建和销毁一个线程 xff0c 是极耗资源的 xff0c 为此创建一个可重用指定线程数的线程池 xff0c 以共享的无界队列方式来运行这些线程 xff0c 可以有效的规划线程的使用 线程池顾名思义 xf
  • Android Biometricprompt 生物识别(指纹)

    从API 29 即 Android 10 开始 xff0c 系统为第三方应用提供了通过生物识别验证用户是否为本人的功能 xff0c 实现也比较简单 因为在进行生物识别时 xff0c 系统会禁止本应用截图 xff0c 录屏也会是黑屏 xff0
  • 生物识别概况

    借助生物识别因素 xff0c 可在 Android 平台上实现安全的身份验证 Android 框架包含人脸和指纹生物识别身份验证方式 您可对 Android 进行自定义以支持其他形式的生物识别身份验证方式 xff08 例如虹膜 xff09
  • 初识Flink(1)-- 关于Flink的架构

    PS xff1a 我是半吊子Storm从业者 xff0c Storm是我司流计算平台很早就在使用的技术 xff0c 一直没有深入了解 xff0c 当时Flink已经出具规模 xff0c 但是鉴于一个Storm还没搞好 xff0c 再弄个Fl
  • Android生物识别 指纹识别面部识别,生物认证Biometric的简单使用

    Android生物识别 指纹识别面部识别 xff0c 生物认证Biometric的简单使用 生物认证Biometric 很多APP都要求免登录 xff0c 银行什么的 xff0c 要求指纹登录 xff0c 密码登录 xff0c 再不就是手势
  • Android 人脸解锁源码剖析

    一 人脸识别身份验证HIDL 借助人脸识别身份验证功能 xff0c 用户只需要将自己的面孔对准设备即可将其解锁 Android 10 增加了对一种新的人脸识别身份验证堆栈的支持 xff0c 这种堆栈可安全处理摄像头帧 xff0c 从而在支持
  • Android Q 上的Biometric生物识别之Fingerprint指纹识别流程

    第一部分 Fingerprint HIDL 在配有指纹传感器的设备上 xff0c 用户可以注册一个或多个指纹 xff0c 并使用这些指纹来解锁设备以及执行其他任务 Android 会利用 Fingerprint HIDL xff08 硬件接
  • 全面解析Android系统指纹启动流程

    一 从Android系统启动流程看指纹启动流程 第一阶段 Boot ROM xff0c Android设备上电后 xff0c 首先会从处理器片上ROM的启动引导代码开始执行 xff0c 片上ROM会寻找Bootloader代码 xff0c
  • 人脸注册,解锁,响应,一网打尽

    现代智能手机 xff0c 基本上都有人脸解锁功能 xff0c 那他是怎么实现的哦 下面从代码角度来分析下他 先上流程图 略 人脸解锁 xff0c 都要先录入 xff08 这部分后面会出其他博客 xff09 xff0c 再注册 xff0c 再