diff options
author | Alex Fong <alexfongg@gmail.com> | 2016-03-21 21:37:19 +0800 |
---|---|---|
committer | Alex Fong <alexfongg@gmail.com> | 2016-04-01 23:12:11 +0800 |
commit | cb9bdb3cf72ec387e6f34d29a31e701f5f5e7d92 (patch) | |
tree | 334ae1c8bb7cb959d61b9b67d1cee9fc8d24a792 /OpenKeychain/src/main/java/org | |
parent | ce5e5f36c56fe14cb4a90a912eece07e4cb92285 (diff) | |
download | open-keychain-cb9bdb3cf72ec387e6f34d29a31e701f5f5e7d92.tar.gz open-keychain-cb9bdb3cf72ec387e6f34d29a31e701f5f5e7d92.tar.bz2 open-keychain-cb9bdb3cf72ec387e6f34d29a31e701f5f5e7d92.zip |
Redesigned subkey creation dialog and changed default key type created to RSA, 3072 bit.
Added code to prevent removal of master subkey when modifying a new key.
Diffstat (limited to 'OpenKeychain/src/main/java/org')
6 files changed, 271 insertions, 352 deletions
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyFinalFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyFinalFragment.java index b53bfc1d0..e1d20f2e1 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyFinalFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyFinalFragment.java @@ -281,11 +281,11 @@ public class CreateKeyFinalFragment extends Fragment { saveKeyringParcel.mNewUnlock = new ChangeUnlockParcel(new Passphrase(), null); } else { saveKeyringParcel.mAddSubKeys.add(new SaveKeyringParcel.SubkeyAdd(Algorithm.RSA, - 4096, null, KeyFlags.CERTIFY_OTHER, 0L)); + 3072, null, KeyFlags.CERTIFY_OTHER, 0L)); saveKeyringParcel.mAddSubKeys.add(new SaveKeyringParcel.SubkeyAdd(Algorithm.RSA, - 4096, null, KeyFlags.SIGN_DATA, 0L)); + 3072, null, KeyFlags.SIGN_DATA, 0L)); saveKeyringParcel.mAddSubKeys.add(new SaveKeyringParcel.SubkeyAdd(Algorithm.RSA, - 4096, null, KeyFlags.ENCRYPT_COMMS | KeyFlags.ENCRYPT_STORAGE, 0L)); + 3072, null, KeyFlags.ENCRYPT_COMMS | KeyFlags.ENCRYPT_STORAGE, 0L)); saveKeyringParcel.mNewUnlock = createKeyActivity.mPassphrase != null ? new ChangeUnlockParcel(createKeyActivity.mPassphrase, null) diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EditKeyFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EditKeyFragment.java index 2d94d0d93..1571a7104 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EditKeyFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EditKeyFragment.java @@ -562,15 +562,9 @@ public class EditKeyFragment extends QueueingCryptoOperationFragment<SaveKeyring } private void addSubkey() { - boolean willBeMasterKey; - if (mSubkeysAdapter != null) { - willBeMasterKey = mSubkeysAdapter.getCount() == 0 && mSubkeysAddedAdapter.getCount() == 0; - } else { - willBeMasterKey = mSubkeysAddedAdapter.getCount() == 0; - } - + // new subkey will never be a masterkey, as masterkey cannot be removed AddSubkeyDialogFragment addSubkeyDialogFragment = - AddSubkeyDialogFragment.newInstance(willBeMasterKey); + AddSubkeyDialogFragment.newInstance(false); addSubkeyDialogFragment .setOnAlgorithmSelectedListener( new AddSubkeyDialogFragment.OnAlgorithmSelectedListener() { diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/SubkeysAddedAdapter.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/SubkeysAddedAdapter.java index 8b2481c29..717299b55 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/SubkeysAddedAdapter.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/SubkeysAddedAdapter.java @@ -20,6 +20,7 @@ package org.sufficientlysecure.keychain.ui.adapter; import android.app.Activity; import android.content.Context; import android.graphics.Typeface; +import android.support.v4.app.FragmentActivity; import android.text.format.DateFormat; import android.view.LayoutInflater; import android.view.View; @@ -32,6 +33,7 @@ import android.widget.TextView; import org.bouncycastle.bcpg.sig.KeyFlags; import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.service.SaveKeyringParcel; +import org.sufficientlysecure.keychain.ui.dialog.AddSubkeyDialogFragment; import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils; import java.util.Calendar; @@ -65,10 +67,11 @@ public class SubkeysAddedAdapter extends ArrayAdapter<SaveKeyringParcel.SubkeyAd public SaveKeyringParcel.SubkeyAdd mModel; } + public View getView(final int position, View convertView, ViewGroup parent) { if (convertView == null) { // Not recycled, inflate a new view - convertView = mInflater.inflate(R.layout.view_key_adv_subkey_item, null); + convertView = mInflater.inflate(R.layout.view_key_adv_subkey_item, parent, false); final ViewHolder holder = new ViewHolder(); holder.vKeyId = (TextView) convertView.findViewById(R.id.subkey_item_key_id); holder.vKeyDetails = (TextView) convertView.findViewById(R.id.subkey_item_details); @@ -88,16 +91,8 @@ public class SubkeysAddedAdapter extends ArrayAdapter<SaveKeyringParcel.SubkeyAd vStatus.setVisibility(View.GONE); convertView.setTag(holder); - - holder.vDelete.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - // remove reference model item from adapter (data and notify about change) - SubkeysAddedAdapter.this.remove(holder.mModel); - } - }); - } + final ViewHolder holder = (ViewHolder) convertView.getTag(); // save reference to model item @@ -113,8 +108,41 @@ public class SubkeysAddedAdapter extends ArrayAdapter<SaveKeyringParcel.SubkeyAd boolean isMasterKey = mNewKeyring && position == 0; if (isMasterKey) { holder.vKeyId.setTypeface(null, Typeface.BOLD); + holder.vDelete.setImageResource(R.drawable.ic_change_grey_24dp); + holder.vDelete.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + // swapping out the old master key with newly set master key + AddSubkeyDialogFragment addSubkeyDialogFragment = + AddSubkeyDialogFragment.newInstance(true); + addSubkeyDialogFragment + .setOnAlgorithmSelectedListener( + new AddSubkeyDialogFragment.OnAlgorithmSelectedListener() { + @Override + public void onAlgorithmSelected(SaveKeyringParcel.SubkeyAdd newSubkey) { + // calculate manually as the provided position variable + // is not always accurate + int pos = SubkeysAddedAdapter.this.getPosition(holder.mModel); + SubkeysAddedAdapter.this.remove(holder.mModel); + SubkeysAddedAdapter.this.insert(newSubkey, pos); + } + } + ); + addSubkeyDialogFragment.show( + ((FragmentActivity)mActivity).getSupportFragmentManager() + , "addSubkeyDialog"); + } + }); } else { holder.vKeyId.setTypeface(null, Typeface.NORMAL); + holder.vDelete.setImageResource(R.drawable.ic_close_grey_24dp); + holder.vDelete.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + // remove reference model item from adapter (data and notify about change) + SubkeysAddedAdapter.this.remove(holder.mModel); + } + }); } holder.vKeyId.setText(R.string.edit_key_new_subkey); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/AddSubkeyDialogFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/AddSubkeyDialogFragment.java index 5b75723fb..ce1665382 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/AddSubkeyDialogFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/AddSubkeyDialogFragment.java @@ -17,25 +17,26 @@ package org.sufficientlysecure.keychain.ui.dialog; -import android.annotation.TargetApi; +import android.annotation.SuppressLint; import android.app.Dialog; -import android.os.Build; +import android.content.Context; import android.os.Bundle; +import android.support.annotation.NonNull; import android.support.v4.app.DialogFragment; import android.support.v4.app.FragmentActivity; import android.support.v7.app.AlertDialog; -import android.text.Editable; -import android.text.TextWatcher; +import android.text.Html; import android.view.LayoutInflater; import android.view.View; -import android.view.inputmethod.InputMethodManager; +import android.view.ViewGroup; import android.widget.AdapterView; import android.widget.ArrayAdapter; import android.widget.Button; import android.widget.CheckBox; import android.widget.CompoundButton; import android.widget.DatePicker; -import android.widget.EditText; +import android.widget.RadioButton; +import android.widget.RadioGroup; import android.widget.Spinner; import android.widget.TableRow; import android.widget.TextView; @@ -49,14 +50,18 @@ import org.sufficientlysecure.keychain.service.SaveKeyringParcel.Curve; import org.sufficientlysecure.keychain.util.Choice; import java.util.ArrayList; -import java.util.Arrays; import java.util.Calendar; +import java.util.List; import java.util.TimeZone; public class AddSubkeyDialogFragment extends DialogFragment { public interface OnAlgorithmSelectedListener { - public void onAlgorithmSelected(SaveKeyringParcel.SubkeyAdd newSubkey); + void onAlgorithmSelected(SaveKeyringParcel.SubkeyAdd newSubkey); + } + + public enum SupportedKeyType { + RSA_2048, RSA_3072, RSA_4096, ECC_P256, ECC_P521 } private static final String ARG_WILL_BE_MASTER_KEY = "will_be_master_key"; @@ -66,18 +71,12 @@ public class AddSubkeyDialogFragment extends DialogFragment { private CheckBox mNoExpiryCheckBox; private TableRow mExpiryRow; private DatePicker mExpiryDatePicker; - private Spinner mAlgorithmSpinner; - private View mKeySizeRow; - private Spinner mKeySizeSpinner; - private View mCurveRow; - private Spinner mCurveSpinner; - private TextView mCustomKeyTextView; - private EditText mCustomKeyEditText; - private TextView mCustomKeyInfoTextView; - private CheckBox mFlagCertify; - private CheckBox mFlagSign; - private CheckBox mFlagEncrypt; - private CheckBox mFlagAuthenticate; + private Spinner mKeyTypeSpinner; + private RadioGroup mUsageRadioGroup; + private RadioButton mUsageNone; + private RadioButton mUsageSign; + private RadioButton mUsageEncrypt; + private RadioButton mUsageSignAndEncrypt; private boolean mWillBeMasterKey; @@ -96,6 +95,8 @@ public class AddSubkeyDialogFragment extends DialogFragment { return frag; } + + @NonNull @Override public Dialog onCreateDialog(Bundle savedInstanceState) { final FragmentActivity context = getActivity(); @@ -106,25 +107,27 @@ public class AddSubkeyDialogFragment extends DialogFragment { CustomAlertDialogBuilder dialog = new CustomAlertDialogBuilder(context); + @SuppressLint("InflateParams") View view = mInflater.inflate(R.layout.add_subkey_dialog, null); dialog.setView(view); - dialog.setTitle(R.string.title_add_subkey); mNoExpiryCheckBox = (CheckBox) view.findViewById(R.id.add_subkey_no_expiry); mExpiryRow = (TableRow) view.findViewById(R.id.add_subkey_expiry_row); mExpiryDatePicker = (DatePicker) view.findViewById(R.id.add_subkey_expiry_date_picker); - mAlgorithmSpinner = (Spinner) view.findViewById(R.id.add_subkey_algorithm); - mKeySizeSpinner = (Spinner) view.findViewById(R.id.add_subkey_size); - mCurveSpinner = (Spinner) view.findViewById(R.id.add_subkey_curve); - mKeySizeRow = view.findViewById(R.id.add_subkey_row_size); - mCurveRow = view.findViewById(R.id.add_subkey_row_curve); - mCustomKeyTextView = (TextView) view.findViewById(R.id.add_subkey_custom_key_size_label); - mCustomKeyEditText = (EditText) view.findViewById(R.id.add_subkey_custom_key_size_input); - mCustomKeyInfoTextView = (TextView) view.findViewById(R.id.add_subkey_custom_key_size_info); - mFlagCertify = (CheckBox) view.findViewById(R.id.add_subkey_flag_certify); - mFlagSign = (CheckBox) view.findViewById(R.id.add_subkey_flag_sign); - mFlagEncrypt = (CheckBox) view.findViewById(R.id.add_subkey_flag_encrypt); - mFlagAuthenticate = (CheckBox) view.findViewById(R.id.add_subkey_flag_authenticate); + mKeyTypeSpinner = (Spinner) view.findViewById(R.id.add_subkey_type); + mUsageRadioGroup = (RadioGroup) view.findViewById(R.id.add_subkey_usage_group); + mUsageNone = (RadioButton) view.findViewById(R.id.add_subkey_usage_none); + mUsageSign = (RadioButton) view.findViewById(R.id.add_subkey_usage_sign); + mUsageEncrypt = (RadioButton) view.findViewById(R.id.add_subkey_usage_encrypt); + mUsageSignAndEncrypt = (RadioButton) view.findViewById(R.id.add_subkey_usage_sign_and_encrypt); + + if(mWillBeMasterKey) { + dialog.setTitle(R.string.title_change_master_key); + mUsageNone.setVisibility(View.VISIBLE); + mUsageNone.setChecked(true); + } else { + dialog.setTitle(R.string.title_add_subkey); + } mNoExpiryCheckBox.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() { @Override @@ -143,65 +146,24 @@ public class AddSubkeyDialogFragment extends DialogFragment { mExpiryDatePicker.setMinDate(minDateCal.getTime().getTime()); { - ArrayList<Choice<Algorithm>> choices = new ArrayList<>(); - choices.add(new Choice<>(Algorithm.DSA, getResources().getString( - R.string.dsa))); - if (!mWillBeMasterKey) { - choices.add(new Choice<>(Algorithm.ELGAMAL, getResources().getString( - R.string.elgamal))); - } - choices.add(new Choice<>(Algorithm.RSA, getResources().getString( - R.string.rsa))); - choices.add(new Choice<>(Algorithm.ECDSA, getResources().getString( - R.string.ecdsa))); - choices.add(new Choice<>(Algorithm.ECDH, getResources().getString( - R.string.ecdh))); - ArrayAdapter<Choice<Algorithm>> adapter = new ArrayAdapter<>(context, + ArrayList<Choice<SupportedKeyType>> choices = new ArrayList<>(); + choices.add(new Choice<>(SupportedKeyType.RSA_2048, getResources().getString( + R.string.rsa_2048), getResources().getString(R.string.rsa_2048_description_html))); + choices.add(new Choice<>(SupportedKeyType.RSA_3072, getResources().getString( + R.string.rsa_3072), getResources().getString(R.string.rsa_3072_description_html))); + choices.add(new Choice<>(SupportedKeyType.RSA_4096, getResources().getString( + R.string.rsa_4096), getResources().getString(R.string.rsa_4096_description_html))); + choices.add(new Choice<>(SupportedKeyType.ECC_P256, getResources().getString( + R.string.ecc_p256), getResources().getString(R.string.ecc_p256_description_html))); + choices.add(new Choice<>(SupportedKeyType.ECC_P521, getResources().getString( + R.string.ecc_p521), getResources().getString(R.string.ecc_p521_description_html))); + TwoLineArrayAdapter adapter = new TwoLineArrayAdapter(context, android.R.layout.simple_spinner_item, choices); - adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); - mAlgorithmSpinner.setAdapter(adapter); - // make RSA the default + mKeyTypeSpinner.setAdapter(adapter); + // make RSA 3072 the default for (int i = 0; i < choices.size(); ++i) { - if (choices.get(i).getId() == Algorithm.RSA) { - mAlgorithmSpinner.setSelection(i); - break; - } - } - } - - // dynamic ArrayAdapter must be created (instead of ArrayAdapter.getFromResource), because it's content may change - ArrayAdapter<CharSequence> keySizeAdapter = new ArrayAdapter<>(context, android.R.layout.simple_spinner_item, - new ArrayList<CharSequence>(Arrays.asList(getResources().getStringArray(R.array.rsa_key_size_spinner_values)))); - keySizeAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); - mKeySizeSpinner.setAdapter(keySizeAdapter); - mKeySizeSpinner.setSelection(1); // Default to 4096 for the key length - - { - ArrayList<Choice<Curve>> choices = new ArrayList<>(); - - choices.add(new Choice<>(Curve.NIST_P256, getResources().getString( - R.string.key_curve_nist_p256))); - choices.add(new Choice<>(Curve.NIST_P384, getResources().getString( - R.string.key_curve_nist_p384))); - choices.add(new Choice<>(Curve.NIST_P521, getResources().getString( - R.string.key_curve_nist_p521))); - - /* @see SaveKeyringParcel - choices.add(new Choice<Curve>(Curve.BRAINPOOL_P256, getResources().getString( - R.string.key_curve_bp_p256))); - choices.add(new Choice<Curve>(Curve.BRAINPOOL_P384, getResources().getString( - R.string.key_curve_bp_p384))); - choices.add(new Choice<Curve>(Curve.BRAINPOOL_P512, getResources().getString( - R.string.key_curve_bp_p512))); - */ - - ArrayAdapter<Choice<Curve>> adapter = new ArrayAdapter<>(context, - android.R.layout.simple_spinner_item, choices); - mCurveSpinner.setAdapter(adapter); - // make NIST P-256 the default - for (int i = 0; i < choices.size(); ++i) { - if (choices.get(i).getId() == Curve.NIST_P256) { - mCurveSpinner.setSelection(i); + if (choices.get(i).getId() == SupportedKeyType.RSA_3072) { + mKeyTypeSpinner.setSelection(i); break; } } @@ -215,45 +177,35 @@ public class AddSubkeyDialogFragment extends DialogFragment { final AlertDialog alertDialog = dialog.show(); - mCustomKeyEditText.addTextChangedListener(new TextWatcher() { - @Override - public void beforeTextChanged(CharSequence s, int start, int count, int after) { - } - - @Override - public void onTextChanged(CharSequence s, int start, int before, int count) { - } + mKeyTypeSpinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() { @Override - public void afterTextChanged(Editable s) { - setOkButtonAvailability(alertDialog); - } - }); - - mKeySizeSpinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() { - @Override public void onItemSelected(AdapterView<?> parent, View view, int position, long id) { - setCustomKeyVisibility(); - setOkButtonAvailability(alertDialog); - } + // noinspection unchecked + SupportedKeyType keyType = ((Choice<SupportedKeyType>) parent.getSelectedItem()).getId(); - @Override - public void onNothingSelected(AdapterView<?> parent) { - } - }); + // RadioGroup.getCheckedRadioButtonId() gives the wrong RadioButton checked + // when programmatically unchecking children radio buttons. Clearing all is the only option. + mUsageRadioGroup.clearCheck(); - mAlgorithmSpinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() { - @Override - public void onItemSelected(AdapterView<?> parent, View view, int position, long id) { - updateUiForAlgorithm(((Choice<Algorithm>) parent.getSelectedItem()).getId()); + if(mWillBeMasterKey) { + mUsageNone.setChecked(true); + } - setCustomKeyVisibility(); - setOkButtonAvailability(alertDialog); + if (keyType == SupportedKeyType.ECC_P521 || keyType == SupportedKeyType.ECC_P256) { + mUsageSignAndEncrypt.setEnabled(false); + if (mWillBeMasterKey) { + mUsageEncrypt.setEnabled(false); + } + } else { + // need to enable if previously disabled for ECC masterkey + mUsageEncrypt.setEnabled(true); + mUsageSignAndEncrypt.setEnabled(true); + } } @Override - public void onNothingSelected(AdapterView<?> parent) { - } + public void onNothingSelected(AdapterView<?> parent) {} }); return alertDialog; @@ -269,36 +221,74 @@ public class AddSubkeyDialogFragment extends DialogFragment { positiveButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { - if (!mFlagCertify.isChecked() && !mFlagSign.isChecked() - && !mFlagEncrypt.isChecked() && !mFlagAuthenticate.isChecked()) { - Toast.makeText(getActivity(), R.string.edit_key_select_flag, Toast.LENGTH_LONG).show(); + if (mUsageRadioGroup.getCheckedRadioButtonId() == -1) { + Toast.makeText(getActivity(), R.string.edit_key_select_usage, Toast.LENGTH_LONG).show(); return; } - Algorithm algorithm = ((Choice<Algorithm>) mAlgorithmSpinner.getSelectedItem()).getId(); + // noinspection unchecked + SupportedKeyType keyType = ((Choice<SupportedKeyType>) mKeyTypeSpinner.getSelectedItem()).getId(); Curve curve = null; Integer keySize = null; - // For EC keys, add a curve - if (algorithm == Algorithm.ECDH || algorithm == Algorithm.ECDSA) { - curve = ((Choice<Curve>) mCurveSpinner.getSelectedItem()).getId(); - // Otherwise, get a keysize - } else { - keySize = getProperKeyLength(algorithm, getSelectedKeyLength()); + Algorithm algorithm = null; + + // set keysize & curve, for RSA & ECC respectively + switch (keyType) { + case RSA_2048: { + keySize = 2048; + break; + } + case RSA_3072: { + keySize = 3072; + break; + } + case RSA_4096: { + keySize = 4096; + break; + } + case ECC_P256: { + curve = Curve.NIST_P256; + break; + } + case ECC_P521: { + curve = Curve.NIST_P521; + break; + } } + // set algorithm + switch (keyType) { + case RSA_2048: + case RSA_3072: + case RSA_4096: { + algorithm = Algorithm.RSA; + break; + } + + case ECC_P256: + case ECC_P521: { + if(mUsageEncrypt.isChecked()) { + algorithm = Algorithm.ECDH; + } else { + algorithm = Algorithm.ECDSA; + } + break; + } + } + + // set flags int flags = 0; - if (mFlagCertify.isChecked()) { + if (mWillBeMasterKey) { flags |= KeyFlags.CERTIFY_OTHER; } - if (mFlagSign.isChecked()) { + if (mUsageSign.isChecked()) { flags |= KeyFlags.SIGN_DATA; - } - if (mFlagEncrypt.isChecked()) { + } else if (mUsageEncrypt.isChecked()) { flags |= KeyFlags.ENCRYPT_COMMS | KeyFlags.ENCRYPT_STORAGE; + } else if (mUsageSignAndEncrypt.isChecked()) { + flags |= KeyFlags.SIGN_DATA | KeyFlags.ENCRYPT_COMMS | KeyFlags.ENCRYPT_STORAGE; } - if (mFlagAuthenticate.isChecked()) { - flags |= KeyFlags.AUTHENTICATION; - } + long expiry; if (mNoExpiryCheckBox.isChecked()) { @@ -332,206 +322,29 @@ public class AddSubkeyDialogFragment extends DialogFragment { } } - private int getSelectedKeyLength() { - final String selectedItemString = (String) mKeySizeSpinner.getSelectedItem(); - final String customLengthString = getResources().getString(R.string.key_size_custom); - final boolean customSelected = customLengthString.equals(selectedItemString); - String keyLengthString = customSelected ? mCustomKeyEditText.getText().toString() : selectedItemString; - int keySize; - try { - keySize = Integer.parseInt(keyLengthString); - } catch (NumberFormatException e) { - keySize = 0; - } - return keySize; - } - - /** - * <h3>RSA</h3> - * <p>for RSA algorithm, key length must be greater than 2048. Possibility to generate keys bigger - * than 8192 bits is currently disabled, because it's almost impossible to generate them on a mobile device (check - * <a href="http://www.javamex.com/tutorials/cryptography/rsa_key_length.shtml">RSA key length plot</a> and - * <a href="http://www.keylength.com/">Cryptographic Key Length Recommendation</a>). Also, key length must be a - * multiplicity of 8.</p> - * <h3>ElGamal</h3> - * <p>For ElGamal algorithm, supported key lengths are 2048, 3072, 4096 or 8192 bits.</p> - * <h3>DSA</h3> - * <p>For DSA algorithm key length must be between 2048 and 3072. Also, it must me dividable by 64.</p> - * - * @return correct key length, according to BouncyCastle specification. Returns <code>-1</code>, if key length is - * inappropriate. - */ - private int getProperKeyLength(Algorithm algorithm, int currentKeyLength) { - final int[] elGamalSupportedLengths = {2048, 3072, 4096, 8192}; - int properKeyLength = -1; - switch (algorithm) { - case RSA: { - if (currentKeyLength >= 2048 && currentKeyLength <= 16384) { - properKeyLength = currentKeyLength + ((8 - (currentKeyLength % 8)) % 8); - } - break; - } - case ELGAMAL: { - int[] elGammalKeyDiff = new int[elGamalSupportedLengths.length]; - for (int i = 0; i < elGamalSupportedLengths.length; i++) { - elGammalKeyDiff[i] = Math.abs(elGamalSupportedLengths[i] - currentKeyLength); - } - int minimalValue = Integer.MAX_VALUE; - int minimalIndex = -1; - for (int i = 0; i < elGammalKeyDiff.length; i++) { - if (elGammalKeyDiff[i] <= minimalValue) { - minimalValue = elGammalKeyDiff[i]; - minimalIndex = i; - } - } - properKeyLength = elGamalSupportedLengths[minimalIndex]; - break; - } - case DSA: { - // Bouncy Castle supports 4096 maximum - if (currentKeyLength >= 2048 && currentKeyLength <= 4096) { - properKeyLength = currentKeyLength + ((64 - (currentKeyLength % 64)) % 64); - } - break; - } + private class TwoLineArrayAdapter extends ArrayAdapter<Choice<SupportedKeyType>> { + public TwoLineArrayAdapter(Context context, int resource, List<Choice<SupportedKeyType>> objects) { + super(context, resource, objects); } - return properKeyLength; - } - private void setOkButtonAvailability(AlertDialog alertDialog) { - Algorithm algorithm = ((Choice<Algorithm>) mAlgorithmSpinner.getSelectedItem()).getId(); - boolean enabled = algorithm == Algorithm.ECDSA || algorithm == Algorithm.ECDH - || getProperKeyLength(algorithm, getSelectedKeyLength()) > 0; - alertDialog.getButton(AlertDialog.BUTTON_POSITIVE).setEnabled(enabled); - } - private void setCustomKeyVisibility() { - final String selectedItemString = (String) mKeySizeSpinner.getSelectedItem(); - final String customLengthString = getResources().getString(R.string.key_size_custom); - final boolean customSelected = customLengthString.equals(selectedItemString); - final int visibility = customSelected ? View.VISIBLE : View.GONE; - - mCustomKeyEditText.setVisibility(visibility); - mCustomKeyTextView.setVisibility(visibility); - mCustomKeyInfoTextView.setVisibility(visibility); - - // hide keyboard after setting visibility to gone - if (visibility == View.GONE) { - InputMethodManager imm = (InputMethodManager) - getActivity().getSystemService(FragmentActivity.INPUT_METHOD_SERVICE); - imm.hideSoftInputFromWindow(mCustomKeyEditText.getWindowToken(), 0); - } - } + @Override + public View getDropDownView(int position, View convertView, ViewGroup parent) { + // inflate view if not given one + if (convertView == null) { + convertView = getActivity().getLayoutInflater() + .inflate(R.layout.two_line_spinner_dropdown_item, parent, false); + } - private void updateUiForAlgorithm(Algorithm algorithm) { - final ArrayAdapter<CharSequence> keySizeAdapter = (ArrayAdapter<CharSequence>) mKeySizeSpinner.getAdapter(); - keySizeAdapter.clear(); - switch (algorithm) { - case RSA: { - replaceArrayAdapterContent(keySizeAdapter, R.array.rsa_key_size_spinner_values); - mKeySizeSpinner.setSelection(1); - mKeySizeRow.setVisibility(View.VISIBLE); - mCurveRow.setVisibility(View.GONE); - mCustomKeyInfoTextView.setText(getResources().getString(R.string.key_size_custom_info_rsa)); - // allowed flags: - mFlagSign.setEnabled(true); - mFlagEncrypt.setEnabled(true); - mFlagAuthenticate.setEnabled(true); - - if (mWillBeMasterKey) { - mFlagCertify.setEnabled(true); - - mFlagCertify.setChecked(true); - mFlagSign.setChecked(false); - mFlagEncrypt.setChecked(false); - } else { - mFlagCertify.setEnabled(false); + Choice c = this.getItem(position); - mFlagCertify.setChecked(false); - mFlagSign.setChecked(true); - mFlagEncrypt.setChecked(true); - } - mFlagAuthenticate.setChecked(false); - break; - } - case ELGAMAL: { - replaceArrayAdapterContent(keySizeAdapter, R.array.elgamal_key_size_spinner_values); - mKeySizeSpinner.setSelection(3); - mKeySizeRow.setVisibility(View.VISIBLE); - mCurveRow.setVisibility(View.GONE); - mCustomKeyInfoTextView.setText(""); // ElGamal does not support custom key length - // allowed flags: - mFlagCertify.setChecked(false); - mFlagCertify.setEnabled(false); - mFlagSign.setChecked(false); - mFlagSign.setEnabled(false); - mFlagEncrypt.setChecked(true); - mFlagEncrypt.setEnabled(true); - mFlagAuthenticate.setChecked(false); - mFlagAuthenticate.setEnabled(false); - break; - } - case DSA: { - replaceArrayAdapterContent(keySizeAdapter, R.array.dsa_key_size_spinner_values); - mKeySizeSpinner.setSelection(2); - mKeySizeRow.setVisibility(View.VISIBLE); - mCurveRow.setVisibility(View.GONE); - mCustomKeyInfoTextView.setText(getResources().getString(R.string.key_size_custom_info_dsa)); - // allowed flags: - mFlagCertify.setChecked(false); - mFlagCertify.setEnabled(false); - mFlagSign.setChecked(true); - mFlagSign.setEnabled(true); - mFlagEncrypt.setChecked(false); - mFlagEncrypt.setEnabled(false); - mFlagAuthenticate.setChecked(false); - mFlagAuthenticate.setEnabled(false); - break; - } - case ECDSA: { - mKeySizeRow.setVisibility(View.GONE); - mCurveRow.setVisibility(View.VISIBLE); - mCustomKeyInfoTextView.setText(""); - // allowed flags: - mFlagCertify.setEnabled(mWillBeMasterKey); - mFlagCertify.setChecked(mWillBeMasterKey); - mFlagSign.setEnabled(true); - mFlagSign.setChecked(!mWillBeMasterKey); - mFlagEncrypt.setEnabled(false); - mFlagEncrypt.setChecked(false); - mFlagAuthenticate.setEnabled(true); - mFlagAuthenticate.setChecked(false); - break; - } - case ECDH: { - mKeySizeRow.setVisibility(View.GONE); - mCurveRow.setVisibility(View.VISIBLE); - mCustomKeyInfoTextView.setText(""); - // allowed flags: - mFlagCertify.setChecked(false); - mFlagCertify.setEnabled(false); - mFlagSign.setChecked(false); - mFlagSign.setEnabled(false); - mFlagEncrypt.setChecked(true); - mFlagEncrypt.setEnabled(true); - mFlagAuthenticate.setChecked(false); - mFlagAuthenticate.setEnabled(false); - break; - } - } - keySizeAdapter.notifyDataSetChanged(); + TextView text1 = (TextView) convertView.findViewById(android.R.id.text1); + TextView text2 = (TextView) convertView.findViewById(android.R.id.text2); - } + text1.setText(c.getName()); + text2.setText(Html.fromHtml(c.getDescription())); - @TargetApi(Build.VERSION_CODES.HONEYCOMB) - private void replaceArrayAdapterContent(ArrayAdapter<CharSequence> arrayAdapter, int stringArrayResourceId) { - final String[] spinnerValuesStringArray = getResources().getStringArray(stringArrayResourceId); - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) { - arrayAdapter.addAll(spinnerValuesStringArray); - } else { - for (final String value : spinnerValuesStringArray) { - arrayAdapter.add(value); - } + return convertView; } } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/spinner/FocusFirstItemSpinner.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/spinner/FocusFirstItemSpinner.java new file mode 100644 index 000000000..7919a0918 --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/spinner/FocusFirstItemSpinner.java @@ -0,0 +1,77 @@ +/* + * Copyright (C) 2016 Alex Fong Jie Wen <alexfongg@gmail.com> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +package org.sufficientlysecure.keychain.ui.util.spinner; + +import android.content.Context; +import android.util.AttributeSet; +import android.widget.Spinner; + +/** + * Custom spinner which uses a hack to + * always set focus on first item in list + * + */ +public class FocusFirstItemSpinner extends Spinner { + /** + * Spinner is originally designed to set focus on the currently selected item. + * When Spinner is selected to show dropdown, 'performClick()' is called internally. + * 'getSelectedItemPosition()' is then called to obtain the item to focus on. + * We use a toggle to have 'getSelectedItemPosition()' return the 0th index + * for this particular case. + */ + + private boolean mToggleFlag = true; + + public FocusFirstItemSpinner(Context context, AttributeSet attrs, + int defStyle, int mode) { + super(context, attrs, defStyle, mode); + } + + public FocusFirstItemSpinner(Context context, AttributeSet attrs, + int defStyle) { + super(context, attrs, defStyle); + } + + public FocusFirstItemSpinner(Context context, AttributeSet attrs) { + super(context, attrs); + } + + public FocusFirstItemSpinner(Context context, int mode) { + super(context, mode); + } + + public FocusFirstItemSpinner(Context context) { + super(context); + } + + @Override + public int getSelectedItemPosition() { + if (!mToggleFlag) { + return 0; + } + return super.getSelectedItemPosition(); + } + + @Override + public boolean performClick() { + mToggleFlag = false; + boolean result = super.performClick(); + mToggleFlag = true; + return result; + } +} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/Choice.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/Choice.java index 48f10d4b9..5ffce9f24 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/Choice.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/Choice.java @@ -18,12 +18,15 @@ package org.sufficientlysecure.keychain.util; public class Choice <E> { + private String mName; private E mId; + private String mDescription; - public Choice(E id, String name) { + public Choice(E id, String name, String description) { mId = id; mName = name; + mDescription = description; } public E getId() { @@ -34,6 +37,10 @@ public class Choice <E> { return mName; } + public String getDescription() { + return mDescription; + } + @Override public String toString() { return mName; |