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
与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…