人脸识别大致流程
应用层Settings进行人脸录入,SystemUI进行人脸识别等操作时,会调用FaceMnager的相关接口,进而调用到FaceService和FaceProvider,具体业务由对应的Face***Client(如FaceEnrollClient,FaceAuthenticationClient等)调用Hal接口实现,hal层会通过Sensor.HalSessionCallback(extends ISessionCallback.Stub)把结果回调给Face***Client,最终回调到应用层。
人脸录入
在系统设置Settings或是开机向导,会有入口跳转到人脸识别管理页面。若此时尚未设置锁屏密码,则会要求添加密码;若已设置锁屏密码,则会要求校验密码。密码验证通过后,如果此时没有已录入的人脸模板或是从开机向导那边过来的,会跳转到人脸录入页面,否则停留在当前页面。
人脸录入 Token 生成
若尚未设置锁屏密码,token会在成功设置锁屏密码后生成;若已设置锁屏密码,token会在成功验证密码后生成。两者的token生成流程类似,下面流程以在设置锁屏密码后生成token为例
人脸录入流程
人脸录入时,会调用到FaceManager的enroll方法,通过跨进程通信,调用到FaceService的enroll方法,目前高安卓版本是通过aidl的方式与hal层进行通信,接下来的流程主要涉及到frameworks/base/services/core/java/com/android/server/biometrics/sensors/face/aidl/ 这个目录里面的代码,会调用到FaceProvider里面的scheduleEnroll方法,在FaceEnrollClient里面打开相机,获取数据后传给hal层,最终通过一系列的回调把结果返回给应用层。
人脸识别管理
在人脸识别管理页,会显示已录入人脸名称
FaceUtils初始化时,会根据 sensorId 拼装一个文件名 fileName = "settings_face_" + sensorId + ".xml";初始化FaceUserState时,会从 fileName 文件里面读取人脸数据,人脸录入成功后也会往里面更新数据。路径类似于data/system/users/0/settings_face_4.xml。
人脸重命名
点击人脸数据item,可更改人脸名称。
renameBiometric方法会根据 mBiometricId 查找匹配的 Face 数据,重置 Name 字段,把数据更新到data/system/users/0/settings_face_4.xml 里面
人脸删除
删除单个人脸
点击人脸数据item删除按钮,可删除单个人脸数据。
FaceRemovalClient 在执行 onRemoved 方法的过程中,会根据 faceId 删除 data/system/users/0/settings_face_4.xml 里面对应的人脸数据
删除所有人脸
移除锁屏密码时,会删除所有已录入的人脸
人脸补光
当打开“暗光环境下使用屏幕补光”选项,在锁屏、应用加密等页面使用人脸识别认证时,会根据当前环境亮度去决定是否要进行人脸补光,具体时机是在打开相机前进行监听,在关闭相机前解除监听。
注视不灭屏
开启“注释时不灭屏”选项,超时灭屏倒计时 2200毫秒时,PowerManagerService 会向某一常驻应用发送广播 com.face.pre.dim,该应用处理完一些自身逻辑后,会向 FaceService 发送广播 com.face.detect,face_state = FACE_OPEN, 开始检测人脸,若检测到人脸注视,则会取消人脸检测,重置灭屏倒计时。若超时灭屏时间只有350毫秒还没检测到人脸,PowerManagerService 会向该应用发送广播 com.face.dim.state.changed,该应用处理完自身逻辑后,会向FaceService 发送广播 com.face.detect,face_state = FACE_CLOSE,取消人脸检测。
解锁
加密应用/文档锁定区/图库加密相册解锁
打开应用加密解锁、文档锁定区解锁、图库加密相册解锁选项时,跳转到相应加密页面时,会增加人脸识别验证方式,对应的验证页面为ConfirmPasswordFragment
首先,会判断是否有录入人脸,从哪些页面跳转过来,并且是否有打开对应解锁类型的选项开关,来确定满不满足使用人脸识别解锁的条件。
private void initFace() {
boolean hasEnrolled = mFaceManager.hasEnrolledTemplates(UserHandle.myUserId());
boolean fromAppLock = mConfirmPasswordFrom == LockPasswordUtils.PASSWORD_FROM_APP_LOCK;
boolean fromAppLockManager = mConfirmPasswordFrom == LockPasswordUtils.PASSWORD_FROM_APP_LOCK_MANAGER;
boolean fromDocument = mConfirmPasswordFrom == LockPasswordUtils.PASSWORD_FROM_DOCUMENT;
boolean fromGalleryLock = mConfirmPasswordFrom == LockPasswordUtils.PASSWORD_FROM_GALLERY_LOCK;
boolean fromNotes = mConfirmPasswordFrom == LockPasswordUtils.PASSWORD_FROM_NOTES;
boolean fromSafe = mConfirmPasswordFrom == LockPasswordUtils.PASSWORD_FROM_SAFE;
boolean isFromEnabled = ((fromAppLock || fromAppLockManager) && FaceUtil.isEnableForAppLock(getContext()))
|| (fromDocument && FaceUtil.isEnableForDocumentLock(getContext()))
|| (fromGalleryLock && FaceUtil.isEnableForGalleryLock(getContext())
|| (fromNotes && FaceUtil.isEnableForNotesLock(getContext()))
|| fromSafe);
if (hasEnrolled && isFromEnabled) {
mIsFaceRecognitionEnable = true;
mIFaceRecognition = new FaceRecognitionImpl(getContext(), mHandler);
} else {
mIsFaceRecognitionEnable = false;
}
}
当满足上述条件时,如果当前人脸识别处于冻结状态,会显示冻结动画,否则进入人脸识别认证流程。
屏幕解锁
屏幕解锁和加密应用解锁等在框架层面的流程类似,在应用层的交互有差异
冻结
多次错误人脸识别导致人脸冻结
屏幕解锁和加密应用解锁等页面,累计识别到75帧错误人脸后,底层会冻结人脸,并给上层发起回调。
指纹冻结导致人脸冻结
指纹是强类型生物识别,人脸是弱类型生物识别。强类型的生物识别被冻结时,弱类型的生物识别也会紧跟着被冻结(此时人脸在上层冻结,底层未冻结)
frameworks/base/services/core/java/com/android/server/biometrics/sensors/MultiBiometricLockoutState.java
void setTimedLockout(int userId, @Authenticators.Types int strength) {
final Map<Integer, AuthenticatorState> authMap = getAuthMapForUser(userId);
switch (strength) {
case Authenticators.BIOMETRIC_STRONG:
authMap.get(BIOMETRIC_STRONG).mTimedLockout = true;
// fall through
case Authenticators.BIOMETRIC_WEAK:
authMap.get(BIOMETRIC_WEAK).mTimedLockout = true;
// fall through
case Authenticators.BIOMETRIC_CONVENIENCE:
authMap.get(BIOMETRIC_CONVENIENCE).mTimedLockout = true;
return;
default:
Slog.e(TAG, "increaseLockoutTime called for invalid strength : " + strength);
}
}
按电源键亮屏,锁屏页面不会判断当前人脸是否冻结,会直接进行人脸识别认证,在框架层FaceAuthenticationClient调用start方法时会判断是否为冻结状态,如果是,则直接给抛出冻结回调,然后直接return,不会去打开相机和传数据给底层
frameworks/base/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClient.java
@Override
public void start(@NonNull ClientMonitorCallback callback) {
super.start(callback);
openCameraIfNecessary();
mState = STATE_STARTED;
}
private void openCameraIfNecessary() {
final int lockoutMode;
LockoutTracker lockoutTracker = getLockoutTracker();
if (lockoutTracker != null) {
lockoutMode = lockoutTracker.getLockoutModeForUser(getTargetUserId());
} else {
lockoutMode = getBiometricContext().getAuthSessionCoordinator()
.getLockoutStateFor(getTargetUserId(), getSensorStrength());
}
if (lockoutMode != LockoutTracker.LOCKOUT_NONE) {
Slog.v(TAG, "In lockout mode(" + lockoutMode + ") ; disallowing open camera");
return;
}
if (!SystemProperties.getBoolean(CTS_PROPERTY, false)) {
openCamera();
}
}
frameworks/base/services/core/java/com/android/server/biometrics/sensors/AuthenticationClient.java
@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();
}
加密应用解锁等页面,会先判断当前人脸是否冻结,如果不是冻结状态,才会进行人脸识别认证。
解冻结
超时自动解除人脸冻结
60秒超时,底层会自动解除人脸冻结,并回调给上层