地点:FingerprintEnrollFindSensor.java //Android P
事件:通过指纹切到录入界面 FingerprintEnrollEnrolling.java
对比于Android N 这里不再支持NEXT按钮切到右边的界面。
那么指纹是如何切到右边的录入界面的呢?
进入左边界面在我们设置过强认证解锁的方式之后,就会获得非空的token(byte[]),直接调用 startLookingForFingerprint()
如果没有设置过强认证方式,则launch对象的设置界面要求设置,设置完成后,回到左边界面,会调用 startLookingForFingerprint()。
starLookingForFingerprint()的调用将让指纹开始工作。
private void startLookingForFingerprint() {
mSidecar = (FingerprintEnrollSidecar) getFragmentManager().findFragmentByTag(
FingerprintEnrollEnrolling.TAG_SIDECAR);
if (mSidecar == null) {
mSidecar = new FingerprintEnrollSidecar();
getFragmentManager().beginTransaction()
.add(mSidecar, FingerprintEnrollEnrolling.TAG_SIDECAR).commit();
}
//经过上面的逻辑,FingerprintEnrollSidecar这个fragment生命周期就开始了,接着导致enroll
//(token,....)被调用。指纹开始工作
mSidecar.setListener(new Listener() {//这是匿名内部类的方式实现接口方法,其实还有其他实现方式如在外部类中实现Listener这个接口
@Override
public void onEnrollmentProgressChange(int steps, int remaining) {
mNextClicked = true;
proceedToEnrolling(true /* cancelEnrollment */);
}
@Override
public void onEnrollmentHelp(CharSequence helpString) {
}
@Override
public void onEnrollmentError(int errMsgId, CharSequence errString) {
//fix: use the same fingerprint to go to enrolling page!
if (errString!=null)
Log.d("call back","onEnrollmentError proceedToEnrolling"+" errMsgId: "+errMsgId+" errString "+ errString.toString());
if (errString==null||errMsgId==FingerprintManager.FINGERPRINT_ERROR_UNABLE_TO_PROCESS)
proceedToEnrolling(false /* cancelEnrollment */);
else if (mNextClicked && errMsgId == FingerprintManager.FINGERPRINT_ERROR_CANCELED) {
mNextClicked = false;
proceedToEnrolling(false /* cancelEnrollment */);
}
}
});
}
这里重点关注一下mNextClicked =和proceedToEnrolling(boolean /* cancelEnrollment /);
private boolean mNextClicked; // 这个变量是全局性的,且多次出现,如下:
先看到 proceedToEnrolling(boolean / cancelEnrollment */);
private void proceedToEnrolling(boolean cancelEnrollment) {
if (mSidecar != null) {
if (cancelEnrollment) {
if (mSidecar.cancelEnrollment()) {
// Enrollment cancel requested. When the cancellation is successful,
// onEnrollmentError will be called with FINGERPRINT_ERROR_CANCELED, calling
// this again.
return;
}
}
getFragmentManager().beginTransaction().remove(mSidecar).commitAllowingStateLoss();
mSidecar = null;
startActivityForResult(getEnrollingIntent(), ENROLLING);
}
}
如果流程走到通过 startActivityForResult(getEnrollingIntent(), ENROLLING)跳转。
/**
Activity explaining the fingerprint sensor location for fingerprint enrollment.
*/
public class FingerprintEnrollFindSensor extends FingerprintEnrollBase {
......
//FingerprintEnrollBase.java
protected Intent getEnrollingIntent() {
Intent intent = new Intent();
intent.setClassName("com.android.settings", FingerprintEnrollEnrolling.class.getName());
intent.putExtra(ChooseLockSettingsHelper.EXTRA_KEY_CHALLENGE_TOKEN, mToken);
if (mUserId != UserHandle.USER_NULL) {
intent.putExtra(Intent.EXTRA_USER_ID, mUserId);
}
return intent;
}
}
则将跳转到FingerprintEnrollEnrolling界面。
大致过程是:指纹enroll操作后,反馈信息的回调,最终导致Listener接口中的对应方法得到调用,如下:(1.onEnrollmentProgressChange,2.onEnrollmentHelp,3.onEnrollmentError)而它们将导致proceedToEnrolling(boolean /*cancelEnrollment */)调用,最终导致页面的跳转。
不过,这里有一个逻辑细节(mNextClicked)很重要。需要做展开分析,才能彻底弄清这个跳转逻辑过程。
mNextClicked是一个典型的状态控制变量,它具体有什么作用?
startActivityForResult的使用,对应有onActivityResult
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
....
startLookingForFingerprint();
} else {
finish();
}
} else if (requestCode == ENROLLING) {
if (resultCode == RESULT_FINISHED) {
setResult(RESULT_FINISHED);
finish();
} else if (resultCode == RESULT_SKIP) {
setResult(RESULT_SKIP);
finish();
} else if (resultCode == RESULT_TIMEOUT) {
........
} else {
// We came back from enrolling but it wasn't completed, start again.
startLookingForFingerprint();
}
}
} else {
super.onActivityResult(requestCode, resultCode, data);
}
}
proceedToEnrolling中的mSidecar.cancelEnrollment()如何导致onEnrollmentError回调的:
private CancellationSignal mEnrollmentCancel;
mEnrollmentCancel = new CancellationSignal();
mFingerprintManager.enroll(mToken, mEnrollmentCancel,
0 /* flags */, mUserId, mEnrollmentCallback);
mSidecar.cancelEnrollment()将导致mEnrollmentCancel.cancel()调用
// FingerprintManager.java
@RequiresPermission(MANAGE_FINGERPRINT)
public void enroll(byte [] token, CancellationSignal cancel, int flags,
int userId, EnrollmentCallback callback) {
....
if (cancel != null) {
if (cancel.isCanceled()) {
Slog.w(TAG, "enrollment already canceled");
return;
} else {
cancel.setOnCancelListener(new OnEnrollCancelListener());
}
....
}
//import android.os.CancellationSignal.OnCancelListener;
private class OnEnrollCancelListener implements OnCancelListener {
@Override
public void onCancel() {
cancelEnrollment();
}
}
private void cancelEnrollment() {
if (mService != null) try {
mService.cancelEnrollment(mToken);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
//FingerprintService.java
@Override // Binder call
public void cancelEnrollment(final IBinder token) {
checkPermission(MANAGE_FINGERPRINT);
mHandler.post(new Runnable() {
@Override
public void run() {
ClientMonitor client = mCurrentClient;
if (client instanceof EnrollClient && client.getToken() == token) {
client.stop(client.getToken() == token);
}
}
});
}
//EnrollClient.java
@Override
public int stop(boolean initiatedByClient) {
if (mAlreadyCancelled) {
Slog.w(TAG, "stopEnroll: already cancelled!");
return 0;
}
IBiometricsFingerprint daemon = getFingerprintDaemon();
if (daemon == null) {
Slog.w(TAG, "stopEnrollment: no fingerprint HAL!");
return ERROR_ESRCH;
}
try {
final int result = daemon.cancel();
if (result != 0) {
Slog.w(TAG, "startEnrollCancel failed, result = " + result);
return result;
}
} catch (RemoteException e) {
Slog.e(TAG, "stopEnrollment failed", e);
}
if (initiatedByClient) {
onError(FingerprintManager.FINGERPRINT_ERROR_CANCELED, 0 /* vendorCode */);
}
mAlreadyCancelled = true;
return 0;
}
通过 cancel.setOnCancelListener(new OnEnrollCancelListener());注册监听,
而mEnrollmentCancel.cancel()调用将导致onCancel()回调–> cancelEnrollment()调用(具体分析见:CancellationSignal.java),最终将导致daemon.cancel()—>onEnrollmentError回调的。
分析CancellationSignal.java
该类提供删除信号类,提供终止操作的能力,具体分析时,注意一下mIsCanceled这个boolean变量。它也是一个状态控制变量,解决的多次调用问题。
public void setOnCancelListener(OnCancelListener listener) {
synchronized (this) {
waitForCancelFinishedLocked();
if (mOnCancelListener == listener) {
return;
}
mOnCancelListener = listener;
if (!mIsCanceled || listener == null) {
return;
}
}
listener.onCancel();//一般情况下不会走到这里。
}
CancellationSignal的用法:
先通过setOnCancelListener(OnCancelListener listener) 设置监听,并对OnCancelListener接口方法做具体实现;
再通过CancellationSignal 的对象去调用cancel(),进而调用已经具体实现了的接口方法,如onCancel()。
回到mNextClicked状态控制变量(一般为boolean)
当成功录入第一步,将导致onEnrollmentProgressChange调用–>mNextClicked变为true—> proceedToEnrolling(true /* cancelEnrollment /)—>mSidecar.cancelEnrollment返回值(true)–>return—>指纹停止工作–>onEnrollmentError回调—>(mNextClicked 为true)–>mNextClicked重新变成默认值false—> proceedToEnrolling(false /* cancelEnrollment /)—>startActivityForResult调用,最终成功跳转。
Note:onActivityResult的部分可以关注一下,但这里不做具体的分析。
作为boolean类型的状态控制变量一般与return连用,可改变代码执行流程,分析的时候,一定要小心谨慎。只要出现如下结构,就多注意一点:
//controller 全局变量,且多次出现
if((boolean)controller ....){
.....
return ... ;
}
or
if((boolean)controller ....){
.....
}
ps.
getFragmentManager().beginTransaction().remove(mSidecar).commitAllowingStateLoss();
mSidecar = null;
FingerprintEnrollSidecar是个一个fragment,可以将它的逻辑(界面)绑到(add)FingerprintEnrollFindSensor这个Activity中,用完之后,也可以将该fragment从Activity中解绑(remove)。