Androidで指紋認証を軽く利用してみる

セールでNexus 6Pをゲットしたので指紋認証APIを使ってみました。
Android 6.0端末にハードウェアの指紋認証センサーがあり、指紋登録が済んでいる場合に動作するように書いています。

# FingerprintManagerで提供されている機能

FingerprintManagerというのが、Androidでの指紋認証を提供しています。
FingerprintManager#authenticate時に指紋センサーに触れた指紋が、予め端末に登録してある指紋の中に存在するかを確認することができます。

# Permission

指紋認証を利用するには、予めパーミッションを宣言しておかないといけないのでAndroidManifest.xmlに追記しておきましょう。

```xml
<uses-permission android:name="android.permission.USE_FINGERPRINT"/>
```

# 下準備

アプリケーションが本当に、パーミッションを持っているのか確認しないとAndroidStudioに怒られるようになったので、FingerprintManagerを使う前に確認を行います

```java
if (checkSelfPermission(Manifest.permission.USE_FINGERPRINT) != PackageManager.PERMISSION_GRANTED) {
    return;
}
```

# 認証してみる

## FingerprintManagerの取得

FingerprintManagerが指紋の認証機能を提供します。getSystemService(Context.FINGERPRINT_SERVICE)で取得できます。

```java
FingerprintManager fingerprintManager = (FingerprintManager) getSystemService(Context.FINGERPRINT_SERVICE);
```

## 指紋認証可能か調べる

認証に入る前に、端末が指紋認証を利用できるのかどうかを確認しないといけません
FingerprintManager#isHardwareDetected()で指紋センサーが端末に存在するのか
FingerprintManager#hasEnrolledFingerprints()で指紋が登録されているのかを取得することができます。
どちらかがtrueならば利用できると考えて良いでしょう。

```java
if (fingerprintManager.isHardwareDetected() || fingerprintManager.hasEnrolledFingerprints()) {
    // 認証可能
}
```

## 認証

実際に認証を行うために、FingerprintManager#authenticateメソッドを呼びます。
呼ぶと、ユーザー指紋センサーに触れるまで待機状態になります。（画面上にはなにも表示されないので、実際はダイアログ等を予め表示しておかないとわからないです）

```java
fingerprintManager.authenticate(null, null, 0, new FingerprintManager.AuthenticationCallback() {
    @Override
    public void onAuthenticationError(int errorCode, CharSequence errString) {
        Toast.makeText(MainActivity.this, "onAuthenticationError: " + errString, Toast.LENGTH_SHORT).show();
    }

    @Override
    public void onAuthenticationHelp(int helpCode, CharSequence helpString) {
        Toast.makeText(MainActivity.this, "onAuthenticationHelp: " + helpString, Toast.LENGTH_SHORT).show();
    }

    @Override
    public void onAuthenticationSucceeded(FingerprintManager.AuthenticationResult result) {
        Toast.makeText(MainActivity.this, "onAuthenticationSucceeded", Toast.LENGTH_SHORT).show();
    }

    @Override
    public void onAuthenticationFailed() {
        Toast.makeText(MainActivity.this, "onAuthenticationFailed", Toast.LENGTH_SHORT).show();
    }
}, new Handler());
```

引数など詳細は[こちら](http://developer.android.com/intl/ja/reference/android/hardware/fingerprint/FingerprintManager.html#authenticate%28android.hardware.fingerprint.FingerprintManager.CryptoObject,%20android.os.CancellationSignal,%20int,%20android.hardware.fingerprint.FingerprintManager.AuthenticationCallback,%20android.os.Handler%29)にありますが、ざっくり言うと

```java
authenticate (FingerprintManager.CryptoObject crypto, CancellationSignal cancel, int flags, FingerprintManager.AuthenticationCallback callback, Handler handler)
```

* crypto
指紋認証成功時に利用する鍵を設定します。使わない場合はnull。これを使うと指紋認証でログインができるようになるようです。

* cancel
指紋認証を途中でキャンセルするためのオブジェクトを設定します。ここに指定したCancellationSignalインスタンスのcancelメソッドを呼ぶことで、指紋認証を途中でキャンセルすることができます。使わない場合はnull

* flags
0を設定する

* callback
成功時や、失敗時などのコールバック

* handler
コールバックを呼ぶHandler

### コールバック

FingerprintManager.AuthenticationCallbackには4つのコールバックがあります。呼ばれるタイミングは以下です。

* onAuthenticationError(int errorCode, CharSequence errString)
エラー時に呼ばれます。キャンセル時もここが呼ばれます。

* onAuthenticationHelp(int helpCode, CharSequence helpString)
読み取り不十分だった場合などに呼ばれます。helpStringの中に原因の文章が入ります。このメソッドが呼ばれても指紋認証自体は終了していないので再度指紋センサーに触れるとハンドリングされます。

* onAuthenticationSucceeded(FingerprintManager.AuthenticationResult result)
指紋認証に成功したときに呼ばれます。resultの中には、authenticateの時に指定したcryptoが入っています。

* onAuthenticationFailed()
登録されていない指紋が検出された時はこちらが呼ばれます。このメソッドが呼ばれても指紋認証自体は終了していないので再度指紋センサーに触れるとハンドリングされます。

 
# サンプル

実際に使うには AlertDialog等で、指紋認証中であることを表す必要があります。ダイアログのキャンセル時にCancellationSignal#cancelを呼んでおくと、指紋認証をキャンセルすることができます。
ダイアログを消すタイミングは、指紋認証が終了するonAuthenticationError, onAuthenticationSucceededが良さそうです。

```java
public class MainActivity extends AppCompatActivity {

    private Dialog mDialog;
    private CancellationSignal mCancellationSignal;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        Button button = (Button) findViewById(R.id.button);
        button.setText("端末に登録されている指紋で認証を行う");
        button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                useFingerprint();
            }
        });
    }

    private void useFingerprint() {
        if (checkSelfPermission(Manifest.permission.USE_FINGERPRINT) != PackageManager.PERMISSION_GRANTED) {
            return;
        }

        FingerprintManager fingerprintManager = (FingerprintManager) getSystemService(Context.FINGERPRINT_SERVICE);
        if (fingerprintManager.isHardwareDetected() || fingerprintManager.hasEnrolledFingerprints()) {

            AlertDialog.Builder builder = new AlertDialog.Builder(this);
            builder.setTitle("指紋認証");
            builder.setMessage("指紋センサーに触れてください");
            builder.setNegativeButton("キャンセル", new DialogInterface.OnClickListener() {
                @Override
                public void onClick(DialogInterface dialog, int which) {
                    mCancellationSignal.cancel();
                }
            });
            builder.setOnCancelListener(new DialogInterface.OnCancelListener() {
                @Override
                public void onCancel(DialogInterface dialog) {
                    mCancellationSignal.cancel();
                }
            });
            mDialog = builder.show();

            mCancellationSignal = new CancellationSignal();

            fingerprintManager.authenticate(null, mCancellationSignal, 0, new FingerprintManager.AuthenticationCallback() {
                @Override
                public void onAuthenticationError(int errorCode, CharSequence errString) {
                    Toast.makeText(MainActivity.this, "onAuthenticationError: " + errString, Toast.LENGTH_SHORT).show();
                    mDialog.dismiss();
                }

                @Override
                public void onAuthenticationHelp(int helpCode, CharSequence helpString) {
                    Toast.makeText(MainActivity.this, "onAuthenticationHelp: " + helpString, Toast.LENGTH_SHORT).show();
                }

                @Override
                public void onAuthenticationSucceeded(FingerprintManager.AuthenticationResult result) {
                    Toast.makeText(MainActivity.this, "onAuthenticationSucceeded", Toast.LENGTH_SHORT).show();
                    mDialog.dismiss();
                }

                @Override
                public void onAuthenticationFailed() {
                    Toast.makeText(MainActivity.this, "onAuthenticationFailed", Toast.LENGTH_SHORT).show();
                }
            }, new Handler());
        }
    }
}

```