R版本 Google对生物识别弹框 BiometricPrompt的逻辑进行了重构,尤其是框架服务和systemui部分。本文从systemui角度来了解下相比于Q版本的变化,其中会涉及部分的框架的调整。
一. 概述
下图为systemui的类图(取自博客:https://blog.csdn.net/u013398960/article/details/105630411),侵删。
更直观的代码结构目录如下:
可以对比下Q版本上的区别:https://blog.nowcoder.net/n/da445eff609041ab957221ddb08f00bb
整体逻辑可概述为如下内容:
- com.android.systemui.biometrics.AuthController 作为总的控制类,接收框架服务的回调,并提供与服务交互的接口。
- com.android.systemui.biometrics.AuthContainerView 作为整个布局的容器,与AuthController交互并拉起其它的子布局。整个view相关的逻辑多在其中。
- com.android.systemui.biometrics.AuthPanelController 控制整体弹框背景部分的视图。
- com.android.systemui.biometrics.AuthCredentialView 新增非生物识别类型的view,如图案密码和文本密码。此部分是完全新增的内容,需重点介绍。
二. 布局
整体为覆盖整个窗口的底层布局:AuthContainerView;控制背景的PanelView。 两者不论是在生物识别(指纹、人脸)还是非生物识别(图案密码、文本密码)等类型中均存在且逻辑相同。
2.1 生物识别布局
对于生物识别类型BiometricView,又专门封装了一个ScrollView来装载后再指纹或人脸的具体元素。
2.1.1 结构视图
2.1.2 代码实现
- 在AuthContainerView构造时先inflate 具体的BiometricView
// Inflate biometric view only if necessary.
if (Utils.isBiometricAllowed(mConfig.mBiometricPromptBundle)) {
if (config.mModalityMask == BiometricAuthenticator.TYPE_FINGERPRINT) {
mBiometricView = (AuthBiometricFingerprintView)
factory.inflate(R.layout.auth_biometric_fingerprint_view, null, false);
} else if (config.mModalityMask == BiometricAuthenticator.TYPE_FACE) {
mBiometricView = (AuthBiometricFaceView)
factory.inflate(R.layout.auth_biometric_face_view, null, false);
} else {
Log.e(TAG, "Unsupported biometric modality: " + config.mModalityMask);
mBiometricView = null;
mBackgroundView = null;
mBiometricScrollView = null;
return;
} - 在onAttachedToWindow 的时候addview
private void addBiometricView() {
mBiometricView.setRequireConfirmation(mConfig.mRequireConfirmation);
mBiometricView.setPanelController(mPanelController);
mBiometricView.setBiometricPromptBundle(mConfig.mBiometricPromptBundle);
mBiometricView.setCallback(mBiometricCallback);
mBiometricView.setBackgroundView(mBackgroundView);
mBiometricView.setUserId(mConfig.mUserId);
mBiometricView.setEffectiveUserId(mEffectiveUserId);
mBiometricScrollView.addView(mBiometricView);
} 2.2 密码认证
密码部分相对简单,满足条件时会在AuthContainerView onAttachedToWindow的时候inflate,并直接add进AuthContainerView。
private void addCredentialView(boolean animatePanel, boolean animateContents) {
final LayoutInflater factory = LayoutInflater.from(mContext);
final @Utils.CredentialType int credentialType = mInjector.getCredentialType(
mContext, mEffectiveUserId);
switch (credentialType) {
case Utils.CREDENTIAL_PATTERN:
mCredentialView = (AuthCredentialView) factory.inflate(
R.layout.auth_credential_pattern_view, null, false);
break;
case Utils.CREDENTIAL_PIN:
case Utils.CREDENTIAL_PASSWORD:
mCredentialView = (AuthCredentialView) factory.inflate(
R.layout.auth_credential_password_view, null, false);
break;
default:
throw new IllegalStateException("Unknown credential type: " + credentialType);
}
// The background is used for detecting taps / cancelling authentication. Since the
// credential view is full-screen and should not be canceled from background taps,
// disable it.
mBackgroundView.setOnClickListener(null);
mBackgroundView.setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_NO);
mCredentialView.setContainerView(this);
mCredentialView.setUserId(mConfig.mUserId);
mCredentialView.setOperationId(mConfig.mOperationId);
mCredentialView.setEffectiveUserId(mEffectiveUserId);
mCredentialView.setCredentialType(credentialType);
mCredentialView.setCallback(mCredentialCallback);
mCredentialView.setBiometricPromptBundle(mConfig.mBiometricPromptBundle);
mCredentialView.setPanelController(mPanelController, animatePanel);
mCredentialView.setShouldAnimateContents(animateContents);
mFrameLayout.addView(mCredentialView);
} view内部的具体内容不再展开。
三. 密码认证逻辑
R版本与Q最大的不同就是R上面systemui集成进了密码认证的部分。 在Q版本上生物识别弹框功能也能使用密码校验的功能,但是由BiometricService去拉的settings的密码模块,但凡涉及密码校验的部分都与systemui无关。 但在R版本上原生将密码校验的功能和view全部集成进了systemui,不再有设置模块的参与。
3.1 密码类型
密码共有4位/6位数字密码、图案密码、复杂密码、pin码等多种类型,从设置密码录入页和锁屏密码中就可以看出。 BiometricPrompt中对密码类型进行了整合:将数字密码、复杂密码等类型统一划分为文本密码类型,与图案密码共同作为使用的两种密码类型。 个人理解:设置密码和锁屏密码属于两个大模块,而生物弹框的密码部分只是一个小组件,没有必要做成和这两个模块一样大规模的逻辑和复杂的view体系。
3.2 文本密码
AuthCredentialPasswordView 本身比较简单,主要是通过输入域来和用户交互。 其输入文本的控件为ImeAwareEditText类型,在onEditorAction回调中,当判断满足条件后会将密码输入底层进行校验:com.android.systemui.biometrics.AuthCredentialPasswordView#checkPasswordAndUnlock。 和锁屏密码、设置密码一样,会有一个回调onCredentialVerified来接校验的结果。
@Override
protected void onCredentialVerified(byte[] attestation, int timeoutMs) {
super.onCredentialVerified(attestation, timeoutMs);
final boolean matched = attestation != null;
if (matched) {
mImm.hideSoftInputFromWindow(getWindowToken(), 0 /* flags */);
} else {
mPasswordField.setText("");
}
} 从上面代码可以看出,对于校验结果AuthCredentialPasswordView本身只处理部分和view相关的内容,其他具体的逻辑交由父类AuthCredentialView执行。
3.3 图案密码
AuthCredentialPatternView 和文本密码逻辑一样,只不过和用户交互的控件变成了LockPatternView类型。
3.4 密码验证处理流程
protected void onCredentialVerified(byte[] attestation, int timeoutMs) {
final boolean matched = attestation != null;
if (matched) {
mClearErrorRunnable.run();
mLockPatternUtils.userPresent(mEffectiveUserId);
**mCallback.onCredentialMatched(attestation); //重点内容**
} else {
if (timeoutMs > 0) {
mHandler.removeCallbacks(mClearErrorRunnable);
long deadline = mLockPatternUtils.setLockoutAttemptDeadline(
mEffectiveUserId, timeoutMs);
mErrorTimer = new ErrorTimer(mContext,
deadline - SystemClock.elapsedRealtime(),
LockPatternUtils.FAILED_ATTEMPT_COUNTDOWN_INTERVAL_MS,
mErrorView) {
@Override
public void onFinish() {
onErrorTimeoutFinish();
mClearErrorRunnable.run();
}
};
mErrorTimer.start();
} else {
final boolean didUpdateErrorText = reportFailedAttempt();
if (!didUpdateErrorText) {
final @StringRes int errorRes;
switch (mCredentialType) {
case Utils.CREDENTIAL_PIN:
errorRes = R.string.biometric_dialog_wrong_pin;
break;
case Utils.CREDENTIAL_PATTERN:
errorRes = R.string.biometric_dialog_wrong_pattern;
break;
case Utils.CREDENTIAL_PASSWORD:
default:
errorRes = R.string.biometric_dialog_wrong_password;
break;
}
showError(getResources().getString(errorRes));
}
}
}
} 整体分为两个方向:当校验成功时会通过onCredentialMatched接口来和服务通信;当校验失败的时候只是会对view本身做出调整。下面看一下onCredentialMatched的传递流程。
final class CredentialCallback implements AuthCredentialView.Callback {
@Override
public void onCredentialMatched(byte[] attestation) {
mCredentialAttestation = attestation;
**animateAway(AuthDialogCallback.DISMISSED_CREDENTIAL_AUTHENTICATED);**
}
} com.android.systemui.biometrics.AuthContainerView.CredentialCallback AuthContainerView的内部类实现该接口,从而调用到animateAway方法。值得注意的是,不论是密码识别和生物识别,与服务交互的路径均需要通过animateAway方法。animateAway - removeWindowIfAttached - sendPendingCallbackIfNotNull.
private void sendPendingCallbackIfNotNull() {
Log.d(TAG, "pendingCallback: " + mPendingCallbackReason
+ " sysUISessionId: " + mConfig.mSysUiSessionId);
if (mPendingCallbackReason != null) {
**mConfig.mCallback.onDismissed(mPendingCallbackReason, mCredentialAttestation);**
mPendingCallbackReason = null;
}
} 最终会走到onDismissed方法,而这就是与服务交互的最终入口。
@Override
public void onDismissed(@DismissedReason int reason, @Nullable byte[] credentialAttestation) {
switch (reason) {
case AuthDialogCallback.DISMISSED_USER_CANCELED:
sendResultAndCleanUp(BiometricPrompt.DISMISSED_REASON_USER_CANCEL,
credentialAttestation);
break;
case AuthDialogCallback.DISMISSED_BUTTON_NEGATIVE:
sendResultAndCleanUp(BiometricPrompt.DISMISSED_REASON_NEGATIVE,
credentialAttestation);
break;
case AuthDialogCallback.DISMISSED_BUTTON_POSITIVE:
sendResultAndCleanUp(BiometricPrompt.DISMISSED_REASON_BIOMETRIC_CONFIRMED,
credentialAttestation);
break;
case AuthDialogCallback.DISMISSED_BIOMETRIC_AUTHENTICATED:
sendResultAndCleanUp(
BiometricPrompt.DISMISSED_REASON_BIOMETRIC_CONFIRM_NOT_REQUIRED,
credentialAttestation);
break;
case AuthDialogCallback.DISMISSED_ERROR:
sendResultAndCleanUp(BiometricPrompt.DISMISSED_REASON_ERROR,
credentialAttestation);
break;
case AuthDialogCallback.DISMISSED_BY_SYSTEM_SERVER:
sendResultAndCleanUp(BiometricPrompt.DISMISSED_REASON_SERVER_REQUESTED,
credentialAttestation);
break;
case AuthDialogCallback.DISMISSED_CREDENTIAL_AUTHENTICATED:
sendResultAndCleanUp(BiometricPrompt.DISMISSED_REASON_CREDENTIAL_CONFIRMED,
credentialAttestation);
break;
default:
Log.e(TAG, "Unhandled reason: " + reason);
break;
}
} 上述分别定义了校验成功、失败等各种情况。在服务的com.android.server.biometrics.BiometricService#handleOnDismissed方法中会有一一对应的处理操作。 上层应用的对应回调结果也是在这里触发。
private void handleOnDismissed(int reason, @Nullable byte[] credentialAttestation) {
if (mCurrentAuthSession == null) {
Slog.e(TAG, "onDismissed: " + reason + ", auth session null");
return;
}
logDialogDismissed(reason);
try {
switch (reason) {
case BiometricPrompt.DISMISSED_REASON_CREDENTIAL_CONFIRMED:
if (credentialAttestation != null) {
mKeyStore.addAuthToken(credentialAttestation);
} else {
Slog.e(TAG, "Credential confirmed but attestation is null");
}
case BiometricPrompt.DISMISSED_REASON_BIOMETRIC_CONFIRMED:
case BiometricPrompt.DISMISSED_REASON_BIOMETRIC_CONFIRM_NOT_REQUIRED:
if (mCurrentAuthSession.mTokenEscrow != null) {
mKeyStore.addAuthToken(mCurrentAuthSession.mTokenEscrow);
} else {
Slog.e(TAG, "mTokenEscrow is null");
}
mCurrentAuthSession.mClientReceiver.onAuthenticationSucceeded(
Utils.getAuthenticationTypeForResult(reason));
break;
case BiometricPrompt.DISMISSED_REASON_NEGATIVE:
mCurrentAuthSession.mClientReceiver.onDialogDismissed(reason);
// Cancel authentication. Skip the token/package check since we are cancelling
// from system server. The interface is permission protected so this is fine.
cancelInternal(null /* token */, null /* package */,
mCurrentAuthSession.mCallingUid, mCurrentAuthSession.mCallingPid,
mCurrentAuthSession.mCallingUserId, false /* fromClient */);
break;
case BiometricPrompt.DISMISSED_REASON_USER_CANCEL:
mCurrentAuthSession.mClientReceiver.onError(
mCurrentAuthSession.mModality,
BiometricConstants.BIOMETRIC_ERROR_USER_CANCELED,
0 /* vendorCode */
);
// Cancel authentication. Skip the token/package check since we are cancelling
// from system server. The interface is permission protected so this is fine.
cancelInternal(null /* token */, null /* package */, Binder.getCallingUid(),
Binder.getCallingPid(), UserHandle.getCallingUserId(),
false /* fromClient */);
break;
case BiometricPrompt.DISMISSED_REASON_SERVER_REQUESTED:
case BiometricPrompt.DISMISSED_REASON_ERROR:
mCurrentAuthSession.mClientReceiver.onError(
mCurrentAuthSession.mModality,
mCurrentAuthSession.mErrorEscrow,
mCurrentAuthSession.mVendorCodeEscrow
);
break;
default:
Slog.w(TAG, "Unhandled reason: " + reason);
break;
}
// Dialog is gone, auth session is done.
mCurrentAuthSession = null;
} catch (RemoteException e) {
Slog.e(TAG, "Remote exception", e);
}
} 三. 总结
- R版本BiometricPrompt的服务和systemui模块的变化很大,主要是集成了密码校验的模块和重构了两者的通信逻辑。
- 生物校验的流程和Q版本整体上保持一致:由上层应用调用方自行注册指纹/人脸服务 ,systemui只提供对应的view界面。并且在某些情况下通知服务取消注册。
- 密码校验的发起和回调接受均在systmeui完成。

京公网安备 11010502036488号