Welcome to OStack Knowledge Sharing Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
343 views
in Technique[技术] by (71.8m points)

android - How to Use Unsupported Exception for Lower Platform Version

I have a DialogFragment that handles login and fingerprint authentication for my application. This fragment uses two classes that are exclusive to API 23, KeyGenParameterSpec and KeyPermanentlyInvalidatedException. I had been under the impression that I could use these classes, as long as I check the build version before I try to initialize the classes (outlined here):

if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
    ...
} else {
    ...
}

But it appears that this is not the case. If I try to run this code on a version prior to API 20, the Dalvik VM rejects the entire class and throws a VerifyError. Though, the code does work for API 20 and greater. How can I use these methods in my code while still allowing the code to be used for previous API levels?

The full stack trace is as follows:

05-31 14:35:50.924 11941-11941/com.example.app E/dalvikvm: Could not find class 'android.security.keystore.KeyGenParameterSpec$Builder', referenced from method com.example.app.ui.fragment.util.LoginFragment.createKeyPair
05-31 14:35:50.924 11941-11941/com.example.app W/dalvikvm: VFY: unable to resolve new-instance 263 (Landroid/security/keystore/KeyGenParameterSpec$Builder;) in Lcom/example/app/ui/fragment/util/LoginFragment;
05-31 14:35:50.924 11941-11941/com.example.app D/dalvikvm: VFY: replacing opcode 0x22 at 0x000c
05-31 14:35:50.924 11941-11941/com.example.app W/dalvikvm: VFY: unable to resolve exception class 265 (Landroid/security/keystore/KeyPermanentlyInvalidatedException;)
05-31 14:35:50.924 11941-11941/com.example.app W/dalvikvm: VFY: unable to find exception handler at addr 0x3f
05-31 14:35:50.924 11941-11941/com.example.app W/dalvikvm: VFY:  rejected Lcom/example/app/ui/fragment/util/LoginFragment;.initializeCipher (I)Z
05-31 14:35:50.924 11941-11941/cp W/dalvikvm: VFY:  rejecting opcode 0x0d at 0x003f
05-31 14:35:50.924 11941-11941/com.example.app W/dalvikvm: VFY:  rejected Lcom/example/app/ui/fragment/util/LoginFragment;.initializeCipher (I)Z
05-31 14:35:50.924 11941-11941/com.example.app W/dalvikvm: Verifier rejected class Lcom/example/app/ui/fragment/util/LoginFragment;
05-31 14:35:50.924 11941-11941/com.example.app D/AndroidRuntime: Shutting down VM
05-31 14:35:50.924 11941-11941/com.example.app W/dalvikvm: threadid=1: thread exiting with uncaught exception (group=0x9cca9b20)
05-31 14:35:50.934 11941-11941/com.example.app E/AndroidRuntime: FATAL EXCEPTION: main
        Process: com.example.app, PID: 11941 java.lang.VerifyError: com/example/app/ui/fragment/util/LoginFragment
            at com.example.app.util.NetworkUtility.login(NetworkUtility.java:41)
            at com.example.app.ui.activity.AbstractNavActivity.onOptionsItemSelected(AbstractNavActivity.java:68)
            at android.app.Activity.onMenuItemSelected(Activity.java:2600)
            at android.support.v4.app.FragmentActivity.onMenuItemSelected(FragmentActivity.java:403)
            at android.support.v7.app.AppCompatActivity.onMenuItemSelected(AppCompatActivity.java:189)
            at android.support.v7.view.WindowCallbackWrapper.onMenuItemSelected(WindowCallbackWrapper.java:100)
            at android.support.v7.view.WindowCallbackWrapper.onMenuItemSelected(WindowCallbackWrapper.java:100)
            at android.support.v7.app.ToolbarActionBar$2.onMenuItemClick(ToolbarActionBar.java:69)
            at android.support.v7.widget.Toolbar$1.onMenuItemClick(Toolbar.java:169)
            at android.support.v7.widget.ActionMenuView$MenuBuilderCallback.onMenuItemSelected(ActionMenuView.java:760)
            at android.support.v7.view.menu.MenuBuilder.dispatchMenuItemSelected(MenuBuilder.java:811)
            at android.support.v7.view.menu.MenuItemImpl.invoke(MenuItemImpl.java:152)
            at android.support.v7.view.menu.MenuBuilder.performItemAction(MenuBuilder.java:958)
            at android.support.v7.view.menu.MenuBuilder.performItemAction(MenuBuilder.java:948)
            at android.support.v7.view.menu.MenuPopupHelper.onItemClick(MenuPopupHelper.java:191)
            at android.widget.AdapterView.performItemClick(AdapterView.java:299)
            at android.widget.AbsListView.performItemClick(AbsListView.java:1113)
            at android.widget.AbsListView$PerformClick.run(AbsListView.java:2904)
            at android.widget.AbsListView$3.run(AbsListView.java:3638)
            at android.os.Handler.handleCallback(Handler.java:733)
            at android.os.Handler.dispatchMessage(Handler.java:95)
            at android.os.Looper.loop(Looper.java:136)
            at android.app.ActivityThread.main(ActivityThread.java:5017)
            at java.lang.reflect.Method.invokeNative(Native Method)
            at java.lang.reflect.Method.invoke(Method.java:515)
            at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:779)
            at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:595)
            at dalvik.system.NativeStart.main(Native Method)

Updated with Code

The login() method is just a convenience method to start the LoginFragment:

public static void login(FragmentManager manager) {
     manager.beginTransAction().add(LoginFragment.newInstance(), null).commit();
}

The relevant code is in the LoginFragment itself. Specifically the createKeyPair() and initializeCipher methods:

public class LoginFragment extends DialogFragment
        implements TextView.OnEditorActionListener, FingerprintCallback.Callback {

    ...

    public static LoginFragment newInstance() {
        return newInstance(null);
    }

    public static LoginFragment newInstance(Intent intent) {
        LoginFragment fragment = new LoginFragment();

        Bundle args = new Bundle();
        args.putParcelable(EXTRA_INTENT, intent);
        fragment.setArguments(args);

        return fragment;
    }

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        Injector.getContextComponent().inject(this);
        setStyle(STYLE_NO_TITLE, R.style.DialogTheme);
        setRetainInstance(true);
        setCancelable(false);

        mSaveUsernamePreference = mPreferences.getBoolean(getString(R.string.key_auth_username_retain));
        mUseFingerprintPreference = mPreferences.getBoolean(getString(R.string.key_auth_fingerprint));
        mUsernamePreference = mPreferences.getString(getString(R.string.key_auth_username));
        mPasswordPreference = mPreferences.getString(getString(R.string.key_auth_password));
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {
        View view = inflater.inflate(R.layout.dialog_login_container, container, false);
        ButterKnife.bind(this, view);

        mPasswordView.setOnEditorActionListener(this);

        if(!mFingerprintManager.isHardwareDetected()) {
            mUseFingerprintToggle.setVisibility(View.GONE);
        } else {
            mGenerated = initializeKeyPair(false);
        }

        if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
            setStage(isFingerprintAvailable() ? Stage.FINGERPRINT : Stage.CREDENTIALS);
        } else {
            setStage(Stage.CREDENTIALS);
        }

        return view;
    }

    @Override
    public void onResume() {
        super.onResume();

        ...

        if(mStage == Stage.FINGERPRINT && Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
            startListening(initializeCipher(Cipher.DECRYPT_MODE));
        }
    }

    @Override
    public void onPause() {
        super.onPause();
        stopListening();
    }

    ...

    @Override
    public void onAuthenticationSucceeded(FingerprintManagerCompat.AuthenticationResult result) {
        Timber.i("Fingerprint succeeded");
        showFingerprintSuccess();

        mSubscriptions.add(
            mGenerated.subscribeOn(Schedulers.newThread())
                    .observeOn(AndroidSchedulers.mainThread())
                    .doOnCompleted(() -> {
                        try {
                            mUsername = mUsernamePreference.get();
                            mPassword = decryptPassword(result.getCryptoObject().getCipher());
                            initLoginAttempt();
                        } catch (IllegalBlockSizeException | BadPaddingException exception) {
                            Timber.e(exception, "Failed to decrypt password");
                        }
                    }).subscribe());
    }

    @Override
    public void onAuthenticationHelp(int messageId, CharSequence message) {
        Timber.i("Fingerprint help id: " + messageId + " message: " + message);
        showFingerprintError(message);
    }

    @Override
    public void onAuthenticationError(int messageId, CharSequence message) {
        Timber.i("Fingerprint error id: " + messageId + " message: " + message);
        if(messageId != 5) {
            showFingerprintError(message);
        }
    }

    @Override
    public void onAuthenticationFailed() {
        Timber.i("Fingerprint failed");
        showFingerprintError(getResources().getString(R.string.msg_fingerprint_error_unknown));
    }

    @OnClick(R.id.button_cancel)
    public void onCancel() {
        dismiss();
    }

    @OnClick(R.id.button_continue)
    public void onContinue() {
        switch (mStage) {
            case CREDENTIALS:
                mUsername = mUsernameView.getText().toString();
                mPassword = mPasswordView.getText().toString();
                initLoginAttempt();
                break;
            case FINGERPRINT:
                setStage(Stage.CREDENTIALS);
                break;
        }
    }

    private void showFingerprintSuccess() {
        int colorAccent = ThemeUtil.getColorAttribute(getContext(), android.R.attr.colorAccent);
        mFingerprintIcon.setImageResource(R.drawable.ic_done_white_24dp);
        mFingerprintIcon.setCircleColor(colorAccent);
        mFingerprintStatus.setText(R.string.msg_fingerprint_success);
        mFingerprintStatus.setTextColor(colorAccent);
    }

    private void showFingerprintError(CharSequence message) {
        int colorError = ContextCompat.getColor(getContext(), R.color.material_deep_orange_600);
        mFingerprintIcon.setImageResource(R.drawable.ic_priority_high_white_24dp);
        mFingerprintIcon.setCircleColor(colorError);
        mFingerprintStatus.setText(message);
        mFingerprintStatus.setTextColor(colorError);
        resetFingerprintStatus();
    }

    private void resetFingerprintStatus() {
        mSubscriptions.add(Observable.timer(1600, TimeUnit.MILLISECONDS)
                .subscribeOn(Schedulers.newThread())
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe(finished -> {
                    mFingerprintIcon.setImageResource(R.drawable.ic_fingerprint_white_24dp);
                    mFingerprintIcon.setCircleColor(ContextCompat
                            .getColor(getContext(), R.color.material_blue_gray_500));
                    mFingerprintStatus.setText(R.string.msg_fingerprint_input);
                    mFingerprintStatus.setTextColor(ThemeUtil
                            .getColorAttribute(getContext(), android.R.attr.textColorHint));
                }));
    }

    private void onSaveUsernameChanged(boo

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Answer

0 votes
by (71.8m points)

My guess is that either FingerprintCallback.Callback extends an API Level 23+ interface or that LoginFragment has fields that reference API Level 23+ stuff.

Your rule about being able to call API Level 23+ methods safely inside the version guard block is correct. However, you cannot:

  • inherit from classes that do not exist on the device
  • implement interfaces that do not exist on the device
  • have fields whose types do not exist on the device
  • accept constructor or method parameters whose types do not exist on the device (where we actually call these)
  • have method return values whose types do not exist on the device (where we actually call these)

In many cases, we don't need any of that, in which case just checking Build.VERSION.SDK_INT before calling API Level 23+ methods is sufficient.

If you need to do some of the things in the bulleted list, that's fine, but then you need to isolate those into classes that you only use on API Level 23+ devices.

So, for example, let's pretend that the problem is that FingerprintCallback.Callback extends some API Level 23+ interface. Rather than implementing FingerprintCallback.Callback on the LoginFragment, you might implement that as an anonymous inner class, and only execute the code creating that anonymous inner class instance if Build.VERSION.SDK_INT is high enough. Then, you only are referencing FingerprintCallback.Callback on the newer devices, and you should be safe.


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome to OStack Knowledge Sharing Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...