diff --git a/res/values/strings.xml b/res/values/strings.xml index 81fd592d8..84b8ec6f5 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -61,6 +61,7 @@ Forward Move Continue + Back Done Remove Discard @@ -1026,6 +1027,9 @@ Welcome to K-9 Mail setup. K-9 is an open source mail client for Android origin Refuse to save draft message. Refuse to save draft message marked encrypted. + Continue without public key? + One or more recipients do not have a saved public key. Continue? + This message can\'t be displayed because the charset \"%s\" wasn\'t found. Select text to copy. diff --git a/src/com/fsck/k9/activity/MessageCompose.java b/src/com/fsck/k9/activity/MessageCompose.java index 9c6e9b634..86e618c20 100644 --- a/src/com/fsck/k9/activity/MessageCompose.java +++ b/src/com/fsck/k9/activity/MessageCompose.java @@ -81,6 +81,7 @@ import com.fsck.k9.mail.store.LocalStore.LocalAttachmentBody; public class MessageCompose extends K9Activity implements OnClickListener, OnFocusChangeListener { private static final int DIALOG_SAVE_OR_DISCARD_DRAFT_MESSAGE = 1; private static final int DIALOG_REFUSE_TO_SAVE_DRAFT_MARKED_ENCRYPTED = 2; + private static final int DIALOG_CONTINUE_WITHOUT_PUBLIC_KEY = 3; private static final String ACTION_COMPOSE = "com.fsck.k9.intent.action.COMPOSE"; private static final String ACTION_REPLY = "com.fsck.k9.intent.action.REPLY"; @@ -210,6 +211,7 @@ public class MessageCompose extends K9Activity implements OnClickListener, OnFoc private PgpData mPgpData = null; private boolean mAutoEncrypt = false; + private boolean mContinueWithoutPublicKey = false; private String mReferences; private String mInReplyTo; @@ -444,6 +446,8 @@ public class MessageCompose extends K9Activity implements OnClickListener, OnFoc public void afterTextChanged(android.text.Editable s) { } }; + // For watching changes to the To:, Cc:, and Bcc: fields for auto-encryption on a matching + // address. TextWatcher recipientWatcher = new TextWatcher() { public void beforeTextChanged(CharSequence s, int start, int before, int after) { } @@ -455,10 +459,10 @@ public class MessageCompose extends K9Activity implements OnClickListener, OnFoc final CryptoProvider crypto = mAccount.getCryptoProvider(); if (mAutoEncrypt && crypto.isAvailable(getApplicationContext())) { for (Address address : getRecipientAddresses()) { - long ids[] = crypto.getPublicKeyIdsFromEmail(getApplicationContext(), - address.getAddress()); - if (ids != null && ids.length > 0) { + if (crypto.hasPublicKeyForEmail(getApplicationContext(), + address.getAddress())) { mEncryptCheckbox.setChecked(true); + mContinueWithoutPublicKey = false; break; } } @@ -950,6 +954,10 @@ public class MessageCompose extends K9Activity implements OnClickListener, OnFoc return Address.parseUnencoded(view.getText().toString().trim()); } + /* + * Returns an Address array of recipients this email will be sent to. + * @return Address array of recipients this email will be sent to. + */ private Address[] getRecipientAddresses() { String addresses = mToView.getText().toString() + mCcView.getText().toString() + mBccView.getText().toString(); @@ -1497,20 +1505,20 @@ public class MessageCompose extends K9Activity implements OnClickListener, OnFoc Toast.makeText(this, getString(R.string.message_compose_error_no_recipients), Toast.LENGTH_LONG).show(); return; } + final CryptoProvider crypto = mAccount.getCryptoProvider(); if (mEncryptCheckbox.isChecked() && !mPgpData.hasEncryptionKeys()) { mMessageFormat = MessageFormat.TEXT; // key selection before encryption StringBuilder emails = new StringBuilder(); - Address[][] addresses = new Address[][] { getAddresses(mToView), - getAddresses(mCcView), - getAddresses(mBccView) - }; - for (Address[] addressArray : addresses) { - for (Address address : addressArray) { - if (emails.length() != 0) { - emails.append(','); - } - emails.append(address.getAddress()); + for (Address address : getRecipientAddresses()) { + if (emails.length() != 0) { + emails.append(','); + } + emails.append(address.getAddress()); + if (!mContinueWithoutPublicKey && + !crypto.hasPublicKeyForEmail(this, address.getAddress())) { + showDialog(DIALOG_CONTINUE_WITHOUT_PUBLIC_KEY); + return; } } if (emails.length() != 0) { @@ -1519,7 +1527,7 @@ public class MessageCompose extends K9Activity implements OnClickListener, OnFoc emails.append(mIdentity.getEmail()); mPreventDraftSaving = true; - if (!mAccount.getCryptoProvider().selectEncryptionKeys(MessageCompose.this, emails.toString(), mPgpData)) { + if (!crypto.selectEncryptionKeys(MessageCompose.this, emails.toString(), mPgpData)) { mPreventDraftSaving = false; } return; @@ -1528,7 +1536,7 @@ public class MessageCompose extends K9Activity implements OnClickListener, OnFoc if (mPgpData.getEncryptedData() == null) { String text = buildText(false).getText(); mPreventDraftSaving = true; - if (!mAccount.getCryptoProvider().encrypt(this, text, mPgpData)) { + if (!crypto.encrypt(this, text, mPgpData)) { mPreventDraftSaving = false; } return; @@ -2015,6 +2023,24 @@ public class MessageCompose extends K9Activity implements OnClickListener, OnFoc } }) .create(); + case DIALOG_CONTINUE_WITHOUT_PUBLIC_KEY: + return new AlertDialog.Builder(this) + .setTitle(R.string.continue_without_public_key_dlg_title) + .setMessage(R.string.continue_without_public_key_instructions_fmt) + .setPositiveButton(R.string.continue_action, new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int whichButton) { + dismissDialog(DIALOG_CONTINUE_WITHOUT_PUBLIC_KEY); + mContinueWithoutPublicKey = true; + onSend(); + } + }) + .setNegativeButton(R.string.back_action, new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int whichButton) { + dismissDialog(DIALOG_CONTINUE_WITHOUT_PUBLIC_KEY); + mContinueWithoutPublicKey = false; + } + }) + .create(); } return super.onCreateDialog(id); } diff --git a/src/com/fsck/k9/crypto/Apg.java b/src/com/fsck/k9/crypto/Apg.java index 350a6c284..58af5ac49 100644 --- a/src/com/fsck/k9/crypto/Apg.java +++ b/src/com/fsck/k9/crypto/Apg.java @@ -243,11 +243,9 @@ public class Apg extends CryptoProvider { public long[] getPublicKeyIdsFromEmail(Context context, String email) { long ids[] = null; try { - Uri contentUri = Uri.withAppendedPath(Apg.CONTENT_URI_PUBLIC_KEY_RING_BY_EMAILS, - email); + Uri contentUri = Uri.withAppendedPath(Apg.CONTENT_URI_PUBLIC_KEY_RING_BY_EMAILS, email); Cursor c = context.getContentResolver().query(contentUri, - new String[] { "master_key_id" }, - null, null, null); + new String[] { "master_key_id" }, null, null, null); if (c != null && c.getCount() > 0) { ids = new long[c.getCount()]; while (c.moveToNext()) { @@ -267,6 +265,62 @@ public class Apg extends CryptoProvider { return ids; } + /** + * Find out if a given email has a secret key. + * + * @param context + * @param email The email in question. + * @return true if there is a secret key for this email. + */ + @Override + public boolean hasSecretKeyForEmail(Context context, String email) { + try { + Uri contentUri = Uri.withAppendedPath(Apg.CONTENT_URI_SECRET_KEY_RING_BY_EMAILS, email); + Cursor c = context.getContentResolver().query(contentUri, + new String[] { "master_key_id" }, null, null, null); + if (c != null && c.getCount() > 0) { + c.close(); + return true; + } + if (c != null) { + c.close(); + } + } catch (SecurityException e) { + Toast.makeText(context, + context.getResources().getString(R.string.insufficient_apg_permissions), + Toast.LENGTH_LONG).show(); + } + return false; + } + + /** + * Find out if a given email has a public key. + * + * @param context + * @param email The email in question. + * @return true if there is a public key for this email. + */ + @Override + public boolean hasPublicKeyForEmail(Context context, String email) { + try { + Uri contentUri = Uri.withAppendedPath(Apg.CONTENT_URI_PUBLIC_KEY_RING_BY_EMAILS, email); + Cursor c = context.getContentResolver().query(contentUri, + new String[] { "master_key_id" }, null, null, null); + if (c != null && c.getCount() > 0) { + c.close(); + return true; + } + if (c != null) { + c.close(); + } + } catch (SecurityException e) { + Toast.makeText(context, + context.getResources().getString(R.string.insufficient_apg_permissions), + Toast.LENGTH_LONG).show(); + } + return false; + } + /** * Get the user id based on the key id. * diff --git a/src/com/fsck/k9/crypto/CryptoProvider.java b/src/com/fsck/k9/crypto/CryptoProvider.java index 373a62df9..73ce8ba4d 100644 --- a/src/com/fsck/k9/crypto/CryptoProvider.java +++ b/src/com/fsck/k9/crypto/CryptoProvider.java @@ -25,6 +25,8 @@ abstract public class CryptoProvider { abstract public boolean decrypt(Activity activity, String data, PgpData pgpData); abstract public long[] getSecretKeyIdsFromEmail(Context context, String email); abstract public long[] getPublicKeyIdsFromEmail(Context context, String email); + abstract public boolean hasSecretKeyForEmail(Context context, String email); + abstract public boolean hasPublicKeyForEmail(Context context, String email); abstract public String getUserId(Context context, long keyId); abstract public String getName(); abstract public boolean test(Context context); diff --git a/src/com/fsck/k9/crypto/None.java b/src/com/fsck/k9/crypto/None.java index cf2af7bc4..2ce271e3d 100644 --- a/src/com/fsck/k9/crypto/None.java +++ b/src/com/fsck/k9/crypto/None.java @@ -42,6 +42,16 @@ public class None extends CryptoProvider { return null; } + @Override + public boolean hasSecretKeyForEmail(Context context, String email) { + return false; + } + + @Override + public boolean hasPublicKeyForEmail(Context context, String email) { + return false; + } + @Override public String getUserId(Context context, long keyId) { return null;