@Test
  public void testPromoteDivert() throws Exception {
    PromoteKeyOperation op =
        new PromoteKeyOperation(
            RuntimeEnvironment.application,
            new ProviderHelper(RuntimeEnvironment.application),
            null,
            null);

    byte[] aid = Hex.decode("D2760001240102000000012345670000");

    PromoteKeyResult result =
        op.execute(new PromoteKeyringParcel(mStaticRing.getMasterKeyId(), aid, null), null);

    Assert.assertTrue("promotion must succeed", result.success());

    {
      CanonicalizedSecretKeyRing ring =
          new ProviderHelper(RuntimeEnvironment.application)
              .getCanonicalizedSecretKeyRing(mStaticRing.getMasterKeyId());

      for (CanonicalizedSecretKey key : ring.secretKeyIterator()) {
        Assert.assertEquals(
            "all subkeys must be divert-to-card",
            SecretKeyType.DIVERT_TO_CARD,
            key.getSecretKeyTypeSuperExpensive());
        Assert.assertArrayEquals("all subkeys must have correct iv", aid, key.getIv());
      }
    }
  }
  @Test
  /**
   * Tests a master key which may sign, but is stripped. In this case, if there is a different
   * subkey available which can sign, that one should be selected.
   */
  public void testImportStrippedFlags() throws Exception {

    UncachedKeyRing key = readRingFromResource("/test-keys/stripped_flags.asc");
    long masterKeyId = key.getMasterKeyId();

    SaveKeyringResult result;

    result = mProviderHelper.saveSecretKeyRing(key, new ProgressScaler());
    Assert.assertTrue("import of keyring should succeed", result.success());

    long signId;
    {
      CanonicalizedSecretKeyRing ring = mProviderHelper.getCanonicalizedSecretKeyRing(masterKeyId);
      Assert.assertTrue("master key should have sign flag", ring.getPublicKey().canSign());
      Assert.assertTrue("master key should have encrypt flag", ring.getPublicKey().canEncrypt());

      signId = ring.getSecretSignId();
      Assert.assertNotEquals("encrypt id should not be 0", 0, signId);
      Assert.assertNotEquals(
          "encrypt key should be different from master key", masterKeyId, signId);
    }

    {
      CachedPublicKeyRing ring = mProviderHelper.getCachedPublicKeyRing(masterKeyId);
      Assert.assertEquals(
          "signing key should be same id cached as uncached", signId, ring.getSecretSignId());
    }
  }
  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);

    // do not allow screenshots of passphrase input
    // to prevent "too easy" passphrase theft by root apps
    if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.HONEYCOMB) {
      getWindow()
          .setFlags(WindowManager.LayoutParams.FLAG_SECURE, WindowManager.LayoutParams.FLAG_SECURE);
    }

    CryptoInputParcel cryptoInputParcel = getIntent().getParcelableExtra(EXTRA_CRYPTO_INPUT);
    if (cryptoInputParcel == null) {
      cryptoInputParcel = new CryptoInputParcel();
      getIntent().putExtra(EXTRA_CRYPTO_INPUT, cryptoInputParcel);
    }

    // this activity itself has no content view (see manifest)
    RequiredInputParcel requiredInput = getIntent().getParcelableExtra(EXTRA_REQUIRED_INPUT);
    if (requiredInput.mType != RequiredInputType.PASSPHRASE) {
      return;
    }

    // handle empty passphrases by directly returning an empty crypto input parcel
    try {
      CanonicalizedSecretKeyRing pubRing =
          new ProviderHelper(this).getCanonicalizedSecretKeyRing(requiredInput.getMasterKeyId());
      // use empty passphrase for empty passphrase
      if (pubRing.getSecretKey(requiredInput.getSubKeyId()).getSecretKeyType()
          == SecretKeyType.PASSPHRASE_EMPTY) {
        // also return passphrase back to activity
        Intent returnIntent = new Intent();
        cryptoInputParcel.mPassphrase = new Passphrase("");
        returnIntent.putExtra(RESULT_CRYPTO_INPUT, cryptoInputParcel);
        setResult(RESULT_OK, returnIntent);
        finish();
      }
    } catch (NotFoundException e) {
      Log.e(Constants.TAG, "Key not found?!", e);
      setResult(RESULT_CANCELED);
      finish();
    }
  }
  @Test
  public void testPromoteDivertSpecific() throws Exception {
    PromoteKeyOperation op =
        new PromoteKeyOperation(
            RuntimeEnvironment.application,
            new ProviderHelper(RuntimeEnvironment.application),
            null,
            null);

    byte[] aid = Hex.decode("D2760001240102000000012345670000");

    // only promote the first, rest stays dummy
    long keyId = KeyringTestingHelper.getSubkeyId(mStaticRing, 1);

    PromoteKeyResult result =
        op.execute(
            new PromoteKeyringParcel(mStaticRing.getMasterKeyId(), aid, new long[] {keyId}), null);

    Assert.assertTrue("promotion must succeed", result.success());

    {
      CanonicalizedSecretKeyRing ring =
          new ProviderHelper(RuntimeEnvironment.application)
              .getCanonicalizedSecretKeyRing(mStaticRing.getMasterKeyId());

      for (CanonicalizedSecretKey key : ring.secretKeyIterator()) {
        if (key.getKeyId() == keyId) {
          Assert.assertEquals(
              "subkey must be divert-to-card",
              SecretKeyType.DIVERT_TO_CARD,
              key.getSecretKeyTypeSuperExpensive());
          Assert.assertArrayEquals("subkey must have correct iv", aid, key.getIv());
        } else {
          Assert.assertEquals(
              "some subkeys must be gnu dummy",
              SecretKeyType.GNU_DUMMY,
              key.getSecretKeyTypeSuperExpensive());
        }
      }
    }
  }
    @NonNull
    @Override
    public Dialog onCreateDialog(Bundle savedInstanceState) {
      final Activity activity = getActivity();

      ContextThemeWrapper theme = ThemeChanger.getDialogThemeWrapper(activity);

      mRequiredInput = getArguments().getParcelable(EXTRA_REQUIRED_INPUT);

      CustomAlertDialogBuilder alert = new CustomAlertDialogBuilder(theme);

      // No title, see http://www.google.com/design/spec/components/dialogs.html#dialogs-alerts
      // alert.setTitle()

      if (mRequiredInput.mType == RequiredInputType.BACKUP_CODE) {
        LayoutInflater inflater = LayoutInflater.from(theme);
        View view = inflater.inflate(R.layout.passphrase_dialog_backup_code, null);
        alert.setView(view);

        mBackupCodeEditText = new EditText[4];
        mBackupCodeEditText[0] = (EditText) view.findViewById(R.id.backup_code_1);
        mBackupCodeEditText[1] = (EditText) view.findViewById(R.id.backup_code_2);
        mBackupCodeEditText[2] = (EditText) view.findViewById(R.id.backup_code_3);
        mBackupCodeEditText[3] = (EditText) view.findViewById(R.id.backup_code_4);
        setupEditTextFocusNext(mBackupCodeEditText);

        AlertDialog dialog = alert.create();
        dialog.setButton(
            DialogInterface.BUTTON_POSITIVE,
            activity.getString(R.string.btn_unlock),
            (DialogInterface.OnClickListener) null);
        return dialog;
      }

      long subKeyId = mRequiredInput.getSubKeyId();

      LayoutInflater inflater = LayoutInflater.from(theme);
      mLayout = (ViewAnimator) inflater.inflate(R.layout.passphrase_dialog, null);
      alert.setView(mLayout);

      mPassphraseText = (TextView) mLayout.findViewById(R.id.passphrase_text);
      mPassphraseEditText = (EditText) mLayout.findViewById(R.id.passphrase_passphrase);

      View vTimeToLiveLayout = mLayout.findViewById(R.id.remember_layout);
      vTimeToLiveLayout.setVisibility(mRequiredInput.mSkipCaching ? View.GONE : View.VISIBLE);

      mTimeToLiveSpinner = (CacheTTLSpinner) mLayout.findViewById(R.id.ttl_spinner);

      alert.setNegativeButton(
          android.R.string.cancel,
          new DialogInterface.OnClickListener() {

            @Override
            public void onClick(DialogInterface dialog, int id) {
              dialog.cancel();
            }
          });

      String userId;
      CanonicalizedSecretKey.SecretKeyType keyType =
          CanonicalizedSecretKey.SecretKeyType.PASSPHRASE;

      String message;
      String hint;
      if (mRequiredInput.mType == RequiredInputType.PASSPHRASE_SYMMETRIC) {
        message = getString(R.string.passphrase_for_symmetric_encryption);
        hint = getString(R.string.label_passphrase);
      } else {
        try {
          ProviderHelper helper = new ProviderHelper(activity);
          mSecretRing =
              helper.getCanonicalizedSecretKeyRing(
                  KeychainContract.KeyRings.buildUnifiedKeyRingsFindBySubkeyUri(subKeyId));
          // yes the inner try/catch block is necessary, otherwise the final variable
          // above can't be statically verified to have been set in all cases because
          // the catch clause doesn't return.
          try {
            String mainUserId = mSecretRing.getPrimaryUserIdWithFallback();
            KeyRing.UserId mainUserIdSplit = KeyRing.splitUserId(mainUserId);
            if (mainUserIdSplit.name != null) {
              userId = mainUserIdSplit.name;
            } else {
              userId = getString(R.string.user_id_no_name);
            }
          } catch (PgpKeyNotFoundException e) {
            userId = null;
          }

          keyType = mSecretRing.getSecretKey(subKeyId).getSecretKeyType();
          switch (keyType) {
            case PASSPHRASE:
              message = getString(R.string.passphrase_for, userId);
              hint = getString(R.string.label_passphrase);
              break;
            case PIN:
              message = getString(R.string.pin_for, userId);
              hint = getString(R.string.label_pin);
              break;
            case DIVERT_TO_CARD:
              message = getString(R.string.yubikey_pin_for, userId);
              hint = getString(R.string.label_pin);
              break;
              // special case: empty passphrase just returns the empty passphrase
            case PASSPHRASE_EMPTY:
              finishCaching(new Passphrase(""));
            default:
              throw new AssertionError("Unhandled SecretKeyType (should not happen)");
          }

        } catch (ProviderHelper.NotFoundException e) {
          alert.setTitle(R.string.title_key_not_found);
          alert.setMessage(getString(R.string.key_not_found, mRequiredInput.getSubKeyId()));
          alert.setPositiveButton(
              android.R.string.ok,
              new DialogInterface.OnClickListener() {
                public void onClick(DialogInterface dialog, int which) {
                  dismiss();
                }
              });
          alert.setCancelable(false);
          return alert.create();
        }
      }

      mPassphraseText.setText(message);
      mPassphraseEditText.setHint(hint);

      // Hack to open keyboard.
      // This is the only method that I found to work across all Android versions
      // http://turbomanage.wordpress.com/2012/05/02/show-soft-keyboard-automatically-when-edittext-receives-focus/
      // Notes: * onCreateView can't be used because we want to add buttons to the dialog
      //        * opening in onActivityCreated does not work on Android 4.4
      mPassphraseEditText.setOnFocusChangeListener(
          new View.OnFocusChangeListener() {
            @Override
            public void onFocusChange(View v, boolean hasFocus) {
              mPassphraseEditText.post(
                  new Runnable() {
                    @Override
                    public void run() {
                      if (getActivity() == null || mPassphraseEditText == null) {
                        return;
                      }
                      InputMethodManager imm =
                          (InputMethodManager)
                              getActivity().getSystemService(Context.INPUT_METHOD_SERVICE);
                      imm.showSoftInput(mPassphraseEditText, InputMethodManager.SHOW_IMPLICIT);
                    }
                  });
            }
          });
      mPassphraseEditText.requestFocus();

      mPassphraseEditText.setImeActionLabel(
          getString(android.R.string.ok), EditorInfo.IME_ACTION_DONE);
      mPassphraseEditText.setOnEditorActionListener(this);

      if ((keyType == CanonicalizedSecretKey.SecretKeyType.DIVERT_TO_CARD
              && Preferences.getPreferences(activity).useNumKeypadForYubiKeyPin())
          || keyType == CanonicalizedSecretKey.SecretKeyType.PIN) {
        mPassphraseEditText.setInputType(InputType.TYPE_CLASS_NUMBER);
        mPassphraseEditText.setTransformationMethod(PasswordTransformationMethod.getInstance());
      } else {
        mPassphraseEditText.setRawInputType(
            InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_PASSWORD);
      }

      mPassphraseEditText.setTransformationMethod(PasswordTransformationMethod.getInstance());

      AlertDialog dialog = alert.create();
      dialog.setButton(
          DialogInterface.BUTTON_POSITIVE,
          activity.getString(R.string.btn_unlock),
          (DialogInterface.OnClickListener) null);

      return dialog;
    }
  @Test
  public void testImportDivertToCard() throws Exception {

    UncachedKeyRing sec = readRingFromResource("/test-keys/divert_to_card_sec.asc");
    long keyId = sec.getMasterKeyId();

    SaveKeyringResult result;

    result = mProviderHelper.saveSecretKeyRing(sec, new ProgressScaler());
    Assert.assertTrue("import of secret keyring should succeed", result.success());

    // make sure both the CanonicalizedSecretKeyRing as well as the CachedPublicKeyRing correctly
    // indicate the secret key type
    CachedPublicKeyRing cachedRing = mProviderHelper.getCachedPublicKeyRing(keyId);
    CanonicalizedSecretKeyRing secRing = mProviderHelper.getCanonicalizedSecretKeyRing(keyId);

    Iterator<CanonicalizedSecretKey> it = secRing.secretKeyIterator().iterator();

    { // first subkey
      Assert.assertTrue("keyring should have 3 subkeys (1)", it.hasNext());
      CanonicalizedSecretKey key = it.next();
      Assert.assertEquals(
          "first subkey should be of type sign+certify",
          KeyFlags.CERTIFY_OTHER | KeyFlags.SIGN_DATA,
          (int) key.getKeyUsage());
      Assert.assertEquals(
          "first subkey should be divert-to-card",
          SecretKeyType.DIVERT_TO_CARD,
          key.getSecretKeyType());
      Assert.assertTrue("canCertify() should be true", key.canCertify());
      Assert.assertTrue("canSign() should be true", key.canSign());

      // cached
      Assert.assertEquals(
          "all subkeys from CachedPublicKeyRing should be divert-to-key",
          SecretKeyType.DIVERT_TO_CARD,
          cachedRing.getSecretKeyType(key.getKeyId()));
    }

    { // second subkey
      Assert.assertTrue("keyring should have 3 subkeys (2)", it.hasNext());
      CanonicalizedSecretKey key = it.next();
      Assert.assertEquals(
          "second subkey should be of type authenticate",
          KeyFlags.AUTHENTICATION,
          (int) key.getKeyUsage());
      Assert.assertEquals(
          "second subkey should be divert-to-card",
          SecretKeyType.DIVERT_TO_CARD,
          key.getSecretKeyType());
      Assert.assertTrue("canAuthenticate() should be true", key.canAuthenticate());

      // cached
      Assert.assertEquals(
          "all subkeys from CachedPublicKeyRing should be divert-to-key",
          SecretKeyType.DIVERT_TO_CARD,
          cachedRing.getSecretKeyType(key.getKeyId()));
    }

    { // third subkey
      Assert.assertTrue("keyring should have 3 subkeys (3)", it.hasNext());
      CanonicalizedSecretKey key = it.next();
      Assert.assertEquals(
          "first subkey should be of type encrypt (both types)",
          KeyFlags.ENCRYPT_COMMS | KeyFlags.ENCRYPT_STORAGE,
          (int) key.getKeyUsage());
      Assert.assertEquals(
          "third subkey should be divert-to-card",
          SecretKeyType.DIVERT_TO_CARD,
          key.getSecretKeyType());
      Assert.assertTrue("canEncrypt() should be true", key.canEncrypt());

      // cached
      Assert.assertEquals(
          "all subkeys from CachedPublicKeyRing should be divert-to-key",
          SecretKeyType.DIVERT_TO_CARD,
          cachedRing.getSecretKeyType(key.getKeyId()));
    }

    Assert.assertFalse("keyring should have 3 subkeys (4)", it.hasNext());
  }