diff options
author | Dominik Schürmann <dominik@dominikschuermann.de> | 2014-03-30 18:04:43 +0200 |
---|---|---|
committer | Dominik Schürmann <dominik@dominikschuermann.de> | 2014-03-30 18:04:43 +0200 |
commit | 6b5d60e1f4889fabf8277c0720a60e3a24ec8caf (patch) | |
tree | ed65b0f6204ceaed0d121b6d0238ad5afb8d7004 /OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui | |
parent | b2342744b5f8e5bce62db18f93468fc0781a2c05 (diff) | |
parent | be007ecc56809cc0b6d489ab09be656e2ced9c32 (diff) | |
download | open-keychain-6b5d60e1f4889fabf8277c0720a60e3a24ec8caf.tar.gz open-keychain-6b5d60e1f4889fabf8277c0720a60e3a24ec8caf.tar.bz2 open-keychain-6b5d60e1f4889fabf8277c0720a60e3a24ec8caf.zip |
Merge pull request #491 from openpgp-keychain/edit-key
Edit key changes
Diffstat (limited to 'OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui')
7 files changed, 599 insertions, 239 deletions
diff --git a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/EditKeyActivity.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/EditKeyActivity.java index 6eb5b9d2d..b7fffc7ff 100644 --- a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/EditKeyActivity.java +++ b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/EditKeyActivity.java @@ -17,6 +17,12 @@ package org.sufficientlysecure.keychain.ui; +import java.util.ArrayList; +import java.util.GregorianCalendar; +import java.util.List; +import java.util.Vector; + +import org.spongycastle.bcpg.sig.KeyFlags; import android.app.Activity; import android.app.ProgressDialog; import android.content.Context; @@ -41,7 +47,6 @@ import org.spongycastle.openpgp.PGPSecretKeyRing; import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.Id; import org.sufficientlysecure.keychain.R; -import org.sufficientlysecure.keychain.helper.ActionBarHelper; import org.sufficientlysecure.keychain.helper.ExportHelper; import org.sufficientlysecure.keychain.pgp.PgpConversionHelper; import org.sufficientlysecure.keychain.pgp.PgpKeyHelper; @@ -51,19 +56,27 @@ import org.sufficientlysecure.keychain.provider.ProviderHelper; import org.sufficientlysecure.keychain.service.KeychainIntentService; import org.sufficientlysecure.keychain.service.KeychainIntentServiceHandler; import org.sufficientlysecure.keychain.service.PassphraseCacheService; +import org.sufficientlysecure.keychain.service.SaveKeyringParcel; import org.sufficientlysecure.keychain.ui.dialog.DeleteKeyDialogFragment; import org.sufficientlysecure.keychain.ui.dialog.PassphraseDialogFragment; import org.sufficientlysecure.keychain.ui.dialog.SetPassphraseDialogFragment; +import org.sufficientlysecure.keychain.ui.widget.Editor; import org.sufficientlysecure.keychain.ui.widget.KeyEditor; import org.sufficientlysecure.keychain.ui.widget.SectionView; import org.sufficientlysecure.keychain.ui.widget.UserIdEditor; +import org.sufficientlysecure.keychain.ui.widget.Editor.EditorListener; import org.sufficientlysecure.keychain.util.IterableIterator; import org.sufficientlysecure.keychain.util.Log; -import java.util.ArrayList; -import java.util.GregorianCalendar; -import java.util.Vector; -public class EditKeyActivity extends ActionBarActivity { +import android.app.AlertDialog; +import android.support.v4.app.ActivityCompat; +import android.view.LayoutInflater; +import android.view.Menu; +import android.view.MenuItem; +import android.view.View; +import android.view.ViewGroup; + +public class EditKeyActivity extends ActionBarActivity implements EditorListener { // Actions for internal use only: public static final String ACTION_CREATE_KEY = Constants.INTENT_PREFIX + "CREATE_KEY"; @@ -90,6 +103,9 @@ public class EditKeyActivity extends ActionBarActivity { private String mNewPassPhrase = null; private String mSavedNewPassPhrase = null; private boolean mIsPassPhraseSet; + private boolean mNeedsSaving; + private boolean mIsBrandNewKeyring = false; + private MenuItem mSaveButton; private BootstrapButton mChangePassphrase; @@ -102,12 +118,42 @@ public class EditKeyActivity extends ActionBarActivity { ExportHelper mExportHelper; + public boolean needsSaving() + { + mNeedsSaving = (mUserIdsView == null) ? false : mUserIdsView.needsSaving(); + mNeedsSaving |= (mKeysView == null) ? false : mKeysView.needsSaving(); + mNeedsSaving |= hasPassphraseChanged(); + mNeedsSaving |= mIsBrandNewKeyring; + return mNeedsSaving; + } + + + public void somethingChanged() + { + ActivityCompat.invalidateOptionsMenu(this); + //Toast.makeText(this, "Needs saving: " + Boolean.toString(mNeedsSaving) + "(" + Boolean.toString(mUserIdsView.needsSaving()) + ", " + Boolean.toString(mKeysView.needsSaving()) + ")", Toast.LENGTH_LONG).show(); + } + + public void onDeleted(Editor e, boolean wasNewItem) + { + somethingChanged(); + } + + public void onEdited() + { + somethingChanged(); + } + @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); mExportHelper = new ExportHelper(this); + getSupportActionBar().setDisplayHomeAsUpEnabled(true); + getSupportActionBar().setIcon(android.R.color.transparent); + getSupportActionBar().setHomeButtonEnabled(true); + mUserIds = new Vector<String>(); mKeys = new Vector<PGPSecretKey>(); mKeysUsages = new Vector<Integer>(); @@ -128,24 +174,10 @@ public class EditKeyActivity extends ActionBarActivity { * @param intent */ private void handleActionCreateKey(Intent intent) { - // Inflate a "Save"/"Cancel" custom action bar - ActionBarHelper.setTwoButtonView(getSupportActionBar(), R.string.btn_save, R.drawable.ic_action_save, - new View.OnClickListener() { - @Override - public void onClick(View v) { - saveClicked(); - } - }, R.string.btn_do_not_save, R.drawable.ic_action_cancel, new View.OnClickListener() { - @Override - public void onClick(View v) { - cancelClicked(); - } - } - ); - Bundle extras = intent.getExtras(); mCurrentPassphrase = ""; + mIsBrandNewKeyring = true; if (extras != null) { // if userId is given, prefill the fields @@ -203,22 +235,24 @@ public class EditKeyActivity extends ActionBarActivity { if (message.arg1 == KeychainIntentServiceHandler.MESSAGE_OKAY) { // get new key from data bundle returned from service Bundle data = message.getData(); - PGPSecretKey masterKey = (PGPSecretKey) PgpConversionHelper + PGPSecretKey masterKey = PgpConversionHelper .BytesToPGPSecretKey(data .getByteArray(KeychainIntentService.RESULT_NEW_KEY)); - PGPSecretKey subKey = (PGPSecretKey) PgpConversionHelper + PGPSecretKey subKey = PgpConversionHelper .BytesToPGPSecretKey(data .getByteArray(KeychainIntentService.RESULT_NEW_KEY2)); + //We must set the key flags here as they are not set when we make the + //key pair. Because we are not generating hashed packets there... // add master key mKeys.add(masterKey); - mKeysUsages.add(Id.choice.usage.sign_only); //TODO: get from key flags + mKeysUsages.add(KeyFlags.CERTIFY_OTHER); // add sub key mKeys.add(subKey); - mKeysUsages.add(Id.choice.usage.encrypt_only); //TODO: get from key flags + mKeysUsages.add(KeyFlags.ENCRYPT_COMMS + KeyFlags.ENCRYPT_STORAGE); - buildLayout(); + buildLayout(true); } } }; @@ -234,7 +268,7 @@ public class EditKeyActivity extends ActionBarActivity { } } } else { - buildLayout(); + buildLayout(false); } } @@ -244,40 +278,29 @@ public class EditKeyActivity extends ActionBarActivity { * @param intent */ private void handleActionEditKey(Intent intent) { - // Inflate a "Save"/"Cancel" custom action bar - ActionBarHelper.setOneButtonView(getSupportActionBar(), R.string.btn_save, R.drawable.ic_action_save, - new View.OnClickListener() { - @Override - public void onClick(View v) { - saveClicked(); - } - }); - mDataUri = intent.getData(); if (mDataUri == null) { Log.e(Constants.TAG, "Intent data missing. Should be Uri of key!"); finish(); - return; } else { Log.d(Constants.TAG, "uri: " + mDataUri); // get master key id using row id long masterKeyId = ProviderHelper.getMasterKeyId(this, mDataUri); - mMasterCanSign = ProviderHelper.getMasterKeyCanSign(this, mDataUri); - finallyEdit(masterKeyId, mMasterCanSign); + mMasterCanSign = ProviderHelper.getMasterKeyCanCertify(this, mDataUri); + finallyEdit(masterKeyId); } } - private void showPassphraseDialog(final long masterKeyId, final boolean masterCanSign) { + private void showPassphraseDialog(final long masterKeyId) { // Message is received after passphrase is cached Handler returnHandler = new Handler() { @Override public void handleMessage(Message message) { if (message.what == PassphraseDialogFragment.MESSAGE_OKAY) { - String passphrase = PassphraseCacheService.getCachedPassphrase( + mCurrentPassphrase = PassphraseCacheService.getCachedPassphrase( EditKeyActivity.this, masterKeyId); - mCurrentPassphrase = passphrase; finallySaveClicked(); } } @@ -299,59 +322,65 @@ public class EditKeyActivity extends ActionBarActivity { } @Override - public boolean onPrepareOptionsMenu(Menu menu) { - // show menu only on edit - if (mDataUri != null) { - return super.onPrepareOptionsMenu(menu); - } else { - return false; - } - } - - @Override public boolean onCreateOptionsMenu(Menu menu) { super.onCreateOptionsMenu(menu); getMenuInflater().inflate(R.menu.key_edit, menu); + mSaveButton = menu.findItem(R.id.menu_key_edit_save); + mSaveButton.setEnabled(needsSaving()); + //totally get rid of some actions for new keys + if (mDataUri == null) { + MenuItem mButton = menu.findItem(R.id.menu_key_edit_export_file); + mButton.setVisible(false); + mButton = menu.findItem(R.id.menu_key_edit_delete); + mButton.setVisible(false); + } return true; } @Override public boolean onOptionsItemSelected(MenuItem item) { switch (item.getItemId()) { - case R.id.menu_key_edit_cancel: - cancelClicked(); - return true; - case R.id.menu_key_edit_export_file: + case android.R.id.home: + cancelClicked(); //TODO: why isn't this triggered on my tablet - one of many ui problems I've had with this device. A code compatibility issue or a Samsung fail? + return true; + case R.id.menu_key_edit_cancel: + cancelClicked(); + return true; + case R.id.menu_key_edit_export_file: + if (needsSaving()) { + Toast.makeText(this, R.string.error_save_first, Toast.LENGTH_LONG).show(); + } else { long masterKeyId = ProviderHelper.getMasterKeyId(this, mDataUri); long[] ids = new long[]{masterKeyId}; mExportHelper.showExportKeysDialog(ids, Id.type.secret_key, Constants.Path.APP_DIR_FILE_SEC, null); return true; - case R.id.menu_key_edit_delete: { - //Convert the uri to one based on rowId - long rowId= ProviderHelper.getRowId(this,mDataUri); - Uri convertUri = KeychainContract.KeyRings.buildSecretKeyRingsUri(Long.toString(rowId)); - - // Message is received after key is deleted - Handler returnHandler = new Handler() { - @Override - public void handleMessage(Message message) { - if (message.what == DeleteKeyDialogFragment.MESSAGE_OKAY) { - setResult(RESULT_CANCELED); - finish(); - } + } + return true; + case R.id.menu_key_edit_delete: + long rowId= ProviderHelper.getRowId(this,mDataUri); + Uri convertUri = KeychainContract.KeyRings.buildSecretKeyRingsUri(Long.toString(rowId)); + // Message is received after key is deleted + Handler returnHandler = new Handler() { + @Override + public void handleMessage(Message message) { + if (message.what == DeleteKeyDialogFragment.MESSAGE_OKAY) { + setResult(RESULT_CANCELED); + finish(); } - }; + }}; + mExportHelper.deleteKey(convertUri, returnHandler); + return true; - mExportHelper.deleteKey(convertUri, returnHandler); - return true; - } + case R.id.menu_key_edit_save: + saveClicked(); + return true; } return super.onOptionsItemSelected(item); } @SuppressWarnings("unchecked") - private void finallyEdit(final long masterKeyId, final boolean masterCanSign) { + private void finallyEdit(final long masterKeyId) { if (masterKeyId != 0) { PGPSecretKey masterKey = null; mKeyRing = ProviderHelper.getPGPSecretKeyRingByMasterKeyId(this, masterKeyId); @@ -366,16 +395,24 @@ public class EditKeyActivity extends ActionBarActivity { Toast.makeText(this, R.string.error_no_secret_key_found, Toast.LENGTH_LONG).show(); } if (masterKey != null) { + boolean isSet = false; for (String userId : new IterableIterator<String>(masterKey.getUserIDs())) { Log.d(Constants.TAG, "Added userId " + userId); + if (!isSet) { + isSet = true; + String[] parts = PgpKeyHelper.splitUserId(userId); + if (parts[0] != null) + setTitle(parts[0]); + } mUserIds.add(userId); } } } mCurrentPassphrase = ""; + buildLayout(false); + mIsPassPhraseSet = PassphraseCacheService.hasPassphrase(this, masterKeyId); - buildLayout(); if (!mIsPassPhraseSet) { // check "no passphrase" checkbox and remove button mNoPassphrase.setChecked(true); @@ -399,6 +436,7 @@ public class EditKeyActivity extends ActionBarActivity { .getString(SetPassphraseDialogFragment.MESSAGE_NEW_PASSPHRASE); updatePassPhraseButtonText(); + somethingChanged(); } } }; @@ -407,7 +445,7 @@ public class EditKeyActivity extends ActionBarActivity { Messenger messenger = new Messenger(returnHandler); // set title based on isPassphraseSet() - int title = -1; + int title; if (isPassphraseSet()) { title = R.string.title_change_passphrase; } else { @@ -423,8 +461,9 @@ public class EditKeyActivity extends ActionBarActivity { /** * Build layout based on mUserId, mKeys and mKeysUsages Vectors. It creates Views for every user * id and key. + * @param newKeys */ - private void buildLayout() { + private void buildLayout(boolean newKeys) { setContentView(R.layout.edit_key_activity); // find views @@ -442,11 +481,13 @@ public class EditKeyActivity extends ActionBarActivity { mUserIdsView.setType(Id.type.user_id); mUserIdsView.setCanEdit(mMasterCanSign); mUserIdsView.setUserIds(mUserIds); + mUserIdsView.setEditorListener(this); container.addView(mUserIdsView); mKeysView = (SectionView) inflater.inflate(R.layout.edit_key_section, container, false); mKeysView.setType(Id.type.key); mKeysView.setCanEdit(mMasterCanSign); - mKeysView.setKeys(mKeys, mKeysUsages); + mKeysView.setKeys(mKeys, mKeysUsages, newKeys); + mKeysView.setEditorListener(this); container.addView(mKeysView); updatePassPhraseButtonText(); @@ -457,7 +498,7 @@ public class EditKeyActivity extends ActionBarActivity { } }); - // disable passphrase when no passphrase checkobox is checked! + // disable passphrase when no passphrase checkbox is checked! mNoPassphrase.setOnCheckedChangeListener(new OnCheckedChangeListener() { @Override @@ -471,6 +512,7 @@ public class EditKeyActivity extends ActionBarActivity { mNewPassPhrase = mSavedNewPassPhrase; mChangePassphrase.setVisibility(View.VISIBLE); } + somethingChanged(); } }); } @@ -493,27 +535,54 @@ public class EditKeyActivity extends ActionBarActivity { } } - private void saveClicked() { - long masterKeyId = getMasterKeyId(); - if (!isPassphraseSet()) { - Log.e(Constants.TAG, "No passphrase has been set"); - Toast.makeText(this, R.string.set_a_passphrase, Toast.LENGTH_LONG).show(); - } else { - String passphrase = null; - if (mIsPassPhraseSet) { - passphrase = PassphraseCacheService.getCachedPassphrase(this, masterKeyId); + public boolean hasPassphraseChanged() + { + if (mNoPassphrase != null) { + if (mNoPassphrase.isChecked()) { + return mIsPassPhraseSet; } else { - passphrase = ""; + return (mNewPassPhrase != null && !mNewPassPhrase.equals("")); } - if (passphrase == null) { - showPassphraseDialog(masterKeyId, mMasterCanSign); - } else { - mCurrentPassphrase = passphrase; - finallySaveClicked(); + }else { + return false; + } + } + + private void saveClicked() { + long masterKeyId = getMasterKeyId(); + if (needsSaving()) { //make sure, as some versions don't support invalidateOptionsMenu + try { + if (!isPassphraseSet()) { + throw new PgpGeneralException(this.getString(R.string.set_a_passphrase)); + } + + String passphrase; + if (mIsPassPhraseSet) + passphrase = PassphraseCacheService.getCachedPassphrase(this, masterKeyId); + else + passphrase = ""; + if (passphrase == null) { + showPassphraseDialog(masterKeyId); + } else { + mCurrentPassphrase = passphrase; + finallySaveClicked(); + } + } catch (PgpGeneralException e) { + Toast.makeText(this, getString(R.string.error_message, e.getMessage()), + Toast.LENGTH_SHORT).show(); } } } + private boolean[] toPrimitiveArray(final List<Boolean> booleanList) { + final boolean[] primitives = new boolean[booleanList.size()]; + int index = 0; + for (Boolean object : booleanList) { + primitives[index++] = object; + } + return primitives; + } + private void finallySaveClicked() { try { // Send all information needed to service to edit key in other thread @@ -521,22 +590,26 @@ public class EditKeyActivity extends ActionBarActivity { intent.setAction(KeychainIntentService.ACTION_SAVE_KEYRING); + SaveKeyringParcel saveParams = new SaveKeyringParcel(); + saveParams.userIDs = getUserIds(mUserIdsView); + saveParams.originalIDs = mUserIdsView.getOriginalIDs(); + saveParams.deletedIDs = mUserIdsView.getDeletedIDs(); + saveParams.primaryIDChanged = mUserIdsView.primaryChanged(); + saveParams.moddedKeys = toPrimitiveArray(mKeysView.getNeedsSavingArray()); + saveParams.deletedKeys = mKeysView.getDeletedKeys(); + saveParams.keysExpiryDates = getKeysExpiryDates(mKeysView); + saveParams.keysUsages = getKeysUsages(mKeysView); + saveParams.newPassPhrase = mNewPassPhrase; + saveParams.oldPassPhrase = mCurrentPassphrase; + saveParams.newKeys = toPrimitiveArray(mKeysView.getNewKeysArray()); + saveParams.keys = getKeys(mKeysView); + saveParams.originalPrimaryID = mUserIdsView.getOriginalPrimaryID(); + + // fill values for this action Bundle data = new Bundle(); - data.putString(KeychainIntentService.SAVE_KEYRING_CURRENT_PASSPHRASE, - mCurrentPassphrase); - data.putString(KeychainIntentService.SAVE_KEYRING_NEW_PASSPHRASE, mNewPassPhrase); - data.putStringArrayList(KeychainIntentService.SAVE_KEYRING_USER_IDS, - getUserIds(mUserIdsView)); - ArrayList<PGPSecretKey> keys = getKeys(mKeysView); - data.putByteArray(KeychainIntentService.SAVE_KEYRING_KEYS, - PgpConversionHelper.PGPSecretKeyArrayListToBytes(keys)); - data.putIntegerArrayList(KeychainIntentService.SAVE_KEYRING_KEYS_USAGES, - getKeysUsages(mKeysView)); - data.putSerializable(KeychainIntentService.SAVE_KEYRING_KEYS_EXPIRY_DATES, - getKeysExpiryDates(mKeysView)); - data.putLong(KeychainIntentService.SAVE_KEYRING_MASTER_KEY_ID, getMasterKeyId()); data.putBoolean(KeychainIntentService.SAVE_KEYRING_CAN_SIGN, mMasterCanSign); + data.putParcelable(KeychainIntentService.SAVE_KEYRING_PARCEL, saveParams); intent.putExtra(KeychainIntentService.EXTRA_DATA, data); @@ -579,8 +652,35 @@ public class EditKeyActivity extends ActionBarActivity { } private void cancelClicked() { - setResult(RESULT_CANCELED); - finish(); + if (needsSaving()) { //ask if we want to save + AlertDialog.Builder alert = new AlertDialog.Builder( + EditKeyActivity.this); + + alert.setIcon(android.R.drawable.ic_dialog_alert); + alert.setTitle(R.string.warning); + alert.setMessage(EditKeyActivity.this.getString(R.string.ask_save_changed_key)); + + alert.setPositiveButton(EditKeyActivity.this.getString(android.R.string.yes), + new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int id) { + dialog.dismiss(); + saveClicked(); + } + }); + alert.setNegativeButton(this.getString(android.R.string.no), + new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int id) { + dialog.dismiss(); + setResult(RESULT_CANCELED); + finish(); + } + }); + alert.setCancelable(false); + alert.create().show(); + } else { + setResult(RESULT_CANCELED); + finish(); + } } /** @@ -597,19 +697,8 @@ public class EditKeyActivity extends ActionBarActivity { boolean gotMainUserId = false; for (int i = 0; i < userIdEditors.getChildCount(); ++i) { UserIdEditor editor = (UserIdEditor) userIdEditors.getChildAt(i); - String userId = null; - try { - userId = editor.getValue(); - } catch (UserIdEditor.NoNameException e) { - throw new PgpGeneralException(this.getString(R.string.error_user_id_needs_a_name)); - } catch (UserIdEditor.NoEmailException e) { - throw new PgpGeneralException( - this.getString(R.string.error_user_id_needs_an_email_address)); - } - - if (userId.equals("")) { - continue; - } + String userId; + userId = editor.getValue(); if (editor.isMainUserId()) { userIds.add(0, userId); @@ -698,4 +787,4 @@ public class EditKeyActivity extends ActionBarActivity { : getString(R.string.btn_set_passphrase)); } -}
\ No newline at end of file +} diff --git a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/PreferencesKeyServerActivity.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/PreferencesKeyServerActivity.java index d890f35cb..719378274 100644 --- a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/PreferencesKeyServerActivity.java +++ b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/PreferencesKeyServerActivity.java @@ -91,10 +91,15 @@ public class PreferencesKeyServerActivity extends ActionBarActivity implements O } } - public void onDeleted(Editor editor) { + public void onDeleted(Editor editor, boolean wasNewItem) { // nothing to do } + @Override + public void onEdited() { + + } + public void onClick(View v) { KeyServerEditor view = (KeyServerEditor) mInflater.inflate(R.layout.key_server_editor, mEditors, false); diff --git a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/Editor.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/Editor.java index 1cf510d3a..7b21c189d 100644 --- a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/Editor.java +++ b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/Editor.java @@ -18,8 +18,10 @@ package org.sufficientlysecure.keychain.ui.widget; public interface Editor { public interface EditorListener { - public void onDeleted(Editor editor); + public void onDeleted(Editor editor, boolean wasNewItem); + public void onEdited(); } public void setEditorListener(EditorListener listener); + public boolean needsSaving(); } diff --git a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/KeyEditor.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/KeyEditor.java index 7e0acfa28..0dfc6dc5e 100644 --- a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/KeyEditor.java +++ b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/KeyEditor.java @@ -16,6 +16,20 @@ package org.sufficientlysecure.keychain.ui.widget; +import java.text.DateFormat; +import java.util.Calendar; +import java.util.Date; +import java.util.GregorianCalendar; +import java.util.TimeZone; +import java.util.Vector; + +import org.spongycastle.bcpg.sig.KeyFlags; +import org.spongycastle.openpgp.PGPKeyFlags; +import org.spongycastle.openpgp.PGPPublicKey; +import org.spongycastle.openpgp.PGPSecretKey; +import org.sufficientlysecure.keychain.R; +import org.sufficientlysecure.keychain.pgp.PgpKeyHelper; +import org.sufficientlysecure.keychain.util.Choice; import android.annotation.TargetApi; import android.app.DatePickerDialog; import android.app.Dialog; @@ -26,7 +40,13 @@ import android.util.AttributeSet; import android.view.View; import android.view.View.OnClickListener; import android.view.ViewGroup; -import android.widget.*; +import android.widget.CheckBox; +import android.widget.CompoundButton; +import android.widget.DatePicker; +import android.widget.LinearLayout; +import android.widget.TableLayout; +import android.widget.TableRow; +import android.widget.TextView; import com.beardedhen.androidbootstrap.BootstrapButton; import org.spongycastle.openpgp.PGPPublicKey; import org.spongycastle.openpgp.PGPSecretKey; @@ -47,11 +67,30 @@ public class KeyEditor extends LinearLayout implements Editor, OnClickListener { BootstrapButton mDeleteButton; TextView mAlgorithm; TextView mKeyId; - Spinner mUsage; TextView mCreationDate; BootstrapButton mExpiryDateButton; GregorianCalendar mCreatedDate; GregorianCalendar mExpiryDate; + GregorianCalendar mOriginalExpiryDate = null; + CheckBox mChkCertify; + CheckBox mChkSign; + CheckBox mChkEncrypt; + CheckBox mChkAuthenticate; + int mUsage; + int mOriginalUsage; + boolean mIsNewKey; + + private CheckBox.OnCheckedChangeListener mCheckChanged = new CheckBox.OnCheckedChangeListener() + { + @Override + public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) + { + if (mEditorListener != null) { + mEditorListener.onEdited(); + } + } + }; + private int mDatePickerResultCount = 0; private DatePickerDialog.OnDateSetListener mExpiryDateSetListener = @@ -61,7 +100,18 @@ public class KeyEditor extends LinearLayout implements Editor, OnClickListener { if (mDatePickerResultCount++ == 0) { GregorianCalendar date = new GregorianCalendar(TimeZone.getTimeZone("UTC")); date.set(year, monthOfYear, dayOfMonth); - setExpiryDate(date); + if (mOriginalExpiryDate != null) { + long numDays = (date.getTimeInMillis() / 86400000) - (mOriginalExpiryDate.getTimeInMillis() / 86400000); + if (numDays == 0) + setExpiryDate(mOriginalExpiryDate); + else + setExpiryDate(date); + } else { + setExpiryDate(date); + } + if (mEditorListener != null) { + mEditorListener.onEdited(); + } } } }; @@ -83,21 +133,17 @@ public class KeyEditor extends LinearLayout implements Editor, OnClickListener { mKeyId = (TextView) findViewById(R.id.keyId); mCreationDate = (TextView) findViewById(R.id.creation); mExpiryDateButton = (BootstrapButton) findViewById(R.id.expiry); - mUsage = (Spinner) findViewById(R.id.usage); - Choice choices[] = { - new Choice(Id.choice.usage.sign_only, getResources().getString( - R.string.choice_sign_only)), - new Choice(Id.choice.usage.encrypt_only, getResources().getString( - R.string.choice_encrypt_only)), - new Choice(Id.choice.usage.sign_and_encrypt, getResources().getString( - R.string.choice_sign_and_encrypt)), }; - ArrayAdapter<Choice> adapter = new ArrayAdapter<Choice>(getContext(), - android.R.layout.simple_spinner_item, choices); - adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); - mUsage.setAdapter(adapter); mDeleteButton = (BootstrapButton) findViewById(R.id.delete); mDeleteButton.setOnClickListener(this); + mChkCertify = (CheckBox) findViewById(R.id.chkCertify); + mChkCertify.setOnCheckedChangeListener(mCheckChanged); + mChkSign = (CheckBox) findViewById(R.id.chkSign); + mChkSign.setOnCheckedChangeListener(mCheckChanged); + mChkEncrypt = (CheckBox) findViewById(R.id.chkEncrypt); + mChkEncrypt.setOnCheckedChangeListener(mCheckChanged); + mChkAuthenticate = (CheckBox) findViewById(R.id.chkAuthenticate); + mChkAuthenticate.setOnCheckedChangeListener(mCheckChanged); setExpiryDate(null); @@ -125,6 +171,9 @@ public class KeyEditor extends LinearLayout implements Editor, OnClickListener { // Note: Ignore results after the first one - android sends multiples. if (mDatePickerResultCount++ == 0) { setExpiryDate(null); + if (mEditorListener != null) { + mEditorListener.onEdited(); + } } } }); @@ -155,12 +204,14 @@ public class KeyEditor extends LinearLayout implements Editor, OnClickListener { public void setCanEdit(boolean bCanEdit) { if (!bCanEdit) { mDeleteButton.setVisibility(View.INVISIBLE); - mUsage.setEnabled(false); mExpiryDateButton.setEnabled(false); + mChkSign.setEnabled(false); //certify is always disabled + mChkEncrypt.setEnabled(false); + mChkAuthenticate.setEnabled(false); } } - public void setValue(PGPSecretKey key, boolean isMasterKey, int usage) { + public void setValue(PGPSecretKey key, boolean isMasterKey, int usage, boolean isNewKey) { mKey = key; mIsMasterKey = isMasterKey; @@ -175,47 +226,45 @@ public class KeyEditor extends LinearLayout implements Editor, OnClickListener { Vector<Choice> choices = new Vector<Choice>(); boolean isElGamalKey = (key.getPublicKey().getAlgorithm() == PGPPublicKey.ELGAMAL_ENCRYPT); boolean isDSAKey = (key.getPublicKey().getAlgorithm() == PGPPublicKey.DSA); - if (!isElGamalKey) { - choices.add(new Choice(Id.choice.usage.sign_only, getResources().getString( - R.string.choice_sign_only))); + if (isElGamalKey) { + mChkSign.setVisibility(View.INVISIBLE); + TableLayout table = (TableLayout)findViewById(R.id.table_keylayout); + TableRow row = (TableRow)findViewById(R.id.row_sign); + table.removeView(row); } - if (!mIsMasterKey && !isDSAKey) { - choices.add(new Choice(Id.choice.usage.encrypt_only, getResources().getString( - R.string.choice_encrypt_only))); + if (isDSAKey) { + mChkEncrypt.setVisibility(View.INVISIBLE); + TableLayout table = (TableLayout)findViewById(R.id.table_keylayout); + TableRow row = (TableRow)findViewById(R.id.row_encrypt); + table.removeView(row); } - if (!isElGamalKey && !isDSAKey) { - choices.add(new Choice(Id.choice.usage.sign_and_encrypt, getResources().getString( - R.string.choice_sign_and_encrypt))); + if (!mIsMasterKey) { + mChkCertify.setVisibility(View.INVISIBLE); + TableLayout table = (TableLayout)findViewById(R.id.table_keylayout); + TableRow row = (TableRow)findViewById(R.id.row_certify); + table.removeView(row); + } else { + TextView mLabelUsage2= (TextView) findViewById(R.id.label_usage2); + mLabelUsage2.setVisibility(View.INVISIBLE); } - ArrayAdapter<Choice> adapter = new ArrayAdapter<Choice>(getContext(), - android.R.layout.simple_spinner_item, choices); - adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); - mUsage.setAdapter(adapter); - - // Set value in choice dropdown to key int selectId = 0; - if (PgpKeyHelper.isEncryptionKey(key)) { - if (PgpKeyHelper.isSigningKey(key)) { - selectId = Id.choice.usage.sign_and_encrypt; - } else { - selectId = Id.choice.usage.encrypt_only; - } + mIsNewKey = isNewKey; + if (isNewKey) { + mUsage = usage; + mChkCertify.setChecked((usage &= KeyFlags.CERTIFY_OTHER) == KeyFlags.CERTIFY_OTHER); + mChkSign.setChecked((usage &= KeyFlags.SIGN_DATA) == KeyFlags.SIGN_DATA); + mChkEncrypt.setChecked(((usage &= KeyFlags.ENCRYPT_COMMS) == KeyFlags.ENCRYPT_COMMS) || + ((usage &= KeyFlags.ENCRYPT_STORAGE) == KeyFlags.ENCRYPT_STORAGE)); + mChkAuthenticate.setChecked((usage &= KeyFlags.AUTHENTICATION) == KeyFlags.AUTHENTICATION); } else { - // set usage if it is predefined - if (usage != -1) { - selectId = usage; - } else { - selectId = Id.choice.usage.sign_only; - } - - } - - for (int i = 0; i < choices.size(); ++i) { - if (choices.get(i).getId() == selectId) { - mUsage.setSelection(i); - break; - } + mUsage = PgpKeyHelper.getKeyUsage(key); + mOriginalUsage = mUsage; + if (key.isMasterKey()) + mChkCertify.setChecked(PgpKeyHelper.isCertificationKey(key)); + mChkSign.setChecked(PgpKeyHelper.isSigningKey(key)); + mChkEncrypt.setChecked(PgpKeyHelper.isEncryptionKey(key)); + mChkAuthenticate.setChecked(PgpKeyHelper.isAuthenticationKey(key)); } GregorianCalendar cal = new GregorianCalendar(TimeZone.getTimeZone("UTC")); @@ -228,6 +277,7 @@ public class KeyEditor extends LinearLayout implements Editor, OnClickListener { } else { cal.setTime(PgpKeyHelper.getExpiryDate(key)); setExpiryDate(cal); + mOriginalExpiryDate = cal; } } @@ -241,7 +291,7 @@ public class KeyEditor extends LinearLayout implements Editor, OnClickListener { if (v == mDeleteButton) { parent.removeView(this); if (mEditorListener != null) { - mEditorListener.onDeleted(this); + mEditorListener.onDeleted(this, mIsNewKey); } } } @@ -273,7 +323,41 @@ public class KeyEditor extends LinearLayout implements Editor, OnClickListener { } public int getUsage() { - return ((Choice) mUsage.getSelectedItem()).getId(); + mUsage = (mUsage & ~KeyFlags.CERTIFY_OTHER) | (mChkCertify.isChecked() ? KeyFlags.CERTIFY_OTHER : 0); + mUsage = (mUsage & ~KeyFlags.SIGN_DATA) | (mChkSign.isChecked() ? KeyFlags.SIGN_DATA : 0); + mUsage = (mUsage & ~KeyFlags.ENCRYPT_COMMS) | (mChkEncrypt.isChecked() ? KeyFlags.ENCRYPT_COMMS : 0); + mUsage = (mUsage & ~KeyFlags.ENCRYPT_STORAGE) | (mChkEncrypt.isChecked() ? KeyFlags.ENCRYPT_STORAGE : 0); + mUsage = (mUsage & ~KeyFlags.AUTHENTICATION) | (mChkAuthenticate.isChecked() ? KeyFlags.AUTHENTICATION : 0); + + return mUsage; + } + + public boolean needsSaving() + { + if (mIsNewKey) + return true; + + boolean retval = (getUsage() != mOriginalUsage); + + boolean dateChanged; + boolean mOEDNull = (mOriginalExpiryDate == null); + boolean mEDNull = (mExpiryDate == null); + if (mOEDNull != mEDNull) { + dateChanged = true; + } else { + if(mOEDNull) //both null, no change + dateChanged = false; + else + dateChanged = ((mExpiryDate.compareTo(mOriginalExpiryDate)) != 0); + } + retval |= dateChanged; + + return retval; + } + + public boolean getIsNewKey() + { + return mIsNewKey; } } diff --git a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/KeyServerEditor.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/KeyServerEditor.java index f92c7532a..171763672 100644 --- a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/KeyServerEditor.java +++ b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/KeyServerEditor.java @@ -66,11 +66,16 @@ public class KeyServerEditor extends LinearLayout implements Editor, OnClickList if (v == mDeleteButton) { parent.removeView(this); if (mEditorListener != null) { - mEditorListener.onDeleted(this); + mEditorListener.onDeleted(this, false); } } } + @Override + public boolean needsSaving() { + return false; + } + public void setEditorListener(EditorListener listener) { mEditorListener = listener; } diff --git a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/SectionView.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/SectionView.java index 1ef178f15..6c7737e6d 100644 --- a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/SectionView.java +++ b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/SectionView.java @@ -16,6 +16,23 @@ package org.sufficientlysecure.keychain.ui.widget; +import java.util.ArrayList; +import java.util.List; +import java.util.Vector; + +import org.spongycastle.openpgp.PGPKeyFlags; +import org.spongycastle.openpgp.PGPSecretKey; +import org.sufficientlysecure.keychain.Id; +import org.sufficientlysecure.keychain.R; +import org.sufficientlysecure.keychain.pgp.PgpConversionHelper; +import org.sufficientlysecure.keychain.service.KeychainIntentService; +import org.sufficientlysecure.keychain.service.KeychainIntentServiceHandler; +import org.sufficientlysecure.keychain.service.PassphraseCacheService; +import org.sufficientlysecure.keychain.ui.dialog.ProgressDialogFragment; +import org.sufficientlysecure.keychain.ui.widget.Editor.EditorListener; +import org.sufficientlysecure.keychain.util.Choice; + +import android.app.AlertDialog; import android.app.ProgressDialog; import android.content.Context; import android.content.DialogInterface; @@ -44,23 +61,31 @@ import org.sufficientlysecure.keychain.ui.dialog.ProgressDialogFragment; import org.sufficientlysecure.keychain.ui.widget.Editor.EditorListener; import org.sufficientlysecure.keychain.util.Choice; -import java.util.Vector; -public class SectionView extends LinearLayout implements OnClickListener, EditorListener { + +public class SectionView extends LinearLayout implements OnClickListener, EditorListener, Editor { private LayoutInflater mInflater; private BootstrapButton mPlusButton; private ViewGroup mEditors; private TextView mTitle; private int mType = 0; + private EditorListener mEditorListener = null; private Choice mNewKeyAlgorithmChoice; private int mNewKeySize; + private boolean oldItemDeleted = false; + private ArrayList<String> mDeletedIDs = new ArrayList<String>(); + private ArrayList<PGPSecretKey> mDeletedKeys = new ArrayList<PGPSecretKey>(); private boolean mCanEdit = true; private ActionBarActivity mActivity; private ProgressDialogFragment mGeneratingDialog; + public void setEditorListener(EditorListener listener) { + mEditorListener = listener; + } + public SectionView(Context context) { super(context); mActivity = (ActionBarActivity) context; @@ -124,8 +149,26 @@ public class SectionView extends LinearLayout implements OnClickListener, Editor /** * {@inheritDoc} */ - public void onDeleted(Editor editor) { + public void onDeleted(Editor editor, boolean wasNewItem) { + oldItemDeleted |= !wasNewItem; + if (oldItemDeleted) { + if (mType == Id.type.user_id) + mDeletedIDs.add(((UserIdEditor)editor).getOriginalID()); + else if (mType == Id.type.key) + mDeletedKeys.add(((KeyEditor)editor).getValue()); + + } this.updateEditorsVisible(); + if (mEditorListener != null) { + mEditorListener.onEdited(); + } + } + + @Override + public void onEdited() { + if (mEditorListener != null) { + mEditorListener.onEdited(); + } } protected void updateEditorsVisible() { @@ -133,6 +176,94 @@ public class SectionView extends LinearLayout implements OnClickListener, Editor mEditors.setVisibility(hasChildren ? View.VISIBLE : View.GONE); } + public boolean needsSaving() + { + //check each view for needs saving, take account of deleted items + boolean ret = oldItemDeleted; + for (int i = 0; i < mEditors.getChildCount(); ++i) { + Editor editor = (Editor) mEditors.getChildAt(i); + ret |= editor.needsSaving(); + if (mType == Id.type.user_id) + ret |= ((UserIdEditor)editor).primarySwapped(); + } + return ret; + } + + public boolean primaryChanged() + { + boolean ret = false; + for (int i = 0; i < mEditors.getChildCount(); ++i) { + Editor editor = (Editor) mEditors.getChildAt(i); + if (mType == Id.type.user_id) + ret |= ((UserIdEditor)editor).primarySwapped(); + } + return ret; + } + + public String getOriginalPrimaryID() + { //NB: this will have to change when we change how Primary IDs are chosen, and so we need to be + // careful about where Master key capabilities are stored... multiple primaries and + // revoked ones make this harder than the simple case we are continuing to assume here + for (int i = 0; i < mEditors.getChildCount(); ++i) { + Editor editor = (Editor) mEditors.getChildAt(i); + if (mType == Id.type.user_id) { + if(((UserIdEditor)editor).getIsOriginallyMainUserID()) { + return ((UserIdEditor)editor).getOriginalID(); + } + } + } + return null; + } + + public ArrayList<String> getOriginalIDs() + { + ArrayList<String> orig = new ArrayList<String>(); + if (mType == Id.type.user_id) { + for (int i = 0; i < mEditors.getChildCount(); ++i) { + UserIdEditor editor = (UserIdEditor) mEditors.getChildAt(i); + if (editor.isMainUserId()) + orig.add(0, editor.getOriginalID()); + else + orig.add(editor.getOriginalID()); + } + return orig; + } else { + return null; + } + } + + public ArrayList<String> getDeletedIDs() + { + return mDeletedIDs; + } + + public ArrayList<PGPSecretKey> getDeletedKeys() + { + return mDeletedKeys; + } + + public List<Boolean> getNeedsSavingArray() + { + ArrayList<Boolean> mList = new ArrayList<Boolean>(); + for (int i = 0; i < mEditors.getChildCount(); ++i) { + Editor editor = (Editor) mEditors.getChildAt(i); + mList.add(editor.needsSaving()); + } + return mList; + } + + public List<Boolean> getNewKeysArray() + { + ArrayList<Boolean> mList = new ArrayList<Boolean>(); + if (mType == Id.type.key) { + for (int i = 0; i < mEditors.getChildCount(); ++i) { + KeyEditor editor = (KeyEditor) mEditors.getChildAt(i); + mList.add(editor.getIsNewKey()); + } + } + return mList; + } + /** * {@inheritDoc} */ @@ -143,10 +274,11 @@ public class SectionView extends LinearLayout implements OnClickListener, Editor UserIdEditor view = (UserIdEditor) mInflater.inflate( R.layout.edit_key_user_id_item, mEditors, false); view.setEditorListener(this); - if (mEditors.getChildCount() == 0) { - view.setIsMainUserId(true); - } + view.setValue("", mEditors.getChildCount() == 0, true); mEditors.addView(view); + if (mEditorListener != null) { + mEditorListener.onEdited(); + } break; } @@ -185,10 +317,7 @@ public class SectionView extends LinearLayout implements OnClickListener, Editor UserIdEditor view = (UserIdEditor) mInflater.inflate(R.layout.edit_key_user_id_item, mEditors, false); view.setEditorListener(this); - view.setValue(userId); - if (mEditors.getChildCount() == 0) { - view.setIsMainUserId(true); - } + view.setValue(userId, mEditors.getChildCount() == 0, false); view.setCanEdit(mCanEdit); mEditors.addView(view); } @@ -196,7 +325,7 @@ public class SectionView extends LinearLayout implements OnClickListener, Editor this.updateEditorsVisible(); } - public void setKeys(Vector<PGPSecretKey> list, Vector<Integer> usages) { + public void setKeys(Vector<PGPSecretKey> list, Vector<Integer> usages, boolean newKeys) { if (mType != Id.type.key) { return; } @@ -209,7 +338,7 @@ public class SectionView extends LinearLayout implements OnClickListener, Editor false); view.setEditorListener(this); boolean isMasterKey = (mEditors.getChildCount() == 0); - view.setValue(list.get(i), isMasterKey, usages.get(i)); + view.setValue(list.get(i), isMasterKey, usages.get(i), newKeys); view.setCanEdit(mCanEdit); mEditors.addView(view); } @@ -289,8 +418,14 @@ public class SectionView extends LinearLayout implements OnClickListener, Editor KeyEditor view = (KeyEditor) mInflater.inflate(R.layout.edit_key_key_item, mEditors, false); view.setEditorListener(SectionView.this); - view.setValue(newKey, newKey.isMasterKey(), -1); + int usage = 0; + if (mEditors.getChildCount() == 0) + usage = PGPKeyFlags.CAN_CERTIFY; + view.setValue(newKey, newKey.isMasterKey(), usage, true); mEditors.addView(view); SectionView.this.updateEditorsVisible(); + if (mEditorListener != null) { + mEditorListener.onEdited(); + } } } diff --git a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/UserIdEditor.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/UserIdEditor.java index ed81b162e..d4b15613a 100644 --- a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/UserIdEditor.java +++ b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/UserIdEditor.java @@ -16,6 +16,11 @@ package org.sufficientlysecure.keychain.ui.widget; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import org.sufficientlysecure.keychain.R; +import org.sufficientlysecure.keychain.pgp.PgpKeyHelper; import android.content.Context; import android.text.Editable; import android.text.TextWatcher; @@ -26,28 +31,23 @@ import android.view.View.OnClickListener; import android.view.ViewGroup; import android.widget.*; import com.beardedhen.androidbootstrap.BootstrapButton; -import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.helper.ContactHelper; -import java.util.regex.Matcher; -import java.util.regex.Pattern; public class UserIdEditor extends LinearLayout implements Editor, OnClickListener { private EditorListener mEditorListener = null; private BootstrapButton mDeleteButton; private RadioButton mIsMainUserId; + private String mOriginalID; private EditText mName; + private String mOriginalName; private AutoCompleteTextView mEmail; + private String mOriginalEmail; private EditText mComment; - - public static class NoNameException extends Exception { - static final long serialVersionUID = 0xf812773343L; - - public NoNameException(String message) { - super(message); - } - } + private String mOriginalComment; + private boolean mOriginallyMainUserID; + private boolean mIsNewId; public void setCanEdit(boolean bCanEdit) { if (!bCanEdit) { @@ -59,14 +59,6 @@ public class UserIdEditor extends LinearLayout implements Editor, OnClickListene } } - public static class NoEmailException extends Exception { - static final long serialVersionUID = 0xf812773344L; - - public NoEmailException(String message) { - super(message); - } - } - public static class InvalidEmailException extends Exception { static final long serialVersionUID = 0xf812773345L; @@ -83,6 +75,24 @@ public class UserIdEditor extends LinearLayout implements Editor, OnClickListene super(context, attrs); } + TextWatcher mTextWatcher = new TextWatcher() { + @Override + public void onTextChanged(CharSequence s, int start, int before, int count) { + } + + @Override + public void beforeTextChanged(CharSequence s, int start, int count, int after) { + } + + @Override + public void afterTextChanged(Editable s) + { + if (mEditorListener != null) { + mEditorListener.onEdited(); + } + } + }; + @Override protected void onFinishInflate() { setDrawingCacheEnabled(true); @@ -94,8 +104,10 @@ public class UserIdEditor extends LinearLayout implements Editor, OnClickListene mIsMainUserId.setOnClickListener(this); mName = (EditText) findViewById(R.id.name); + mName.addTextChangedListener(mTextWatcher); mEmail = (AutoCompleteTextView) findViewById(R.id.email); mComment = (EditText) findViewById(R.id.comment); + mComment.addTextChangedListener(mTextWatcher); mEmail.setThreshold(1); // Start working from first character @@ -127,36 +139,45 @@ public class UserIdEditor extends LinearLayout implements Editor, OnClickListene // remove drawable if email is empty mEmail.setCompoundDrawablesWithIntrinsicBounds(0, 0, 0, 0); } + if (mEditorListener != null) { + mEditorListener.onEdited(); + } } }); super.onFinishInflate(); } - public void setValue(String userId) { + public void setValue(String userId, boolean isMainID, boolean isNewId) { + mName.setText(""); + mOriginalName = ""; mComment.setText(""); + mOriginalComment = ""; mEmail.setText(""); - - Pattern withComment = Pattern.compile("^(.*) [(](.*)[)] <(.*)>$"); - Matcher matcher = withComment.matcher(userId); - if (matcher.matches()) { - mName.setText(matcher.group(1)); - mComment.setText(matcher.group(2)); - mEmail.setText(matcher.group(3)); - return; + mOriginalEmail = ""; + mIsNewId = isNewId; + mOriginalID = userId; + + String[] result = PgpKeyHelper.splitUserId(userId); + if (result[0] != null) { + mName.setText(result[0]); + mOriginalName = result[0]; } - - Pattern withoutComment = Pattern.compile("^(.*) <(.*)>$"); - matcher = withoutComment.matcher(userId); - if (matcher.matches()) { - mName.setText(matcher.group(1)); - mEmail.setText(matcher.group(2)); - return; + if (result[1] != null) { + mEmail.setText(result[1]); + mOriginalEmail = result[1]; + } + if (result[2] != null) { + mComment.setText(result[2]); + mOriginalComment = result[2]; } + + mOriginallyMainUserID = isMainID; + setIsMainUserId(isMainID); } - public String getValue() throws NoNameException, NoEmailException { + public String getValue() { String name = ("" + mName.getText()).trim(); String email = ("" + mEmail.getText()).trim(); String comment = ("" + mComment.getText()).trim(); @@ -173,16 +194,7 @@ public class UserIdEditor extends LinearLayout implements Editor, OnClickListene // ok, empty one... return userId; } - - // otherwise make sure that name and email exist - if (name.equals("")) { - throw new NoNameException("need a name"); - } - - if (email.equals("")) { - throw new NoEmailException("need an email"); - } - + //TODO: check gpg accepts an entirely empty ID packet. specs say this is allowed return userId; } @@ -192,7 +204,7 @@ public class UserIdEditor extends LinearLayout implements Editor, OnClickListene boolean wasMainUserId = mIsMainUserId.isChecked(); parent.removeView(this); if (mEditorListener != null) { - mEditorListener.onDeleted(this); + mEditorListener.onDeleted(this, mIsNewId); } if (wasMainUserId && parent.getChildCount() > 0) { UserIdEditor editor = (UserIdEditor) parent.getChildAt(0); @@ -207,6 +219,9 @@ public class UserIdEditor extends LinearLayout implements Editor, OnClickListene editor.setIsMainUserId(false); } } + if (mEditorListener != null) { + mEditorListener.onEdited(); + } } } @@ -221,4 +236,29 @@ public class UserIdEditor extends LinearLayout implements Editor, OnClickListene public void setEditorListener(EditorListener listener) { mEditorListener = listener; } + + @Override + public boolean needsSaving() { + boolean retval = false; //(mOriginallyMainUserID != isMainUserId()); + retval |= !(mOriginalName.equals( ("" + mName.getText()).trim() ) ); + retval |= !(mOriginalEmail.equals( ("" + mEmail.getText()).trim() ) ); + retval |= !(mOriginalComment.equals( ("" + mComment.getText()).trim() ) ); + retval |= mIsNewId; + return retval; + } + + public boolean getIsOriginallyMainUserID() + { + return mOriginallyMainUserID; + } + + public boolean primarySwapped() + { + return (mOriginallyMainUserID != isMainUserId()); + } + + public String getOriginalID() + { + return mOriginalID; + } } |