title: How to Use Unsupported Exception for Lower Platform Version
body: <p>I have a <code>DialogFragment</code> that handles login and fingerprint authentication for my application. This fragment uses two classes that are exclusive to API 23, <code>KeyGenParameterSpec</code> and <code>KeyPermanentlyInvalidatedException</code>. 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 <a href="https://developer.android.com/training/basics/supporting-devices/platforms.html" rel="noreferrer">here</a>):</p>
  
  <pre><code>if(Build.VERSION.SDK_INT &gt;= Build.VERSION_CODES.M) {
      ...
  } else {
      ...
  }
  </code></pre>
  
  <p>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 <code>VerifyError</code>. 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?</p>
  
  <p>The full stack trace is as follows:    </p>
  
  <pre><code>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)
  </code></pre>
  
  <hr>
  
  <p><strong>Updated with Code</strong></p>
  
  <p>The <code>login()</code> method is just a convenience method to start the <code>LoginFragment</code>:</p>
  
  <pre><code>public static void login(FragmentManager manager) {
       manager.beginTransAction().add(LoginFragment.newInstance(), null).commit();
  }
  </code></pre>
  
  <p>The relevant code is in the <code>LoginFragment</code> itself. Specifically the <code>createKeyPair()</code> and <code>initializeCipher</code> methods:</p>
  
  <pre><code>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 &gt;= 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 &amp;&amp; Build.VERSION.SDK_INT &gt;= 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(() -&gt; {
                          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 -&gt; {
                      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(boolean checked) {
          if(!checked) {
              mUseFingerprintToggle.setChecked(false);
          }
      }
  
      private void onUseFingerprintChanged(boolean checked) {
          if(checked) {
              mSaveUsernameToggle.setChecked(true);
  
              if(!mFingerprintManager.hasEnrolledFingerprints()) {
                  displaySettingsDialog();
                  mUseFingerprintToggle.setChecked(false);
              }
          }
      }
  
      public void setStage(Stage stage) {
          switch (stage) {
              case CREDENTIALS:
                  Timber.d("Set stage Credentials");
                  mPositiveButton.setText(R.string.btn_login);
                  mFingerprintContent.setVisibility(View.GONE);
                  mCredentialContent.setVisibility(View.VISIBLE);
                  setForm();
                  break;
              case FINGERPRINT:
                  mPositiveButton.setText(R.string.btn_password);
                  mCredentialContent.setVisibility(View.GONE);
                  mFingerprintContent.setVisibility(View.VISIBLE);
                  break;
          } mStage = stage;
      }
  
      private void startListening(boolean cipher) {
          Timber.v("Start listening for fingerprint input");
          mCancellationSignal = new CancellationSignal();
          if(cipher) {
              mFingerprintManager.authenticate(new FingerprintManagerCompat.CryptoObject(mCipher),
                      0, mCancellationSignal, new FingerprintCallback(this), null);
          } else {
              setStage(Stage.CREDENTIALS);
          }
      }
  
      private void stopListening() {
          if(mCancellationSignal != null) {
              mCancellationSignal.cancel();
              mCancellationSignal = null;
          }
      }
  
      private void setForm() {
          if(mSaveUsernamePreference.isSet() &amp;&amp; mSaveUsernamePreference.get()
                  &amp;&amp; mUsernamePreference.isSet()) {
              mUsernameView.setText(mUsernamePreference.get());
              mUsernameView.setSelectAllOnFocus(true);
              mPasswordView.requestFocus();
          } else {
              mUsernameView.requestFocus();
          }
      }
  
      public void initLoginAttempt() {
          mProgressBar.setVisibility(View.VISIBLE);
          mAuthenticationService.getLoginForm().subscribeOn(Schedulers.newThread())
                  .observeOn(AndroidSchedulers.mainThread())
                  .subscribe(this::onLoginFormResponse, this::onError);
      }
  
      private void onLoginFormResponse(ResponseBody response) {
          try {
              attemptLogin(LoginForm.parse(response.string()));
          } catch (IOException exception) {
              Timber.w(exception, "Failed to parse login form");
          }
      }
  
      private void attemptLogin(LoginForm loginForm) {
          mAuthenticationService
                  .login(loginForm.getLoginTicket(), loginForm.getExecution(), loginForm.getEventIdentifier(),
                          mUsername, mPassword, loginForm.getSubmitValue())
                  .subscribeOn(Schedulers.newThread())
                  .observeOn(AndroidSchedulers.mainThread())
                  .subscribe(this::onLoginResponse, this::onError);
      }
  
      public void onLoginResponse(ResponseBody response) {
          Timber.d("LOGIN RESPONSE");
          try {
              Timber.d(response.string());
          } catch (IOException exception) {
              Timber.w(exception, "Failed to retrieve attemptLogin response");
          }
  
          mSubscriptions.add(NetworkUtility.getAuthentication()
                  .subscribeOn(Schedulers.newThread())
                  .observeOn(AndroidSchedulers.mainThread())
                  .subscribe(this::onAuthenticationChanged, this::onError));
      }
  
      public void onAuthenticationChanged(Boolean authenticated) {
          if(authenticated) {
              Timber.d("Authentication success");
  
              if(mStage == Stage.CREDENTIALS) {
                  if (mSaveUsernameToggle.isChecked()) {
                      storeUsername();
                  } else {
                      clearUsername();
                  }
  
                  if (mUseFingerprintToggle.isChecked()) {
                      mGenerated = initializeKeyPair(true);
                      storePassword();
                  } else {
                      clearPassword();
                      finishIntent();
                  }
              } else {
                  finishIntent();
              }
          } else {
              Timber.d("Authentication failed");
              setStage(Stage.CREDENTIALS);
              mCaptionView.setTextColor(ContextCompat.getColor(getContext(), R.color.material_deep_orange_600));
              mCaptionView.setText(getString(R.string.msg_login_failed));
              mPasswordView.setText("");
          }
      }
  
      private void finishIntent() {
          mProgressBar.setVisibility(View.INVISIBLE);
          Intent intent = getArguments().getParcelable(EXTRA_INTENT);
          if(intent != null) {
              startActivity(intent);
          } dismiss();
      }
  
      private void onError(Throwable throwable) {
          Timber.w(throwable, "Login attempt failed");
          mProgressBar.setVisibility(View.INVISIBLE);
          mCaptionView.setTextColor(ContextCompat.getColor(getContext(), R.color.material_deep_orange_600));
          mCaptionView.setText("Login attempt failed\nPlease check your internet connection and try again");
          mPasswordView.setText("");
      }
  
      private void storeUsername() {
          String username = mUsernameView.getText().toString();
          mUsernamePreference.set(username);
          if(mPreferences.getBoolean(getString(R.string.key_auth_push), false).get()) {
              UAirship.shared().getPushManager().getNamedUser().setId(username);
          }
      }
  
      private void clearUsername() {
          UAirship.shared().getPushManager().getNamedUser().setId(null);
          mUsernamePreference.delete();
      }
  
      private void storePassword() {
          Timber.d("STORE PASSWORD");
          mSubscriptions.add(mGenerated.subscribeOn(Schedulers.newThread())
                  .observeOn(AndroidSchedulers.mainThread())
                  .doOnCompleted(() -&gt; {
                      try {
                          Timber.d("Store password");
                          initializeCipher(Cipher.ENCRYPT_MODE);
  
                          String password = mPasswordView.getText().toString();
                          byte[] bytes = password.getBytes();
                          byte[] encrypted = mCipher.doFinal(bytes);
                          String encoded = Base64.encodeToString(encrypted, Base64.NO_WRAP);
  
                          mPasswordPreference.set(encoded);
  
                          finishIntent();
  
                      } catch (IllegalBlockSizeException | BadPaddingException exception) {
                          Timber.e(exception, "Failed to encrypt password");
                      }
                  }).subscribe());
      }
  
      private String decryptPassword(Cipher cipher) throws IllegalBlockSizeException, BadPaddingException {
          String encoded = mPasswordPreference.get();
  
          Timber.d("ENCODED STRING " + encoded);
  
          byte[] encrypted = Base64.decode(encoded, Base64.NO_WRAP);
  
          byte[] bytes = cipher.doFinal(encrypted);
  
          return new String(bytes);
      }
  
      private void clearPassword() {
          mPasswordPreference.delete();
      }
  
      private boolean isFingerprintAvailable() {
          return mUseFingerprintPreference.isSet() &amp;&amp; mUseFingerprintPreference.get()
                  &amp;&amp; mFingerprintManager.hasEnrolledFingerprints()
                  &amp;&amp; mSaveUsernamePreference.isSet()
                  &amp;&amp; mPasswordPreference.isSet();
      }
  
      private void displaySettingsDialog() {
          new AlertDialog.Builder(getContext())
                  .setTitle(R.string.title_dialog_secure_lock)
                  .setMessage(R.string.msg_fingerprint_unavailable)
                  .setPositiveButton(R.string.btn_settings, (dialog, which) -&gt; {
                      startActivity(new Intent(android.provider.Settings.ACTION_SECURITY_SETTINGS));
                      dialog.dismiss();
                  }).setNegativeButton(R.string.btn_cancel, (dialog, which) -&gt; {
              dialog.dismiss();
          }).create().show();
      }
  
      @TargetApi(Build.VERSION_CODES.M)
      private boolean initializeCipher(int opmode) {
          try {
              mKeyStore.load(null);
  
              /**
               * A known bug in the Android 6.0 (API Level 23) implementation of Bouncy Castle
               * RSA OAEP causes the cipher to default to an SHA-1 certificate, making the SHA-256
               * certificate of the public key incompatible
               * To work around this issue, explicitly provide a new OAEP specification upon
               * initialization
               * @see &lt;a href="https://code.google.com/p/android/issues/detail?id=197719"&gt;Issue 197719&lt;/a&gt;
               */
              AlgorithmParameterSpec spec = generateOAEPParameterSpec();
              Key key;
  
              if(opmode == Cipher.ENCRYPT_MODE) {
                  Key publicKey = mKeyStore.getCertificate(CIPHER_KEY_ALIAS).getPublicKey();
  
                  /**
                   * A known bug in Android 6.0 (API Level 23) causes user authentication-related
                   * authorizations to be enforced even for public keys
                   * To work around this issue, extract the public key material to use outside of
                   * the Android Keystore
                   * @see &lt;a href="http://developer.android.com/reference/android/security/keystore/KeyGenParameterSpec.html"&gt;KeyGenParameterSpec Known Issues&lt;/a&gt;
                   */
                  key = KeyFactory.getInstance(publicKey.getAlgorithm())
                          .generatePublic(new X509EncodedKeySpec(publicKey.getEncoded()));
              } else {
                  key = mKeyStore.getKey(CIPHER_KEY_ALIAS, null);
              }
  
              mCipher.init(opmode, key, spec);
              return true;
          } catch (KeyPermanentlyInvalidatedException exception) {
              Timber.w(exception, "Failed to initialize Cipher");
              handleKeyPermanentlyInvalidated();
              return false;
          } catch (IOException | KeyStoreException | UnrecoverableEntryException
                  | InvalidKeySpecException | CertificateException | InvalidKeyException
                  | NoSuchAlgorithmException | InvalidAlgorithmParameterException exception) {
              throw new RuntimeException("Failed to initialize Cipher", exception);
          }
      }
  
      private OAEPParameterSpec generateOAEPParameterSpec() {
          return new OAEPParameterSpec("SHA-256", "MGF1", MGF1ParameterSpec.SHA1, PSource.PSpecified.DEFAULT);
      }
  
      private void handleKeyPermanentlyInvalidated() {
          mCaptionView.setText(getString(R.string.msg_fingerprint_invalidated));
          mGenerated = initializeKeyPair(true);
          clearPassword();
      }
  
      private Observable&lt;KeyPair&gt; initializeKeyPair(boolean generate) {
          return Observable.create(subscriber -&gt; {
              try {
                  mKeyStore.load(null);
  
                  if(!generate || mKeyStore.containsAlias(CIPHER_KEY_ALIAS)) {
                      PublicKey publicKey = mKeyStore.getCertificate(CIPHER_KEY_ALIAS).getPublicKey();
                      PrivateKey privateKey = (PrivateKey) mKeyStore.getKey(CIPHER_KEY_ALIAS, null);
                      subscriber.onNext(new KeyPair(publicKey, privateKey));
                  } else {
                      subscriber.onNext(createKeyPair());
                  }
  
                  subscriber.onCompleted();
              } catch (IOException | KeyStoreException | UnrecoverableKeyException
                      | CertificateException | NoSuchAlgorithmException
                      | InvalidAlgorithmParameterException exception) {
                  Timber.e(exception, "Failed to generate key pair");
                  subscriber.onError(exception);
              }
          });
      }
  
      @TargetApi(Build.VERSION_CODES.M)
      private KeyPair createKeyPair() throws InvalidAlgorithmParameterException {
          // Set the alias of the entry in Android KeyStore where the key will appear
          // and the constrains (purposes) in the constructor of the Builder
          Timber.d("Initialize key pair");
          mKeyPairGenerator.initialize(
                  new KeyGenParameterSpec.Builder(CIPHER_KEY_ALIAS, KeyProperties.PURPOSE_DECRYPT)
                      .setDigests(KeyProperties.DIGEST_SHA256, KeyProperties.DIGEST_SHA512)
                      .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_RSA_OAEP)
                          .setUserAuthenticationRequired(true)
                          .build());
  
          return mKeyPairGenerator.generateKeyPair();
      }
  
  }
  </code></pre>
  
  <hr>
  
  <p><strong>Update</strong></p>
  
  <p>Okay, so I figured out it is the <code>KeyPermanentlyInvalidatedException</code> that is causing the error. If I comment out the <code>catch</code> block that handles that exception, the code runs fine on any device. The problem is that I need to be able to handle that exception on devices on API 23+:</p>
  
  <pre><code>catch (KeyPermanentlyInvalidatedException exception) {
      Timber.w(exception, "A new fingerprint was added to the device");
      handleKeyPermanentlyInvalidated();
      return false;
  }
  </code></pre>