AuthenticationCallback and KeygenParameterSpec crashing app despite checking API level

醉酒当歌 提交于 2019-12-13 16:52:18

问题


EDIT: See my answer below for solution

I have a login activity with a fingerprint sign-in feature. I've implemented the BiometricPrompt for API 28 and I use FingerprintManagerCompat for API 23-27 and everything works great when using any of those versions of Android. However, I'm testing my app on API 19 (my app's minimumSdk) and despite having explicit API checks around the relevant areas, the app still crashes with the errors
Unable to resolve superclass of Lcom/example/myapp/LoginActivity$BiometricCallback
and
Could not find class 'android.security.keystore.KeyGenParameterSpec$Builder', referenced from method com.example.myapp.LoginActivity.generateKey
Is there a way to get my login activity to work with APIs lower than 23? What am I doing wrong? Here's my code...

LoginActivity.java

public class LoginActivity extends AppCompatActivity {

    //Fingerprint authorization
    private final String KEY_NAME = "FingerPrintKey";
    FingerprintManagerCompat fingerprintManager;

    KeyStore keyStore;
    KeyGenerator keyGenerator;
    Cipher cipher;
    FingerprintManagerCompat.CryptoObject cryptoObject;
    SecretKey key;

    @RequiresApi(api = 23)
    private BiometricDialog mBioDialog;


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

        // Checking shared preferences to see if fingerprint sign in is enabled           

        if (bioEnabled) {
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
                showBioMetricDialog();
            }
        }
    }


    // Method to show the dialog prompting for fingerprint
    @RequiresApi(api = 23)
    private void showBioMetricDialog() {

        //Android P uses Biometric Prompt
        if (Build.VERSION.SDK_INT >= 28) {
            BiometricCallback biometricCallback = new BiometricCallback();
            displayBiometricPrompt(biometricCallback);

        //Older versions use Android's Keystore
        } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
            fingerprintManager = FingerprintManagerCompat.from(this);

            if (fingerprintManager.isHardwareDetected()
                    && fingerprintManager.hasEnrolledFingerprints()) {
                generateKey();

                if (initCipher()) {
                    cryptoObject = new FingerprintManagerCompat.CryptoObject(cipher);
                    FingerPrintCallback callback = new FingerPrintCallback();
                    fingerprintManager.authenticate(cryptoObject, 0,
                            new android.support.v4.os.CancellationSignal(),
                            callback, null);

                    mBioDialog = new BiometricDialog(this, callback);
                    mBioDialog.setTitle(getString(R.string.bio_dialog_title));
                    mBioDialog.setSubtitle(getString(R.string.bio_dialog_subtitle));
                    mBioDialog.setDescription(getString(R.string.bio_dialog_finger_desc));
                    mBioDialog.setNegativeButtonText(getString(R.string.bio_dialog_negative));
                    mBioDialog.show();
                }
            }
        }
    }


    @RequiresApi(api = Build.VERSION_CODES.M)
    private void generateKey() {
        try {

            keyStore = KeyStore.getInstance("AndroidKeyStore");
            keyStore.load(null);

            keyGenerator = KeyGenerator.getInstance(
                    KeyProperties.KEY_ALGORITHM_AES, "AndroidKeyStore");
            keyGenerator.init(new KeyGenParameterSpec.Builder(KEY_NAME, KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT)
                    .setBlockModes(KeyProperties.BLOCK_MODE_CBC)
                    .setUserAuthenticationRequired(true)
                    .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_PKCS7)
                    .build());
            keyGenerator.generateKey();

        } catch (KeyStoreException
                | NoSuchAlgorithmException
                | NoSuchProviderException
                | InvalidAlgorithmParameterException
                | CertificateException
                | IOException e) {
            e.printStackTrace();
        }
    }


    @RequiresApi(api = Build.VERSION_CODES.M)
    private boolean initCipher() {
        try {
            cipher = Cipher.getInstance(KeyProperties.KEY_ALGORITHM_AES + "/"
                    + KeyProperties.BLOCK_MODE_CBC + "/" + KeyProperties.ENCRYPTION_PADDING_PKCS7);
        } catch (NoSuchAlgorithmException
                | NoSuchPaddingException e) {
            throw new RuntimeException("Failed to get Cipher", e);
        }

        try {
            keyStore.load(null);
            key = (SecretKey) keyStore.getKey(KEY_NAME, null);
            cipher.init(Cipher.ENCRYPT_MODE, key);
            return true;
        } catch (KeyPermanentlyInvalidatedException e) {
            return false;
        } catch (KeyStoreException | CertificateException
                | UnrecoverableKeyException | IOException
                | NoSuchAlgorithmException | InvalidKeyException e) {
            throw new RuntimeException("Failed to init Cipher", e);
        }
    }


    @Override
    protected void onDestroy() {
        super.onDestroy();
        if (Build.VERSION.SDK_INT >= 23) {
            if (mBioDialog != null && mBioDialog.isShowing())
                mBioDialog.cancel();
        }
    }


    @RequiresApi(api = 28)
    private void displayBiometricPrompt(final BiometricCallback callback) {

        CancellationSignal cancellationSignal = new CancellationSignal();
        cancellationSignal.setOnCancelListener(new CancellationSignal.OnCancelListener() {
            @Override
            public void onCancel() {
                Toast.makeText(LoginActivity.this, "Cancelled", Toast.LENGTH_SHORT).show();
            }
        });

        new BiometricPrompt.Builder(this)
                .setTitle(getString(R.string.bio_dialog_title))
                .setSubtitle(getString(R.string.bio_dialog_subtitle))
                .setDescription(getString(R.string.bio_dialog_desc))
                .setNegativeButton(getString(R.string.bio_dialog_negative), this.getMainExecutor(), new DialogInterface.OnClickListener() {

                    @Override
                    public void onClick(DialogInterface dialog, int which) {
                        callback.onAuthenticationCancelled();
                    }
                }).build().authenticate(cancellationSignal, this.getMainExecutor(), callback);
    }


    private void bioEnabledLogin() {
        // Fetch login credentials from shared prefs and do a loginTask
    }


    // Inner class for Biometric Authentication callbacks
    @RequiresApi(api = Build.VERSION_CODES.P)
    public class BiometricCallback extends android.hardware.biometrics.BiometricPrompt.AuthenticationCallback {

        @Override
        public void onAuthenticationSucceeded(android.hardware.biometrics.BiometricPrompt.AuthenticationResult result) {
            super.onAuthenticationSucceeded(result);
            bioEnabledLogin();
        } 

        // The rest of the callback methods here (they don't currently do anything)

    }


    // Inner class for FingerPrintManager Authentication callbacks
    @RequiresApi(api = Build.VERSION_CODES.M)
    public class FingerPrintCallback extends FingerprintManagerCompat.AuthenticationCallback {
        FingerPrintCallback() {
            super();
        }

        @Override
        public void onAuthenticationSucceeded(FingerprintManagerCompat.AuthenticationResult result) {
            super.onAuthenticationSucceeded(result);
            mBioDialog.dismiss();
            bioEnabledLogin();
        }
    }
}

And here is the relevant bit of logcat:

09:48:01.480 15048 15048 W dalvikvm: Unable to resolve superclass of Lcom/example/myapp/LoginActivity$BiometricCallback; (300)
10-19 09:48:01.480 15048 15048 W dalvikvm: Link of class 'Lcom/example/myapp/LoginActivity$BiometricCallback;' failed
10-19 09:48:01.480   825  1043 V SmartFaceService - 3rd party pause: onReceive [android.intent.action.ACTIVITY_STATE/com.example.myapp/create]
10-19 09:48:01.490 15048 15048 E dalvikvm: Could not find class 'android.hardware.biometrics.BiometricPrompt$Builder', referenced from method com.example.myapp.LoginActivity.displayBiometricPrompt
10-19 09:48:01.490 15048 15048 W dalvikvm: VFY: unable to resolve new-instance 302 (Landroid/hardware/biometrics/BiometricPrompt$Builder;) in Lcom/example/myapp/LoginActivity;
10-19 09:48:01.490 15048 15048 D dalvikvm: VFY: replacing opcode 0x22 at 0x000d
10-19 09:48:01.490 15048 15048 E dalvikvm: Could not find class 'android.security.keystore.KeyGenParameterSpec$Builder', referenced from method com.example.myapp.LoginActivity.generateKey
10-19 09:48:01.490 15048 15048 W dalvikvm: VFY: unable to resolve new-instance 430 (Landroid/security/keystore/KeyGenParameterSpec$Builder;) in Lcom/example/myapp/LoginActivity;
10-19 09:48:01.490 15048 15048 D dalvikvm: VFY: replacing opcode 0x22 at 0x001a
10-19 09:48:01.490 15048 15048 W dalvikvm: VFY: unable to resolve exception class 432 (Landroid/security/keystore/KeyPermanentlyInvalidatedException;)
10-19 09:48:01.490 15048 15048 W dalvikvm: VFY: unable to find exception handler at addr 0x2d
10-19 09:48:01.490 15048 15048 W dalvikvm: VFY:  rejected Lcom/example/myapp/LoginActivity;.initCipher ()Z
10-19 09:48:01.490 15048 15048 W dalvikvm: VFY:  rejecting opcode 0x0d at 0x002d
10-19 09:48:01.490 15048 15048 W dalvikvm: VFY:  rejected Lcom/example/myapp/LoginActivity;.initCipher ()Z
10-19 09:48:01.490 15048 15048 W dalvikvm: Verifier rejected class Lcom/example/myapp/LoginActivity;
10-19 09:48:01.490 15048 15048 W dalvikvm: Class init failed in newInstance call (Lcom/example/myapp/LoginActivity;)
10-19 09:48:01.490 15048 15048 D AndroidRuntime: Shutting down VM
10-19 09:48:01.490 15048 15048 W dalvikvm: threadid=1: thread exiting with uncaught exception (group=0x417d3da0)
10-19 09:48:01.490 15048 15048 E AndroidRuntime: FATAL EXCEPTION: main
10-19 09:48:01.490 15048 15048 E AndroidRuntime: Process: com.example.myapp, PID: 15048
10-19 09:48:01.490 15048 15048 E AndroidRuntime: java.lang.VerifyError: com/example/myapp/LoginActivity
10-19 09:48:01.490 15048 15048 E AndroidRuntime:    at java.lang.Class.newInstanceImpl(Native Method)
10-19 09:48:01.490 15048 15048 E AndroidRuntime:    at java.lang.Class.newInstance(Class.java:1208)
10-19 09:48:01.490 15048 15048 E AndroidRuntime:    at android.app.Instrumentation.newActivity(Instrumentation.java:1079)
10-19 09:48:01.490 15048 15048 E AndroidRuntime:    at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2199)
10-19 09:48:01.490 15048 15048 E AndroidRuntime:    at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2340)
10-19 09:48:01.490 15048 15048 E AndroidRuntime:    at android.app.ActivityThread.access$800(ActivityThread.java:157)
10-19 09:48:01.490 15048 15048 E AndroidRuntime:    at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1247)
10-19 09:48:01.490 15048 15048 E AndroidRuntime:    at android.os.Handler.dispatchMessage(Handler.java:102)
10-19 09:48:01.490 15048 15048 E AndroidRuntime:    at android.os.Looper.loop(Looper.java:157)
10-19 09:48:01.490 15048 15048 E AndroidRuntime:    at android.app.ActivityThread.main(ActivityThread.java:5293)
10-19 09:48:01.490 15048 15048 E AndroidRuntime:    at java.lang.reflect.Method.invokeNative(Native Method)
10-19 09:48:01.490 15048 15048 E AndroidRuntime:    at java.lang.reflect.Method.invoke(Method.java:515)
10-19 09:48:01.490 15048 15048 E AndroidRuntime:    at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:1265)
10-19 09:48:01.490 15048 15048 E AndroidRuntime:    at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1081)
10-19 09:48:01.490 15048 15048 E AndroidRuntime:    at dalvik.system.NativeStart.main(Native Method)  

I've tried putting explicit API checks on each declaration of every variable used in the process but that didn't work either. Any ideas?


回答1:


I was able to fix the problem by creating a separate class called BiometricLogin. (Call it whatever you like, obviously). I moved all the fingerprint/biometric logic from my LoginActivity to the new class, and implemented an interface that included an onLogin() method which I called from within the callback classes that were previously defined in LoginActivity. In the onCreate() of LoginActivity just do something like this:

if (bioEnabled) {
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
        mBiometricLogin = new BiometricLogin(this); // Pass in context to manipulate dialog view
        mBiometricLogin.setBiometricLoginCallback(this);
        mBiometricLogin.showDialog();
    }
}

Handle all the API checks in the BiometricLogin class and it only needs to communicate with LoginActivity when it's finished. And to simply handle lifecycle changes while the dialog is open, add this to onDestroy()

@Override
protected void onDestroy() {
    super.onDestroy();
    if (Build.VERSION.SDK_INT >= 23) {
        if (mBiometricLogin != null && mBiometricLogin.isShowing())
            mBiometricLogin.cancel();
    }
}

Hope this helps anyone who may run into this issue!



来源:https://stackoverflow.com/questions/52896090/authenticationcallback-and-keygenparameterspec-crashing-app-despite-checking-api

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!