概要
株式会社 Zaim で提供している家計簿アプリ Zaim では、12/16(水)にアプリの画面ロック機能の方法として指紋認証を追加しました。
パスワードによるロックに比べて、とても使いやすいのでぜひ触ってみてください。(Android 6.0 以上の端末でのみ利用可能です。)
今回はこの機能の開発中にエラーハンドリングの部分でハマったので、注意点をまとめてみました。
指紋認証 API について
指紋認証APIについては、公式ブログおよび(手前味噌ですが)以下のブログなどで紹介されていますので、ここでは使い方などの説明は省きます。
- Android 新機能のサンプル: Fingerprint API を使用したリモート サーバー認証
- Android 6.0 の指紋認証 API を触ってみた感想文
- API level 23 未満を考慮した指紋認証 API の実装
また Google のサンプルコードも公開されています。
指紋センサーとのやり取り
指紋センサーによる読み取りを準備(warm up)するには、 FingerprintManager#authenticate()
をコールします。
このメソッドがコールされた後、指紋センサーでスキャンすることが出来るようになります。この状態は、 AuthenticationCallback クラスの onAuthenticationError()
か onAuthenticationSucceeded()
が呼ばれるまで維持されます。
authenticate() メソッドの Parameter は以下となっています。
- crypto: オブジェクトの呼び出しに関連づけられる
- cancel: 認証をキャンセルできるオブジェクト
- flags: オプションフラグ(0をセットすべき)
- callback: 認証イベントを受け取るオブジェクト
- handler: callbackイベントを扱うオプションハンドラー
このうち、callback には次の4つのメソッドがあります。
- onAuthenticationError : 以下のどれにも当てはまらない復帰不可能なエラーを検出したとき
- onAuthenticationFailed : 指紋は読み取れたけど未登録と判断したとき
- onAuthenticationHelp : 復帰可能なエラーを検出したとき
- onAuthenticationSucceeded : 指紋を登録済みのものと判断したとき
エラーハンドリングが必要なケース
この中で onAuthenticationError は、以下の場合にコールされます。
- CancellationSignal が発動したとき
- 所定回数、指紋の読み取りに失敗したとき
Google のサンプルでは、上記1と2の発生を区別するために、以下のように mSelfCancelled
というフラグで処理を分岐させています。
// このメソッドはFragmentのonResume()で呼ばれます
public void startListening(FingerprintManager.CryptoObject cryptoObject) {
if (!isFingerprintAuthAvailable()) {
return;
}
mCancellationSignal = new CancellationSignal();
mSelfCancelled = false;
mFingerprintManager
.authenticate(cryptoObject, mCancellationSignal, 0 /* flags */, this, null);
mIcon.setImageResource(R.drawable.ic_fp_40px);
}
// このメソッドはFragmentのonPause()で呼ばれます
public void stopListening() {
if (mCancellationSignal != null) {
mSelfCancelled = true;
mCancellationSignal.cancel();
mCancellationSignal = null;
}
}
@Override
public void onAuthenticationError(int errMsgId, CharSequence errString) {
// stopListening()が呼ばれなかったときはif文の中の処理が動きます
if (!mSelfCancelled) {
showError(errString);
mIcon.postDelayed(new Runnable() {
@Override
public void run() {
mCallback.onError();
}
}, ERROR_TIMEOUT_MILLIS);
}
}
ところが、1のケースには特殊なケースがありました。
authenticate() で指紋センサーの応答を待機しているときに、電源ボタンを押すと onPause()より早いタイミングで CancellationSignal が発動されます。
そのためサンプルコードのままだと、mSelfCancelled が false のまま onAuthenticationError() に入ってきてしまいます。
onAuthenticationError() の実装内容にもよりますが、そのことを考慮しておかないと意図しない挙動になるケースもありますので、注意した方がよさそうです。
回避するなら
電源ボタン押下による CancellationSignal 発動を考慮するのであれば、以下のように onAuthenticationError() を修正するのが簡単かと思います。
@Override
public void onAuthenticationError(int errMsgId, CharSequence errString) {
// 電源ボタンを押したときなど
if (errMsgId == FingerprintManager.FINGERPRINT_ERROR_CANCELED) {
return;
}
// stopListening()が呼ばれなかったときはif文の中の処理が動きます
if (!mSelfCancelled) {
showError(errString);
mIcon.postDelayed(new Runnable() {
@Override
public void run() {
mCallback.onError();
}
}, ERROR_TIMEOUT_MILLIS);
}
}
まとめ
onAuthenticationError()が呼ばれるケースは、
- CancellationSignal が発動したとき
- 明示的に
CancellationSignal#cancel()
を呼ぶ - 指紋センサー準備中に電源ボタンを押す
- 所定回数、指紋の読み取りに失敗したとき