AndroidXの生体認証(BiometricPrompt)の使い方
TL; DR
- AndroidXの生体認証コンポネント
BiometricPrompt
が正式リリースされてv1.0になった! - サポート対象はAndroid6.0以上!
- FingerPrintManagerは非推奨になった!
- ↓みたいな画面が簡単に作れる!
目次
はじめに
2019/11/7のリリースで生体認証コンポネントのバージョンが1.0になってたので使ってみました。UIも含めて簡単に利用できるようになってるのでBiometricPromptの利用方法をシェアします。
ちなみにこれまではFingerprintManager
というコンポネントがありましたが今は非推奨です。BiometricPrompt
を利用するようにとリファレンスに書かれています。
FingerprintManager
のかわりにBiometricPrompt
が導入された背景は、生体認証をカスタムしてある端末であってもアプリ開発者が意識せずに実装できるようにというGoogleの優しさです。興味のある方はAndroid Developers Blogのこのポストを読んでみてください。
開発環境
- Android Studio 3.5.2
- BiometricPropmt 1.0.0
- Android10 (Pixel3)
実装方法
できるだけシンプルな実装で解説します。
依存関係の追加
app/build.gradle
を編集します。
dependencies { ...(省略)... def biometric_version = "1.0.0" implementation "androidx.biometric:biometric:$biometric_version" }
生体認証が使えるかのチェック
BiometricManager#canAuthenticate()
を使って生体認証が使えるかチェックする関数を実装します。生体認証が利用できないときは原因を返り値で区別することが出来ます。詳しくは次のセクションを読んでください。
fun checkBiometric(): Boolean { val biometricManager = BiometricManager.from(context!!) return biometricManager.canAuthenticate() == BiometricManager.BIOMETRIC_SUCCESS }
生体認証APIの呼び出し
上で実装したcheckBiometric()
をパスしたら、BiometricPrompt#authenticate()
を使って生体認証プロンプトを起動します。
BiometricPrompt#AuthenticationCallback()
には認証成功時、失敗時のメソッドを実装できます。
onAuthenticationSucceeded
- ユーザーが認証されたときに呼び出されます。onAuthenticationError
- 回復不能なエラーが発生したときに呼び出されます。原因はエラーコードで区別することが出来ます。詳しくは次のセクションを読んでください。onAuthenticationFailed
- 回復可能な失敗が発生したときに呼び出されます。
fun authenticate() { // 生体認証の利用可否をチェックします。 if (!checkBiometric()) { Snackbar.make( view!!, "This device can't be authenticated with biometric", Snackbar.LENGTH_INDEFINITE ).show() return } // BiometricPromptにわたすExecutor。 // Android Pより前ではActivity#getMainExecutorが使えないので分岐があります。 val mainExecutor = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) { activity!!.mainExecutor } else { object: Executor { val handler = Handler(Looper.getMainLooper()) override fun execute(command: Runnable) { handler.post(command) } } } val biometricPrompt = BiometricPrompt( this, mainExecutor, object: BiometricPrompt.AuthenticationCallback() { override fun onAuthenticationSucceeded(result: BiometricPrompt.AuthenticationResult) { // 認証が成功。次の画面への遷移させます。 findNavController().navigate(R.id.action_startFragment_to_mainFragment) } override fun onAuthenticationError(errorCode: Int, errString: CharSequence) { // 回復不能なエラーが発生。スナックバーを表示しておきます。 Snackbar.make( view!!, "An error occurred during biometric authentication", Snackbar.LENGTH_INDEFINITE ).show() } override fun onAuthenticationFailed() { // 回復不能なエラーが発生。認証を続けることができるのでロギングだけしておきます。 Log.e("StartFragment", "Failed to authenticate with biometric information") } }) // 生体認証のプロンプトに表示するタイトルとキャンセルボタンのテキストの設定。 val info = BiometricPrompt.PromptInfo.Builder() .setTitle(context!!.getString(R.string.title_biometric_authentication)) .setNegativeButtonText(context!!.getText(R.string.cancel)) .build() // 生体認証の実行 biometricPrompt.authenticate(info) }
初回起動時に設定
画面が表示されたときと、ボタンが押されたときに生体認証が立ち上がるようにしておきます。
override fun onActivityCreated(savedInstanceState: Bundle?) { super.onActivityCreated(savedInstanceState) authenticate() button_show_authentication.setOnClickListener { authenticate() } }
エラーの区別
ここまではなるべくシンプルな実装を書いてきました。
しかし実際の製品ではエラーが起こった際に、生体認証情報が登録されてないケースや、生体認証デバイスがそもそも存在しないケースで処理を変えたいというのが現実的でしょう。(たとえば試行回数が多すぎてエラーが発生した時は、しばらく待つようにユーザーに伝えるなど)
BiometricManager#canAuthenticate()
ではその返り値で、BiometricPrompt#authenticate()
ではBiometricPrompt#AuthenticationCallback#onAuthenticationError()
に渡ってくるエラーコードでそれぞれエラーの原因を特定することが出来ます。
下にエラーコードの一覧をまとめたのでそれぞれのアプリを実装するときの参考にしてください。
生体認証の利用可否
返り値 | 概要 |
---|---|
BIOMETRIC_SUCCESS | エラーは検出されませんでした。 |
BIOMETRIC_ERROR_HW_UNAVAILABLE | ハードウェアは利用できません。 あとでもう一度試してみてください。 |
BIOMETRIC_ERROR_NONE_ENROLLED | ユーザーには生体認証が登録されていません。 |
BIOMETRIC_ERROR_NO_HARDWARE | 生体認証ハードウェアはありません。 |
生体認証の回復不能なエラー
エラーコード | 概要 |
---|---|
ERROR_HW_UNAVAILABLE | ハードウェアは利用できません。あとでもう一度試してみてください。 |
ERROR_UNABLE_TO_PROCESS | センサーが現在の画像を処理できなかったときに返されるエラー状態。 |
ERROR_TIMEOUT | 現在のリクエストの実行時間が長すぎる場合に返されるエラー状態。これは、プログラムが生体認証センサーを無期限に待機するのを防ぐことを目的としています。タイムアウトはプラットフォームおよびセンサー固有ですが、通常は30秒程度です。 |
ERROR_NO_SPACE | 登録などの操作に対して返されるエラー状態。操作を完了するのに十分なストレージが残っていないため、操作を完了できません。 |
ERROR_CANCELED | 生体認証センサーが利用できないため、操作はキャンセルされました。たとえば、これは、ユーザーが切り替えられたとき、デバイスがロックされたとき、または別の保留中の操作によって防止または無効化されたときに発生する可能性があります。 |
ERROR_UNABLE_TO_REMOVE | ※ライブラリ内部エラー |
ERROR_LOCKOUT | 試行回数が多すぎるためAPIがロックアウトされたため、操作はキャンセルされました。これは、5回失敗した後に発生し、30秒間続きます。 |
ERROR_VENDOR | 上記のカテゴリのいずれにも該当しない条件がある場合、ハードウェアベンダーはこのリストを拡張できます。 ベンダーは、これらのエラーのエラー文字列を提供する責任があります。 これらのメッセージは通常、登録などの内部操作用に予約されていますが、他の方法ではカバーされないベンダーエラーを表すために使用される場合があります。 エラーが発生した場合、アプリケーションはエラーメッセージ文字列を表示することが期待されますが、デバイスおよびベンダー固有であるため、メッセージIDに依存しないことをお勧めします。 |
ERROR_LOCKOUT_PERMANENT | ERROR_LOCKOUTが何度も発生したため、操作はキャンセルされました。 ユーザーが強力な認証(PIN /パターン/パスワード)でロックを解除するまで、生体認証は無効になります |
ERROR_USER_CANCELED | ユーザーが操作をキャンセルしました。 これを受信すると、アプリケーションは代替認証(パスワードなど)を使用する必要があります。 アプリケーションは、「 |
ERROR_NO_BIOMETRICS | ユーザーには生体認証が登録されていません。 |
ERROR_HW_NOT_PRESENT | デバイスには生体認証センサーがありません。 |
ERROR_NEGATIVE_BUTTON | ユーザーがネガティブボタンを押した。 |
ERROR_NO_DEVICE_CREDENTIAL | デバイスには、ピン、パターン、またはパスワードが設定されていません。 |
ERROR_VENDOR_BASE | ※ライブラリ内部エラー |
生体認証が利用できないときのフォールバック処理
下記のように実装すると、生体認証が扱えない場合のフォールバック処理としてPIN/Pattern/Passwordの認証を代わりに使うことも出来ます。 ただしBiometricPrompt#PromptInfo#Builder#setNegativeButtonText()
と同時に使えないことに注意してください。
val info = BiometricPrompt.PromptInfo.Builder() .setTitle(context!!.getString(R.string.title_biometric_authentication)) .setDeviceCredentialAllowed(true) .build()
サンプルアプリ
下のRepositoryに実行可能なアプリを置いてあります。シンプルな実装を見たい場合はsimplify
ブランチに切り替えてみてください。
免責
今回はPixel3の指紋認証でしか試していないので、Pixel4などの顔認証でも同じAPIが使えるか未確認です。(ただ@hotdrop_77さんによるとPixel4の顔認証もOKのようです⇓)
会社に検証端末用Pixel4が届いたので、前作ったBiometricPromptで指紋認証対応した版のアプリで顔認証いけるか試したらちゃんとUIともに認証成功して感動した。これは素晴らしい。
— hotdrop_77 (@hotdrop_77) November 19, 2019
非推奨だったfingerprintManagerは完全に消え去ってよいかな。