EDIT MODE

モバイルアプリ開発に関することを書いています

API level 23 未満を考慮した指紋認証 API の実装

前回、指紋認証 API のサンプルコードをざっと見てみたのですが、そのときに FingerprintManagerCompat クラスが Support Library v4 にあるのを見つけました。

androhi.hatenablog.com

このクラスを使えば Android 6.0 (API level 23 未満) も含め、スッキリと実装できるんじゃないかなと思い調べてみました。

FingerprintManager のインスタンス取得

前回のGoogleのサンプルコードでは、以下のようなコードでインスタンスを取得していました。

FingerprintManager manager = context.getSystemService(FingerprintManager.class);

ところが以下の2点で API level 23 未満では、getSystemService() 経由だと使えません。

  1. Context.getSystemService(Class<T>)API level 23から追加されたメソッド
  2. Context.getSystemService(String) だと引数のサービス名に Fingerprint は無い

そのため FingerprintManagerCompat では、別途 Context を使った方法が用意されていました。

// contextはActivityなど
FingerprintManagerCompat managerCompat = FingerprintManagerCompat.from(context);

FingerprintManagerCompat のソースコードを辿って行くと、内部で Build.VERSION.SDK_INT をチェックしていて、23 以上だと Api23FingerprintManagerCompatImpl 内部クラスを、23 未満だと LegacyFingerprintManagerCompatImpl 内部クラスを、それぞれインスタンス化していました。

指紋センサーの判定など

FingerprintManager では、端末に指紋センサーのハードウェアがあるか?や、指紋の登録があるか?などを判定するメソッドを提供しています。

まったく同じメソッドが、FingerprintManagerCompat クラスにも実装されていますが、どのような挙動になるのか確認してみました。

結論から書くと、API level 23 以上だとちゃんと FingerprintManager 経由で判定し、23 未満だと固定で false を返していました。

API level 23 未満

    private static class LegacyFingerprintManagerCompatImpl implements FingerprintManagerCompatImpl {

        public LegacyFingerprintManagerCompatImpl() {
        }

        @Override
        public boolean hasEnrolledFingerprints(Context context) {
            return false;
        }

        @Override
        public boolean isHardwareDetected(Context context) {
            return false;
        }

        ...
        }
    }

API level 23 以上

    private static class Api23FingerprintManagerCompatImpl implements FingerprintManagerCompatImpl {

        public Api23FingerprintManagerCompatImpl() {
        }

        @Override
        public boolean hasEnrolledFingerprints(Context context) {
            return FingerprintManagerCompatApi23.hasEnrolledFingerprints(context);
        }

        @Override
        public boolean isHardwareDetected(Context context) {
            return FingerprintManagerCompatApi23.isHardwareDetected(context);
        }
        
        ...
public final class FingerprintManagerCompatApi23 {

    private static FingerprintManager getFingerprintManager(Context ctx) {
        return ctx.getSystemService(FingerprintManager.class);
    }

    public static boolean hasEnrolledFingerprints(Context context) {
        return getFingerprintManager(context).hasEnrolledFingerprints();
    }

    public static boolean isHardwareDetected(Context context) {
        return getFingerprintManager(context).isHardwareDetected();
    }
    
    ...

分かりやすくいいですね。

指紋認証

実際に指紋センサーから指紋を読取るときは、 FingerprintManager#authenticate(CryptoObject, CancellationSignal, int, AuthenticationCallback, Handler) メソッドを使ってましたが、これも先ほどと同じ形でした。

API level 23 以上では、FingerprintManager 経由で処理し、23 未満では何もしません。

API level 23 未満

    private static class LegacyFingerprintManagerCompatImpl
            implements FingerprintManagerCompatImpl {
        ...

        @Override
        public void authenticate(Context context, CryptoObject crypto, int flags,
                CancellationSignal cancel, AuthenticationCallback callback, Handler handler) {
            // TODO: Figure out behavior when there is no fingerprint hardware available
        }
    }

API level 23 以上

    private static class Api23FingerprintManagerCompatImpl implements FingerprintManagerCompatImpl {
        ...
        @Override
        public void authenticate(Context context, CryptoObject crypto, int flags,
                CancellationSignal cancel, AuthenticationCallback callback, Handler handler) {
            FingerprintManagerCompatApi23.authenticate(context, wrapCryptoObject(crypto), flags,
                    cancel != null ? cancel.getCancellationSignalObject() : null,
                    wrapCallback(callback), handler);
        }
public final class FingerprintManagerCompatApi23 {
    ...
    public static void authenticate(Context context, CryptoObject crypto, int flags, Object cancel,
            AuthenticationCallback callback, Handler handler) {
        getFingerprintManager(context).authenticate(wrapCryptoObject(crypto),
                (android.os.CancellationSignal) cancel, flags,
                wrapCallback(callback), handler);
    }

まとめ

当分 API level 23 以上でアプリを開発出来る状況は来ないので、開発しているアプリに指紋認証を導入したいときは、この FingerprintManagerCompat を使うのが良さそうです。

今まで割と自力で API level 判定して処理を分けていたようなところも、xxxCompat クラスがいい感じに内部で切り替えてくれるケースが増えてきた気がします。

新しい機能が出てきたら、まずは xxxCompat クラスも一緒に探すと、既存アプリへの導入が楽になりそうですね。